diff --git a/README.md b/README.md
index e2f80be..2306565 100644
--- a/README.md
+++ b/README.md
@@ -227,6 +227,47 @@ allows for sorting data such as Go structs and JSON objects.
> Note: angle brackets (`<` and `>`) are not supported by block mode due to
> being used for mathematical expressions in an unbalanced format.
+#### Custom grouping
+
+Another way to group lines together is with the `group_prefixes` argument. This
+takes a comma-separated list of prefixes. Any line beginning with one of those
+prefixes will be treated as a continuation line.
+
+
+
+|
+
+```
+
+spaghetti
+with meatballs
+peanut butter
+and jelly
+hamburger
+with lettuce
+and tomatoes
+
+```
+
+ |
+
+
+```diff
++// keep-sorted start group_prefixes=and,with
+ hamburger
+ with lettuce
+ and tomatoes
+ peanut butter
+ and jelly
+ spaghetti
+ with meatballs
++// keep-sorted end
+```
+
+ |
+
+
+
#### Comments
Comments embedded within the sorted block are made to stick with their
diff --git a/goldens/skip_lines.in b/goldens/skip_lines.in
index 1d1bea8..947f7f5 100644
--- a/goldens/skip_lines.in
+++ b/goldens/skip_lines.in
@@ -34,3 +34,25 @@ This line should not be sorted.
Charlie | Baz
+
+Skip lines with group_prefixes:
+// keep-sorted-test start skip_lines=2 group_prefixes=:
+| birds |
+| ------------ |
+| blue footed |
+: booby |
+| crested |
+: bobwhite |
+| anhinga |
+// keep-sorted-test end
+
+Skip lines with group_prefixes indented:
+// keep-sorted-test start skip_lines=2 group_prefixes=:
+ | birds |
+ | ------------ |
+ | blue footed |
+ : booby |
+ | crested |
+ : bobwhite |
+ | anhinga |
+// keep-sorted-test end
diff --git a/goldens/skip_lines.out b/goldens/skip_lines.out
index 0ed51af..1954d72 100644
--- a/goldens/skip_lines.out
+++ b/goldens/skip_lines.out
@@ -34,3 +34,25 @@ This line should not be sorted.
Bravo | Foxtrot
+
+Skip lines with group_prefixes:
+// keep-sorted-test start skip_lines=2 group_prefixes=:
+| birds |
+| ------------ |
+| anhinga |
+| blue footed |
+: booby |
+| crested |
+: bobwhite |
+// keep-sorted-test end
+
+Skip lines with group_prefixes indented:
+// keep-sorted-test start skip_lines=2 group_prefixes=:
+ | birds |
+ | ------------ |
+ | anhinga |
+ | blue footed |
+ : booby |
+ | crested |
+ : bobwhite |
+// keep-sorted-test end
diff --git a/keepsorted/keep_sorted_test.go b/keepsorted/keep_sorted_test.go
index b46a27f..2a4aa64 100644
--- a/keepsorted/keep_sorted_test.go
+++ b/keepsorted/keep_sorted_test.go
@@ -29,6 +29,7 @@ var (
Lint: true,
SkipLines: 0,
Group: true,
+ GroupPrefixes: nil,
IgnorePrefixes: nil,
Numeric: false,
StickyComments: true,
@@ -1266,6 +1267,33 @@ func TestLineGrouping(t *testing.T) {
}},
},
},
+ {
+ name: "Group_Prefixes",
+ opts: defaultOptionsWith(func(opts *blockOptions) {
+ opts.GroupPrefixes = map[string]bool{"and": true, "with": true}
+ opts.Block = false
+ }),
+
+ want: []lineGroup{
+ {nil, []string{
+ "peanut butter",
+ "and jelly",
+ }},
+ {nil, []string{
+ "spaghetti",
+ "with meatballs",
+ }},
+ {nil, []string{
+ "hamburger",
+ " with lettuce",
+ " and tomatoes",
+ "and cheese",
+ }},
+ {nil, []string{
+ "dogs and cats",
+ }},
+ },
+ },
{
name: "Group_UnindentedNewlines",
opts: defaultOptionsWith(func(opts *blockOptions) {
@@ -1567,6 +1595,15 @@ func TestBlockOptions(t *testing.T) {
opts.IgnorePrefixes = []string{"a", "b", "c", "d"}
}),
},
+ {
+ name: "GroupPrefixesRequiresGrouping",
+ in: "// keep-sorted-test group_prefixes=a,b,c group=no",
+
+ want: defaultOptionsWith(func(opts *blockOptions) {
+ opts.Group = false
+ }),
+ wantErr: "group_prefixes may not be used with group=no",
+ },
{
name: "ignore_prefixes_ChecksLognestPrefixesFirst",
in: "// keep-sorted-test ignore_prefixes=DoSomething(,DoSomething({",
diff --git a/keepsorted/line_group.go b/keepsorted/line_group.go
index 5d56c56..ccf5844 100644
--- a/keepsorted/line_group.go
+++ b/keepsorted/line_group.go
@@ -82,6 +82,8 @@ func groupLines(lines []string, metadata blockMetadata) []lineGroup {
appendLine(i, l)
} else if metadata.opts.Group && (!lineRange.empty() && initialIndent != nil && indents[i] > *initialIndent || numUnmatchedStartDirectives > 0) {
appendLine(i, l)
+ } else if metadata.opts.Group && metadata.opts.hasGroupPrefix(l) {
+ appendLine(i, l)
} else if metadata.opts.hasStickyPrefix(l) {
if !lineRange.empty() {
finishGroup()
diff --git a/keepsorted/options.go b/keepsorted/options.go
index b5e3c97..b0ac606 100644
--- a/keepsorted/options.go
+++ b/keepsorted/options.go
@@ -56,6 +56,8 @@ type blockOptions struct {
SkipLines int `key:"skip_lines"`
// Group determines whether we group lines together based on increasing indentation.
Group bool `default:"true"`
+ // GroupPrefixes tells us about other types of lines that should be added to a group.
+ GroupPrefixes map[string]bool `key:"group_prefixes"`
// Block opts us into a more complicated algorithm to try and understand blocks of code.
Block bool `default:"false"`
// StickyComments tells us to attach comments to the line immediately below them while sorting.
@@ -114,6 +116,11 @@ func (f *Fixer) parseBlockOptions(startLine string) (blockOptions, error) {
ret.SkipLines = 0
}
+ if ret.GroupPrefixes != nil && !ret.Group {
+ errs = errors.Join(errs, fmt.Errorf("group_prefixes may not be used with group=no"))
+ ret.GroupPrefixes = nil
+ }
+
if cm := f.guessCommentMarker(startLine); cm != "" {
ret.commentMarker = cm
if ret.StickyComments {
@@ -216,10 +223,13 @@ func (f *Fixer) guessCommentMarker(startLine string) string {
return ""
}
-// hasStickyPrefix determines if s has one of the StickyPrefixes.
-func (opts blockOptions) hasStickyPrefix(s string) bool {
+// hasPrefix determines if s has one of the prefixes.
+func hasPrefix(s string, prefixes map[string]bool) bool {
+ if len(prefixes) == 0 {
+ return false
+ }
s = strings.TrimLeftFunc(s, unicode.IsSpace)
- for p := range opts.StickyPrefixes {
+ for p := range prefixes {
if strings.HasPrefix(s, p) {
return true
}
@@ -227,6 +237,16 @@ func (opts blockOptions) hasStickyPrefix(s string) bool {
return false
}
+// hasStickyPrefix determines if s has one of the StickyPrefixes.
+func (opts blockOptions) hasStickyPrefix(s string) bool {
+ return hasPrefix(s, opts.StickyPrefixes)
+}
+
+// hasGroupPrefix determines if s has one of the GroupPrefixes.
+func (opts blockOptions) hasGroupPrefix(s string) bool {
+ return hasPrefix(s, opts.GroupPrefixes)
+}
+
// removeIgnorePrefix removes the first matching IgnorePrefixes from s, if s
// matches one of the IgnorePrefixes.
func (opts blockOptions) removeIgnorePrefix(s string) (string, bool) {