mirror of
https://github.com/itchyny/mmv.git
synced 2025-12-26 14:14:57 +08:00
implement cycle renames
This commit is contained in:
parent
02f7964dc5
commit
33d7af2229
2 changed files with 141 additions and 2 deletions
60
mmv.go
60
mmv.go
|
|
@ -1,6 +1,11 @@
|
|||
package mmv
|
||||
|
||||
import "os"
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Move multiple files.
|
||||
func Move(files map[string]string) error {
|
||||
|
|
@ -22,8 +27,59 @@ type rename struct {
|
|||
|
||||
func buildRenames(files map[string]string) ([]rename, error) {
|
||||
rs := make([]rename, 0, 2*len(files))
|
||||
vs := make(map[string]int, len(files))
|
||||
revs := make(map[string]string, len(files))
|
||||
for src, dst := range files {
|
||||
rs = append(rs, rename{src, dst})
|
||||
revs[dst] = src
|
||||
}
|
||||
var i int
|
||||
for _, dst := range files {
|
||||
if vs[dst] > 0 {
|
||||
continue
|
||||
}
|
||||
i++
|
||||
var cycle bool
|
||||
for {
|
||||
vs[dst] = i
|
||||
if x, ok := files[dst]; ok {
|
||||
dst = x
|
||||
if vs[x] > 0 {
|
||||
cycle = vs[x] == i
|
||||
break
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
var tmp string
|
||||
if cycle {
|
||||
tmp = randomPath(filepath.Dir(dst))
|
||||
rs = append(rs, rename{dst, tmp})
|
||||
vs[dst]--
|
||||
}
|
||||
for {
|
||||
if src, ok := revs[dst]; ok && (!cycle || vs[src] == i) {
|
||||
rs = append(rs, rename{src, dst})
|
||||
if !cycle {
|
||||
vs[dst] = i
|
||||
}
|
||||
dst = src
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if cycle {
|
||||
rs = append(rs, rename{tmp, dst})
|
||||
}
|
||||
}
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
func randomPath(dir string) string {
|
||||
for {
|
||||
path := filepath.Join(dir, fmt.Sprint(rand.Uint64()))
|
||||
if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
|
||||
return path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
83
mmv_test.go
83
mmv_test.go
|
|
@ -16,6 +16,7 @@ func TestMove(t *testing.T) {
|
|||
files map[string]string
|
||||
contents map[string]string
|
||||
expected map[string]string
|
||||
cnt int
|
||||
err error
|
||||
}{
|
||||
{
|
||||
|
|
@ -27,6 +28,7 @@ func TestMove(t *testing.T) {
|
|||
files: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
cnt: 1,
|
||||
contents: map[string]string{
|
||||
"foo": "0",
|
||||
},
|
||||
|
|
@ -40,6 +42,7 @@ func TestMove(t *testing.T) {
|
|||
"foo": "qux",
|
||||
"bar": "quxx",
|
||||
},
|
||||
cnt: 2,
|
||||
contents: map[string]string{
|
||||
"foo": "0",
|
||||
"bar": "1",
|
||||
|
|
@ -51,6 +54,84 @@ func TestMove(t *testing.T) {
|
|||
"baz": "2",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "swap two files",
|
||||
files: map[string]string{
|
||||
"foo": "bar",
|
||||
"bar": "foo",
|
||||
},
|
||||
cnt: 3,
|
||||
contents: map[string]string{
|
||||
"foo": "0",
|
||||
"bar": "1",
|
||||
"baz": "2",
|
||||
},
|
||||
expected: map[string]string{
|
||||
"bar": "0",
|
||||
"foo": "1",
|
||||
"baz": "2",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "two swaps",
|
||||
files: map[string]string{
|
||||
"foo": "bar",
|
||||
"bar": "foo",
|
||||
"baz": "qux",
|
||||
"qux": "baz",
|
||||
},
|
||||
cnt: 6,
|
||||
contents: map[string]string{
|
||||
"foo": "0",
|
||||
"bar": "1",
|
||||
"baz": "2",
|
||||
"qux": "3",
|
||||
},
|
||||
expected: map[string]string{
|
||||
"bar": "0",
|
||||
"foo": "1",
|
||||
"qux": "2",
|
||||
"baz": "3",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "three files",
|
||||
files: map[string]string{
|
||||
"foo": "bar",
|
||||
"bar": "baz",
|
||||
"baz": "qux",
|
||||
},
|
||||
cnt: 3,
|
||||
contents: map[string]string{
|
||||
"foo": "0",
|
||||
"bar": "1",
|
||||
"baz": "2",
|
||||
},
|
||||
expected: map[string]string{
|
||||
"bar": "0",
|
||||
"baz": "1",
|
||||
"qux": "2",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "cycle three files",
|
||||
files: map[string]string{
|
||||
"foo": "bar",
|
||||
"bar": "baz",
|
||||
"baz": "foo",
|
||||
},
|
||||
cnt: 4,
|
||||
contents: map[string]string{
|
||||
"foo": "0",
|
||||
"bar": "1",
|
||||
"baz": "2",
|
||||
},
|
||||
expected: map[string]string{
|
||||
"bar": "0",
|
||||
"baz": "1",
|
||||
"foo": "2",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
|
@ -59,6 +140,8 @@ func TestMove(t *testing.T) {
|
|||
require.NoError(t, os.Chdir(dir))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, setupFiles(tc.contents))
|
||||
rs, _ := buildRenames(tc.files)
|
||||
assert.Equal(t, tc.cnt, len(rs))
|
||||
require.NoError(t, Move(tc.files))
|
||||
assert.Equal(t, tc.expected, fileContents("."))
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue