Skip to content

Commit 4499892

Browse files
committed
Fix up how tosec works
1 parent 0dbe018 commit 4499892

File tree

9 files changed

+249
-178
lines changed

9 files changed

+249
-178
lines changed

TODO.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Todo
2+
3+
* Handle cases where Sort results in >`size` _directories_
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import (
44
"fmt"
55
"maps"
66
"os"
7+
"runtime/debug"
78
"slices"
89

910
"github.com/spf13/cobra"
10-
"github.com/stilvoid/retrosort"
11+
retrosort "github.com/stilvoid/retro-sort"
1112
)
1213

1314
var src, dst string
@@ -25,6 +26,10 @@ func init() {
2526
rootCmd.Flags().BoolVarP(&printOnly, "dry-run", "n", false, "Dry run. Print the file names and exit")
2627
rootCmd.Flags().BoolVarP(&quiet, "quiet", "q", false, "Don't print anything, just do it")
2728
rootCmd.Flags().BoolVar(&tosec, "tosec", false, "Experimental: Detect TOSEC filenames and group related files")
29+
30+
if b, ok := debug.ReadBuildInfo(); ok {
31+
rootCmd.Version = b.Main.Version
32+
}
2833
}
2934

3035
var rootCmd = &cobra.Command{

entry.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package retrosort
2+
3+
import (
4+
"regexp"
5+
"strings"
6+
7+
"path/filepath"
8+
)
9+
10+
func getPrefix(fn string, prefixSize int) string {
11+
fn = strings.ToLower(filepath.Base(fn))
12+
13+
if len(fn) < prefixSize {
14+
return fn
15+
}
16+
17+
return fn[:prefixSize]
18+
}
19+
20+
func getCategory(fn string) string {
21+
c := strings.ToLower(filepath.Base(fn))[0]
22+
23+
if c >= 'a' && c <= 'z' {
24+
return string(c)
25+
}
26+
27+
return "#"
28+
}
29+
30+
// entry represents a single entry in the output tree
31+
// name is the file name for a single source
32+
// or the group name for multiple sources
33+
// sources is a list of paths to copy from
34+
type entry struct {
35+
name string
36+
sortName string
37+
sources []string
38+
}
39+
40+
var sortNameRe = regexp.MustCompile(`[^a-z0-9]+`)
41+
42+
func newEntry(name string, sources []string) entry {
43+
sortName := strings.ToLower(name)
44+
sortName = sortNameRe.ReplaceAllString(sortName, "_")
45+
46+
return entry{
47+
name: name,
48+
sortName: sortName,
49+
sources: sources,
50+
}
51+
}
52+
53+
func (e entry) prefix(size int) string {
54+
if size == 1 {
55+
return getCategory(e.sortName[:size])
56+
}
57+
58+
if size >= len(e.sortName) {
59+
return e.sortName
60+
}
61+
62+
return e.sortName[:size]
63+
}
64+
65+
func (e entry) fileMap() map[string]string {
66+
out := make(map[string]string)
67+
68+
for _, source := range e.sources {
69+
path := filepath.Base(source)
70+
71+
if len(e.sources) > 1 {
72+
path = filepath.Join(e.name, path)
73+
}
74+
75+
out[source] = path
76+
}
77+
78+
return out
79+
}

entry_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package retrosort
2+
3+
import (
4+
"testing"
5+
6+
"github.com/google/go-cmp/cmp"
7+
)
8+
9+
func TestEntryPrefixLong(t *testing.T) {
10+
e := newEntry("a file name.foo", []string{"a file name.foo"})
11+
12+
if d := cmp.Diff("a_file_name_foo", e.prefix(15)); d != "" {
13+
t.Error(d)
14+
}
15+
16+
if d := cmp.Diff("a_file_name_foo", e.prefix(16)); d != "" {
17+
t.Error(d)
18+
}
19+
}
20+
21+
func TestEntryPrefixSingle(t *testing.T) {
22+
es := []entry{
23+
newEntry("123", []string{}),
24+
newEntry(".-#", []string{}),
25+
newEntry(" _%3", []string{}),
26+
}
27+
28+
for _, e := range es {
29+
if d := cmp.Diff("#", e.prefix(1)); d != "" {
30+
t.Error(e, d)
31+
}
32+
}
33+
}
34+
35+
func TestEntryPrefixVarious(t *testing.T) {
36+
e := newEntry("my great(-)file.yes", []string{})
37+
38+
testCases := []struct {
39+
expected string
40+
size int
41+
}{
42+
{"m", 1},
43+
{"my_great", 8},
44+
{"my_great_file", 13},
45+
}
46+
47+
for _, c := range testCases {
48+
49+
if d := cmp.Diff(c.expected, e.prefix(c.size)); d != "" {
50+
t.Error(d)
51+
}
52+
}
53+
}
54+
55+
func TestEntryFileMapSingle(t *testing.T) {
56+
e := newEntry("a", []string{"/path/to/a.file"})
57+
58+
expected := map[string]string{
59+
"/path/to/a.file": "a.file",
60+
}
61+
62+
actual := e.fileMap()
63+
64+
if d := cmp.Diff(expected, actual); d != "" {
65+
t.Error(d)
66+
}
67+
}
68+
69+
func TestEntryFileMapMultiple(t *testing.T) {
70+
e := newEntry("a", []string{"/path/to/a.file", "/other/path/for/a [test].file"})
71+
72+
expected := map[string]string{
73+
"/path/to/a.file": "a/a.file",
74+
"/other/path/for/a [test].file": "a/a [test].file",
75+
}
76+
77+
actual := e.fileMap()
78+
79+
if d := cmp.Diff(expected, actual); d != "" {
80+
t.Error(d)
81+
}
82+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module github.com/stilvoid/retrosort
1+
module github.com/stilvoid/retro-sort
22

33
go 1.19
44

group.go

Lines changed: 34 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ package retrosort
22

33
import (
44
"fmt"
5-
"maps"
6-
"regexp"
75
"slices"
86
"strings"
97

@@ -13,104 +11,62 @@ import (
1311
// Sort converts a list of paths to files into a mapping from source paths
1412
// to destination paths where no directory in the destinations
1513
// contains any more than size files
16-
func Sort(files []string, size int) map[string]string {
17-
group := newGroup(files)
14+
func Sort(sources []string, size int) map[string]string {
15+
group := newGroup(sources)
1816

1917
groups := group.sort(size)
2018

2119
return groups.fileMap()
2220
}
2321

24-
func getPrefix(fn string, prefixSize int) string {
25-
fn = strings.ToLower(filepath.Base(fn))
26-
27-
if len(fn) < prefixSize {
28-
return fn
29-
}
30-
31-
return fn[:prefixSize]
32-
}
33-
34-
func getCategory(fn string) string {
35-
c := strings.ToLower(filepath.Base(fn))[0]
36-
37-
if c >= 'a' && c <= 'z' {
38-
return string(c)
39-
}
40-
41-
return "#"
42-
}
43-
44-
type file struct {
45-
name string
46-
sortName string
22+
type group struct {
23+
entries []entry
24+
prefixSize int
25+
path string
4726
}
4827

49-
var sortNameRe = regexp.MustCompile(`[^a-z0-9]+`)
50-
51-
func newFile(fn string) file {
52-
if TosecMode {
53-
fn = doTosec(fn)
54-
}
55-
56-
sortName := filepath.Base(fn)
57-
sortName = strings.ToLower(sortName)
58-
sortName = sortNameRe.ReplaceAllString(sortName, "_")
28+
func newGroup(sources []string) group {
29+
groupedSources := make(map[string][]string)
30+
for _, fn := range sources {
31+
name := filepath.Base(fn)
5932

60-
return file{
61-
name: fn,
62-
sortName: sortName,
63-
}
64-
}
33+
if TosecMode {
34+
name = tosecName(name)
35+
}
6536

66-
func (f file) prefix(size int) string {
67-
if size == 1 {
68-
return getCategory(f.sortName[:size])
69-
}
37+
if _, ok := groupedSources[name]; !ok {
38+
groupedSources[name] = make([]string, 0)
39+
}
7040

71-
if size >= len(f.sortName) {
72-
return f.sortName
41+
groupedSources[name] = append(groupedSources[name], fn)
7342
}
7443

75-
return f.sortName[:size]
76-
}
77-
78-
type group struct {
79-
files []file
80-
prefixSize int
81-
path string
82-
}
83-
84-
func newGroup(names []string) group {
85-
dedupFiles := make(map[string]file)
86-
for _, name := range names {
87-
f := newFile(name)
88-
dedupFiles[f.name] = f
44+
entries := make([]entry, 0)
45+
for name, sources := range groupedSources {
46+
entries = append(entries, newEntry(name, sources))
8947
}
9048

91-
files := slices.Collect(maps.Values(dedupFiles))
92-
93-
slices.SortStableFunc(files, func(a, b file) int {
49+
slices.SortStableFunc(entries, func(a, b entry) int {
9450
return strings.Compare(a.sortName, b.sortName)
9551
})
9652

9753
return group{
98-
files: files,
54+
entries: entries,
9955
prefixSize: 0,
10056
}
10157
}
10258

10359
func (g group) Len() int {
104-
return len(g.files)
60+
return len(g.entries)
10561
}
10662

10763
func (g group) name() string {
10864
if g.prefixSize == 0 {
10965
return ""
11066
}
11167

112-
a := g.files[0].prefix(g.prefixSize)
113-
b := g.files[g.Len()-1].prefix(g.prefixSize)
68+
a := g.entries[0].prefix(g.prefixSize)
69+
b := g.entries[g.Len()-1].prefix(g.prefixSize)
11470

11571
if g.prefixSize == 1 {
11672
a = getCategory(a)
@@ -133,8 +89,8 @@ func (g group) split(prefixSize, size int) (groups, bool) {
13389
prefixes := make([]string, 0)
13490

13591
// Fail if any individual prefix is too big
136-
for _, file := range g.files {
137-
prefix := file.prefix(prefixSize)
92+
for _, entry := range g.entries {
93+
prefix := entry.prefix(prefixSize)
13894

13995
if counts[prefix] == 0 {
14096
prefixes = append(prefixes, prefix)
@@ -157,7 +113,7 @@ func (g group) split(prefixSize, size int) (groups, bool) {
157113
for i, prefix := range prefixes {
158114
// Copy
159115
for j := 0; j < counts[prefix]; j++ {
160-
cur.files = append(cur.files, g.files[pos])
116+
cur.entries = append(cur.entries, g.entries[pos])
161117
pos++
162118
}
163119

@@ -170,7 +126,7 @@ func (g group) split(prefixSize, size int) (groups, bool) {
170126
}
171127
}
172128

173-
if len(cur.files) > 0 {
129+
if len(cur.entries) > 0 {
174130
groups = append(groups, cur)
175131
}
176132

@@ -216,8 +172,10 @@ func (g group) String() string {
216172
func (g group) fileMap() map[string]string {
217173
out := make(map[string]string)
218174

219-
for _, file := range g.files {
220-
out[file.name] = filepath.Join(g.path, g.name(), filepath.Base(file.name))
175+
for _, entry := range g.entries {
176+
for src, dst := range entry.fileMap() {
177+
out[src] = filepath.Join(g.path, g.name(), dst)
178+
}
221179
}
222180

223181
return out
@@ -230,13 +188,7 @@ func (gs groups) fileMap() map[string]string {
230188

231189
for _, g := range gs {
232190
for src, dst := range g.fileMap() {
233-
if fileNames, ok := tosecFiles[src]; TosecMode && ok {
234-
for _, fn := range fileNames {
235-
out[fn] = filepath.Join(dst, filepath.Base(fn))
236-
}
237-
} else {
238-
out[src] = dst
239-
}
191+
out[src] = dst
240192
}
241193
}
242194

0 commit comments

Comments
 (0)