Skip to content

Commit

Permalink
refactor: Move comparator functionality to a new file and introduce a…
Browse files Browse the repository at this point in the history
… CmpFunc type to make things a little more fluent. (#58)
  • Loading branch information
JeffFaer authored Jan 7, 2025
1 parent 65bfea4 commit 50eef69
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 52 deletions.
66 changes: 19 additions & 47 deletions keepsorted/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
package keepsorted

import (
"cmp"
"slices"
"strings"

Expand Down Expand Up @@ -367,31 +366,34 @@ func allHaveSuffix(lgs []lineGroup, s string) bool {
return true
}

func (b block) lessFn() func(a, b lineGroup) int {
func (b block) lessFn() cmpFunc[lineGroup] {
// Always put groups that are only comments last.
commentOnlyBlock := comparingProperty(func(lg lineGroup) int {
commentOnlyBlock := comparing(func(lg lineGroup) int {
if len(lg.lines) > 0 {
return 0
}
return 1
})

// Check preferred prefixes from longest to shortest. The list of prefixes
// is reversed to assign weights in ascending order: they are multiplied by
// -1 to ensure that entries with matching prefixes are put before any
// non-matching lines (which assume the weight of 0).
// Assign a weight to each prefix so that they will be sorted into their
// predetermined order.
// Weights are negative so that entries with matching prefixes are put before
// any non-matching line (which will have a weight of 0).
//
// An empty prefix can be used to move all remaining entries to a position
// An empty prefix can be used to move "non-matching" entries to a position
// between other prefixes.
type prefixWeight struct {
prefix string
weight int
}
var prefixWeights []prefixWeight
for i, p := range b.metadata.opts.PrefixOrder {
prefixWeights = append(prefixWeights, prefixWeight{p, i - len(b.metadata.opts.PrefixOrder)})
}
slices.SortStableFunc(prefixWeights, func(a, b prefixWeight) int {
return cmp.Compare(b.prefix, a.prefix)
})
// Sort prefixes longest -> shortest to find the most appropriate weight.
slices.SortStableFunc(prefixWeights, comparing(func(pw prefixWeight) int { return len(pw.prefix) }).reversed())

prefixOrder := comparingProperty(func(lg lineGroup) int {
prefixOrder := comparing(func(lg lineGroup) int {
for _, w := range prefixWeights {
if lg.hasPrefix(w.prefix) {
return w.weight
Expand All @@ -418,7 +420,7 @@ func (b block) lessFn() func(a, b lineGroup) int {
// foo_6
// Foo_45
// foo_123
transformOrder := comparingPropertyWith(func(lg lineGroup) numericTokens {
transformOrder := comparingFunc(func(lg lineGroup) numericTokens {
l := lg.joinedLines()
if s, ok := b.metadata.opts.removeIgnorePrefix(l); ok {
l = s
Expand All @@ -429,38 +431,8 @@ func (b block) lessFn() func(a, b lineGroup) int {
return b.metadata.opts.maybeParseNumeric(l)
}, numericTokens.compare)

return func(a, b lineGroup) int {
for _, cmp := range []func(a, b lineGroup) int{
commentOnlyBlock,
prefixOrder,
transformOrder,
} {
if c := cmp(a, b); c != 0 {
return c
}
}
return a.less(b)
}
}

func comparingProperty[T any, E cmp.Ordered](f func(T) E) func(a, b T) int {
return comparingPropertyWith(f, func(a, b E) int {
if a < b {
return -1
} else if a > b {
return 1
}
return 0
})
}

func comparingPropertyWith[T any, R any](f func(T) R, cmp func(R, R) int) func(a, b T) int {
return func(a, b T) int {
return cmp(f(a), f(b))
}
}

type prefixWeight struct {
prefix string
weight int
return commentOnlyBlock.
andThen(prefixOrder).
andThen(transformOrder).
andThen(lineGroup.less)
}
41 changes: 41 additions & 0 deletions keepsorted/cmp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package keepsorted

import (
"cmp"
)

type cmpFunc[T any] func(T, T) int

// andThen returns a cmpFunc based on the current one that first checks the
// current cmpFunc and only checks the next cmpFunc if the current cmpFunc
// thinks the two elements are equal.
func (f cmpFunc[T]) andThen(next cmpFunc[T]) cmpFunc[T] {
return func(a, b T) int {
if c := f(a, b); c != 0 {
return c
}
return next(a, b)
}
}

// reverse returns a cmpFunc based on the current one that yields the opposite
// order.
func (f cmpFunc[T]) reversed() cmpFunc[T] {
return func(a, b T) int {
return f(b, a)
}
}

// comparing creates a cmpFunc that orders T based on one of its properties, R.
func comparing[T any, R cmp.Ordered](f func(T) R) cmpFunc[T] {
return comparingFunc(f, cmp.Compare)
}

// comparingFunc creates a cmpFunc that orders T based on one of its properties,
// R and R has its own explicit ordering.
func comparingFunc[T, R any](f func(T) R, cmp cmpFunc[R]) cmpFunc[T] {
return func(a, b T) int {
r1, r2 := f(a), f(b)
return cmp(r1, r2)
}
}
6 changes: 1 addition & 5 deletions keepsorted/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,11 +387,7 @@ func (t numericTokens) len() int {
}

func (t numericTokens) compare(o numericTokens) int {
l := t.len()
if k := o.len(); k < l {
l = k
}
for i := 0; i < l; i++ {
for i := 0; i < min(t.len(), o.len()); i++ {
if i%2 == 0 { // Start by comparing strings.
if c := strings.Compare(t.s[i/2], o.s[i/2]); c != 0 {
return c
Expand Down

0 comments on commit 50eef69

Please sign in to comment.