-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcommand.go
More file actions
115 lines (104 loc) · 3.28 KB
/
Copy pathcommand.go
File metadata and controls
115 lines (104 loc) · 3.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package command
import (
"math/rand"
"sort"
gloo "github.com/gloo-foo/framework"
"github.com/gloo-foo/framework/patterns"
)
// defaultDelimiter is the field separator used when -t is not supplied.
var defaultDelimiter = []byte(" ")
// Sort returns a Command that sorts input lines, GNU sort style.
//
// Flags:
// - SortReverse (-r): reverse the comparison
// - SortNumeric (-n): compare by leading numeric value
// - SortHumanNumeric (-h): compare human sizes (2K < 1M)
// - SortMonthSort (-M): compare month names (Jan < Feb)
// - SortVersionSort (-V): natural/version compare (v1.2 < v1.10)
// - SortUnique (-u): drop adjacent duplicate keys
// - SortIgnoreCase (-f): fold case before comparing
// - SortIgnoreLeadingBlanks (-b): trim leading blanks before comparing
// - SortField (-k) with SortDelimiter (-t): compare a single field
// - SortStableSort (-s): preserve input order of equal keys
// - SortRandom (-R): shuffle instead of sort
func Sort(opts ...any) gloo.Command[[]byte, []byte] {
own, rest := foldOptions(opts)
gloo.NewParameters[gloo.File, struct{}](rest...)
f := normalize(own)
return patterns.Accumulate(func(lines [][]byte) ([][]byte, error) {
return arrange(lines, f), nil
})
}
// foldOptions applies every recognized sort option to a zero flags value and
// returns the leftover arguments for the framework's positional classification.
func foldOptions(opts []any) (flags, []any) {
var f flags
var rest []any
for _, o := range opts {
next, isOption := f.with(o)
if !isOption {
rest = append(rest, o)
continue
}
f = next
}
return f, rest
}
// normalize fills in defaults the comparator relies on.
func normalize(f flags) flags {
if len(f.delimiter) == 0 {
f.delimiter = defaultDelimiter
}
return f
}
// arrange shuffles, sorts, and de-duplicates lines according to f. It owns a
// fresh copy so the caller's slice is never mutated.
func arrange(lines [][]byte, f flags) [][]byte {
out := make([][]byte, len(lines))
copy(out, lines)
if bool(f.randomEnabled) {
rand.Shuffle(len(out), func(i, j int) { out[i], out[j] = out[j], out[i] })
return out
}
chooseSort(f)(out, comparator(f, out))
return dedupe(out, f)
}
// sortFunc is sort.Slice or sort.SliceStable.
type sortFunc func(slice any, less func(i, j int) bool)
// chooseSort selects a stable or unstable sort per -s.
func chooseSort(f flags) sortFunc {
if bool(f.stableSortEnabled) {
return sort.SliceStable
}
return sort.Slice
}
// comparator builds the index-level less function used by the sort, applying
// the active key transform, ordering, and -r reversal over out.
func comparator(f flags, out [][]byte) func(i, j int) bool {
less := chooseLess(f)
key := keyFunc(f)
return func(i, j int) bool {
result := less(key(out[i]), key(out[j]))
if bool(f.reverseEnabled) {
return !result
}
return result
}
}
// dedupe drops repeated keys when -u is set. Lines are already ordered, so
// equal keys are adjacent and a single pass suffices.
func dedupe(sorted [][]byte, f flags) [][]byte {
if !bool(f.uniqueEnabled) {
return sorted
}
out := make([][]byte, 0, len(sorted))
seen := make(map[string]struct{}, len(sorted))
for _, line := range sorted {
k := string(keyFunc(f)(line))
if _, dup := seen[k]; !dup {
seen[k] = struct{}{}
out = append(out, line)
}
}
return out
}