@@ -40,6 +40,16 @@ type Config struct {
4040 // Sort adjacent scalar fields of the same field name by their contents.
4141 SortRepeatedFieldsByContent bool
4242
43+ // Map from Node.Name to the order of all fields within that node. See AddFieldSortOrder().
44+ fieldSortOrder map [string ]map [string ]int
45+
46+ // RequireFieldSortOrderToMatchAllFieldsInNode will cause parsing to fail if a node was added via
47+ // AddFieldSortOrder() but 1+ fields under that node in the textproto aren't specified in the
48+ // field order. This won't fail for nodes that don't have a field order specified at all. Use this
49+ // to strictly enforce that your field order config always orders ALL the fields, and you're
50+ // willing for new fields in the textproto to break parsing in order to enforce it.
51+ RequireFieldSortOrderToMatchAllFieldsInNode bool
52+
4353 // Remove lines that have the same field name and scalar value as another.
4454 RemoveDuplicateValuesForRepeatedFields bool
4555
@@ -62,6 +72,45 @@ type Config struct {
6272 SmartQuotes bool
6373}
6474
75+ // AddFieldSortOrder adds a config rule for the given Node.Name, so that all contained field names
76+ // are output in the provided order.
77+ func (c * Config ) AddFieldSortOrder (nodeName string , fieldOrder ... string ) {
78+ if c .fieldSortOrder == nil {
79+ c .fieldSortOrder = make (map [string ]map [string ]int )
80+ }
81+ c .fieldSortOrder [nodeName ] = makePriorities (fieldOrder ... )
82+ }
83+
84+ // UnsortedFieldsError will be returned by ParseWithConfig if
85+ // Config.RequireFieldSortOrderToMatchAllFieldsInNode is set, and an unrecognized field is found
86+ // while parsing.
87+ type UnsortedFieldsError struct {
88+ UnsortedFields []UnsortedField
89+ }
90+
91+ // UnsortedField records details about a single unsorted field.
92+ type UnsortedField struct {
93+ Line int32
94+ ParentFieldName string
95+ FieldName string
96+ }
97+
98+ func (e * UnsortedFieldsError ) Error () string {
99+ var errs []string
100+ for _ , us := range e .UnsortedFields {
101+ errs = append (errs , fmt .Sprintf (" line: %d, parent field: %q, unsorted field: %q" , us .Line , us .ParentFieldName , us .FieldName ))
102+ }
103+ return fmt .Sprintf ("fields parsed that were not specified in the parser.AddFieldSortOrder() call:\n %s" , strings .Join (errs , "\n " ))
104+ }
105+
106+ func makePriorities (fieldOrder ... string ) map [string ]int {
107+ priorities := make (map [string ]int )
108+ for i , fieldName := range fieldOrder {
109+ priorities [fieldName ] = i + 1
110+ }
111+ return priorities
112+ }
113+
65114type parser struct {
66115 in []byte
67116 index int
@@ -309,7 +358,7 @@ func parseWithConfig(in []byte, c Config, metaComments map[string]bool) ([]*ast.
309358 return nil , fmt .Errorf ("parser didn't consume all input. Stopped at %s" , p .errorContext ())
310359 }
311360 wrapStrings (nodes , 0 , c )
312- sortAndFilterNodes (nodes , nodeSortFunction ( c . SortFieldsByFieldName , c . SortRepeatedFieldsByContent ), c . RemoveDuplicateValuesForRepeatedFields )
361+ err = sortAndFilterNodes ("" , nodes , c )
313362 return nodes , err
314363}
315364
@@ -914,15 +963,15 @@ func (p *parser) readTemplate() string {
914963 return p .advance (i )
915964}
916965
917- func sortAndFilterNodes (nodes [] * ast. Node , sortFunction func ( []* ast.Node ), removeDuplicates bool ) {
966+ func sortAndFilterNodes (parentNodeName string , nodes []* ast.Node , c Config ) error {
918967 if len (nodes ) == 0 {
919- return
968+ return nil
920969 }
921970 type nameAndValue struct {
922971 name , value string
923972 }
924973 var seen map [nameAndValue ]bool
925- if removeDuplicates {
974+ if c . RemoveDuplicateValuesForRepeatedFields {
926975 seen = make (map [nameAndValue ]bool )
927976 }
928977 for _ , nd := range nodes {
@@ -935,11 +984,23 @@ func sortAndFilterNodes(nodes []*ast.Node, sortFunction func([]*ast.Node), remov
935984 seen [key ] = true
936985 }
937986 }
938- sortAndFilterNodes (nd .Children , sortFunction , removeDuplicates )
987+ err := sortAndFilterNodes (nd .Name , nd .Children , c )
988+ if err != nil {
989+ return err
990+ }
939991 }
940- if sortFunction != nil {
941- sortFunction (nodes )
992+
993+ s := sortable {
994+ config : c ,
995+ nodes : nodes ,
996+ }
997+ sortableByNameAndValue {sortable : s }.Sort ()
998+ fieldOrderSorter := sortableByFieldOrder {sortable : s , parentNodeName : parentNodeName }
999+ fieldOrderSorter .Sort ()
1000+ if c .RequireFieldSortOrderToMatchAllFieldsInNode && len (fieldOrderSorter .unsortedFields ) > 0 {
1001+ return & UnsortedFieldsError {fieldOrderSorter .unsortedFields }
9421002 }
1003+ return nil
9431004}
9441005
9451006func wrapStrings (nodes []* ast.Node , depth int , c Config ) {
@@ -1125,17 +1186,87 @@ func out(nodes []*ast.Node) []byte {
11251186 return result .Bytes ()
11261187}
11271188
1128- func nodeSortFunction (sortByFieldName , sortByFieldValue bool ) func ([]* ast.Node ) {
1129- switch {
1130- case sortByFieldName && sortByFieldValue :
1131- return func (ns []* ast.Node ) { sort .Stable (ast .ByFieldNameAndValue (ns )) }
1132- case sortByFieldName :
1133- return func (ns []* ast.Node ) { sort .Stable (ast .ByFieldName (ns )) }
1134- case sortByFieldValue :
1135- return func (ns []* ast.Node ) { sort .Stable (ast .ByFieldValue (ns )) }
1136- default :
1137- return nil
1189+ type sortable struct {
1190+ config Config
1191+ nodes []* ast.Node
1192+ }
1193+
1194+ func (s sortable ) Len () int {
1195+ return len (s .nodes )
1196+ }
1197+
1198+ func (s sortable ) Swap (i , j int ) {
1199+ s .nodes [i ], s .nodes [j ] = s .nodes [j ], s .nodes [i ]
1200+ }
1201+
1202+ type sortableByNameAndValue struct {
1203+ sortable
1204+ }
1205+
1206+ func (s sortableByNameAndValue ) Sort () {
1207+ if s .config .SortFieldsByFieldName || s .config .SortRepeatedFieldsByContent {
1208+ sort .Stable (s )
1209+ }
1210+ }
1211+
1212+ func (s sortableByNameAndValue ) Less (i , j int ) bool {
1213+ ni , nj := s .nodes [i ], s .nodes [j ]
1214+ if s .config .SortFieldsByFieldName {
1215+ if ni .Name != nj .Name {
1216+ return ni .Name < nj .Name
1217+ }
1218+ }
1219+ if s .config .SortRepeatedFieldsByContent {
1220+ if ni .Name != nj .Name || len (ni .Values ) != 1 || len (nj .Values ) != 1 {
1221+ return false
1222+ }
1223+ return ni .Values [0 ].Value < nj .Values [0 ].Value
1224+ }
1225+ return false
1226+ }
1227+
1228+ type sortableByFieldOrder struct {
1229+ sortable
1230+ parentNodeName string
1231+
1232+ // If a Config.fieldOrder exists for a parent field, but fields in the textproto are found that
1233+ // are not in the config, the unsorted field info will be added to this slice.
1234+ unsortedFields []UnsortedField
1235+ }
1236+
1237+ func (s * sortableByFieldOrder ) Sort () {
1238+ // Only sort if we have field orders for this parent node.
1239+ if s .config .fieldSortOrder [s .parentNodeName ] != nil {
1240+ sort .Stable (s )
1241+ }
1242+ }
1243+
1244+ func (s * sortableByFieldOrder ) Less (i , j int ) bool {
1245+ order := s .config .fieldSortOrder [s .parentNodeName ]
1246+ if order == nil {
1247+ return false
1248+ }
1249+
1250+ // CommentOnly nodes don't set priority below, and default to 9999, which keeps them at the bottom
1251+ lprio := 9999
1252+ rprio := 9999
1253+
1254+ // Unknown fields will get the int nil value of 0 from the order map, and bubble to the top.
1255+ if ! s .nodes [i ].IsCommentOnly () {
1256+ node := s .nodes [i ]
1257+ lprio = order [node .Name ]
1258+ if lprio == 0 {
1259+ s .unsortedFields = append (s .unsortedFields , UnsortedField {node .Start .Line , s .parentNodeName , node .Name })
1260+ }
1261+ }
1262+ if ! s .nodes [j ].IsCommentOnly () {
1263+ node := s .nodes [j ]
1264+ rprio = order [node .Name ]
1265+ if rprio == 0 {
1266+ s .unsortedFields = append (s .unsortedFields , UnsortedField {node .Start .Line , s .parentNodeName , node .Name })
1267+ }
11381268 }
1269+ return lprio < rprio
11391270}
11401271
11411272// stringWriter abstracts over bytes.Buffer and strings.Builder
0 commit comments