Skip to content

Commit cf07efc

Browse files
Support sorting protos in DNS order.
PiperOrigin-RevId: 802614039
1 parent f293424 commit cf07efc

9 files changed

Lines changed: 136 additions & 19 deletions

ast/ast.go

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -183,21 +183,24 @@ func getFieldValueForByFieldValue(n *Node) *Value {
183183
return n.Values[0]
184184
}
185185

186-
// ByFieldValue is a NodeLess function that orders adjacent scalar nodes with the same name by
187-
// their scalar value.
188-
func ByFieldValue(_, ni, nj *Node, isWholeSlice bool) bool {
189-
if isWholeSlice {
190-
return false
191-
}
192-
vi := getFieldValueForByFieldValue(ni)
193-
vj := getFieldValueForByFieldValue(nj)
194-
if vi == nil {
195-
return vj != nil
196-
}
197-
if vj == nil {
198-
return false
186+
// ByFieldValue returns a NodeLess function that orders adjacent scalar nodes
187+
// with the same name by their scalar value. The values are passed through
188+
// `projection` before sorting.
189+
func ByFieldValue(projection func(string) string) NodeLess {
190+
return func(_, ni, nj *Node, isWholeSlice bool) bool {
191+
if isWholeSlice {
192+
return false
193+
}
194+
vi := getFieldValueForByFieldValue(ni)
195+
vj := getFieldValueForByFieldValue(nj)
196+
if vi == nil {
197+
return vj != nil
198+
}
199+
if vj == nil {
200+
return false
201+
}
202+
return projection(vi.Value) < projection(vj.Value)
199203
}
200-
return vi.Value < vj.Value
201204
}
202205

203206
func getChildValueByFieldSubfield(field, subfield string, n *Node) *Value {
@@ -245,8 +248,9 @@ func ByFieldSubfield(field, subfield string) NodeLess {
245248

246249
// ByFieldSubfieldPath returns a NodeLess function that orders adjacent message nodes with the given
247250
// field name by the given subfield path value. If no field name is provided, it compares the
248-
// subfields of any adjacent nodes with matching names.
249-
func ByFieldSubfieldPath(field string, subfieldPath []string) NodeLess {
251+
// subfields of any adjacent nodes with matching names. Values are passed
252+
// through `projection` before sorting.
253+
func ByFieldSubfieldPath(field string, subfieldPath []string, projection func(string) string) NodeLess {
250254
return func(_, ni, nj *Node, isWholeSlice bool) bool {
251255
if isWholeSlice {
252256
return false
@@ -259,7 +263,7 @@ func ByFieldSubfieldPath(field string, subfieldPath []string) NodeLess {
259263
if vj == nil {
260264
return false
261265
}
262-
return vi.Value < vj.Value
266+
return projection(vi.Value) < projection(vj.Value)
263267
}
264268
}
265269

config/config.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ type Config struct {
3535
// Sort the Sort* fields by descending order instead of ascending order.
3636
ReverseSort bool
3737

38+
// Sort content fields in a way that's suitable for DNS names. It splits the
39+
// value around '.' characters, reverses the substrings, and concatenates to
40+
// generate the sort key.
41+
DNSSortOrder bool
42+
3843
// Map from Node.Name to the order of all fields within that node. See AddFieldSortOrder().
3944
FieldSortOrder map[string][]string
4045

docs/config.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,22 @@ order. Does nothing if not used with at least 1 other `sort_*` field.
153153

154154
[Example](examples/reverse_sort.OUT.textproto)
155155

156+
## DNSSortOrder
157+
`# txtpbfmt: dns_sort_order`
158+
159+
When sorting field contents, sorts in a way intended for DNS names. It splits
160+
the contents on '.' characters, reverses the substrings, and sorts on their
161+
concatenation. This puts, e.g., `a.com` and `c.com` next to each other even if
162+
`b.au` is in the list.
163+
164+
### Before formatting
165+
166+
[Example](examples/sort_repeated_fields_by_subfield_in_dns_order.IN.textproto)
167+
168+
### After formatting
169+
170+
[Example](examples/sort_repeated_fields_by_subfield_in_dns_order.OUT.textproto)
171+
156172
## WrapHTMLStrings
157173
`# txtpbfmt: wrap_html_strings`
158174

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# txtpbfmt: sort_repeated_fields_by_subfield=hostinfo.name
2+
# txtpbfmt: dns_sort_order
3+
head {
4+
hostinfo {
5+
name: "a.com"
6+
}
7+
hostinfo {
8+
name: "b.au"
9+
}
10+
hostinfo {
11+
name: "c.com"
12+
}
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# txtpbfmt: sort_repeated_fields_by_subfield=hostinfo.name
2+
# txtpbfmt: dns_sort_order
3+
head {
4+
hostinfo {
5+
name: "b.au"
6+
}
7+
hostinfo {
8+
name: "a.com"
9+
}
10+
hostinfo {
11+
name: "c.com"
12+
}
13+
}

impl/impl.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,8 @@ func addToConfig(metaComment string, c *config.Config) error {
209209
c.SortRepeatedFieldsBySubfield = append(c.SortRepeatedFieldsBySubfield, val)
210210
case "reverse_sort":
211211
c.ReverseSort = true
212+
case "dns_sort_order":
213+
c.DNSSortOrder = true
212214
case "wrap_strings_at_column":
213215
// If multiple of this MetaComment exists in the file, take the last one.
214216
if !hasEqualSign {

parser/parser_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2147,6 +2147,48 @@ func TestParserConfigs(t *testing.T) {
21472147
}
21482148
auto_reviewers: "reviewerA"
21492149
}
2150+
`,
2151+
}, {
2152+
name: "SortRepeatedFieldsByContentInDNSOrder",
2153+
in: `head {
2154+
host: "a.com"
2155+
host: "b.au"
2156+
host: "c.com"
2157+
}
2158+
`,
2159+
config: config.Config{SortRepeatedFieldsByContent: true, DNSSortOrder: true},
2160+
out: `head {
2161+
host: "b.au"
2162+
host: "a.com"
2163+
host: "c.com"
2164+
}
2165+
`,
2166+
}, {
2167+
name: "SortRepeatedFieldsBySubfieldInDNSOrder",
2168+
in: `head {
2169+
hostinfo {
2170+
name: "a.com"
2171+
}
2172+
hostinfo {
2173+
name: "b.au"
2174+
}
2175+
hostinfo {
2176+
name: "c.com"
2177+
}
2178+
}
2179+
`,
2180+
config: config.Config{SortRepeatedFieldsBySubfield: []string{"hostinfo.name"}, DNSSortOrder: true},
2181+
out: `head {
2182+
hostinfo {
2183+
name: "b.au"
2184+
}
2185+
hostinfo {
2186+
name: "a.com"
2187+
}
2188+
hostinfo {
2189+
name: "c.com"
2190+
}
2191+
}
21502192
`,
21512193
}, {
21522194
name: "SortFieldNamesAndContents",

sort/sort.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,19 @@ func (e *UnsortedFieldsError) Error() string {
3232
return fmt.Sprintf("fields parsed that were not specified in the parser.AddFieldSortOrder() call:\n%s", strings.Join(errs, "\n"))
3333
}
3434

35+
func identityProjection(s string) string {
36+
return s
37+
}
38+
39+
func dnsProjection(s string) string {
40+
parts := strings.Split(s, ".")
41+
// Reverse `parts`.
42+
for i, j := 0, len(parts)-1; i < j; i, j = i+1, j-1 {
43+
parts[i], parts[j] = parts[j], parts[i]
44+
}
45+
return strings.Join(parts, ".")
46+
}
47+
3548
// nodeSortFunction sorts the given nodes, using the parent node as context. parent can be nil.
3649
type nodeSortFunction func(parent *ast.Node, nodes []*ast.Node) error
3750

@@ -130,13 +143,18 @@ func nodeSortFunctionConfig(c config.Config) nodeSortFunction {
130143
if c.SortFieldsByFieldName {
131144
sorter = ast.ChainNodeLess(sorter, ast.ByFieldName)
132145
}
146+
projection := identityProjection
147+
if c.DNSSortOrder {
148+
projection = dnsProjection
149+
}
133150
if c.SortRepeatedFieldsByContent {
134-
sorter = ast.ChainNodeLess(sorter, ast.ByFieldValue)
151+
sorter = ast.ChainNodeLess(sorter, ast.ByFieldValue(projection))
135152
}
136153
for _, sf := range c.SortRepeatedFieldsBySubfield {
137154
field, subfieldPath := parseSubfieldSpec(sf)
138155
if len(subfieldPath) > 0 {
139-
sorter = ast.ChainNodeLess(sorter, ast.ByFieldSubfieldPath(field, subfieldPath))
156+
sorter = ast.ChainNodeLess(sorter, ast.ByFieldSubfieldPath(field, subfieldPath,
157+
projection))
140158
}
141159
}
142160
if sorter != nil {

sort/sort_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,10 @@ func TestNodeSortFunction(t *testing.T) {
317317
name: "SortRepeatedFieldsBySubfield",
318318
c: config.Config{SortRepeatedFieldsBySubfield: []string{"field.subfield"}},
319319
want: true,
320+
}, {
321+
name: "SortRepeatedFieldsBySubfieldInDNSOrder",
322+
c: config.Config{SortRepeatedFieldsBySubfield: []string{"field.subfield"}, DNSSortOrder: true},
323+
want: true,
320324
}, {
321325
name: "FieldSortOrder",
322326
c: config.Config{

0 commit comments

Comments
 (0)