-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
179 lines (155 loc) · 4 KB
/
main.go
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
package main
import (
"bufio"
"flag"
"go/token"
"io"
"log"
"os"
"sort"
"strings"
"github.com/dave/dst"
"github.com/dave/dst/decorator"
)
type config struct {
// Filename defines the name of the file to be parsed.
Filename string
// OutputFilename defines the location of the output file.
OutputFilename string
// WriteToFile, if true, writes the formatted output to OutputFilename.
WriteToFile bool
file *dst.File
preferredFields map[string]int
}
// Parse validates the configuration and creates the parsed *dst.File.
func (c *config) Parse(ff string) error {
file, err := os.OpenFile(c.Filename, os.O_RDWR, 0755)
defer checkClose(file)
if err != nil {
return err
}
preferred := make(map[string]int)
for i, fieldName := range strings.Split(ff, ",") {
if fieldName != "" {
preferred[fieldName] = i + 1
}
}
c.preferredFields = preferred
if c.OutputFilename == "" {
c.OutputFilename = c.Filename
}
fset := token.NewFileSet()
f, err := decorator.ParseFile(fset, "", bufio.NewReader(file), 0)
if err != nil {
return err
}
c.file = f
return nil
}
// Rewrite walks the AST and rewrite structs and interfaces with sorted
// field order.
func (c *config) Rewrite() error {
dst.Inspect(c.file, func(n dst.Node) bool {
switch x := n.(type) {
case *dst.InterfaceType:
sort.Sort(byFieldName{
Fields: x.Methods.List,
PreferredFields: c.preferredFields,
})
setFieldDecorators(x.Methods.List)
case *dst.StructType:
sort.Sort(byFieldName{
Fields: x.Fields.List,
PreferredFields: c.preferredFields,
})
setFieldDecorators(x.Fields.List)
}
return true
})
return nil
}
// Write outputs the file to the configured writer.
func (c *config) Write() error {
var writer io.Writer
if c.WriteToFile {
file, err := os.OpenFile(c.OutputFilename, os.O_RDWR|os.O_CREATE, 0755)
if err != nil {
return err
}
writer = file
} else {
writer = os.Stdout
}
w := bufio.NewWriter(writer)
defer w.Flush()
return decorator.Fprint(w, c.file)
}
func main() {
flagFilename := flag.String("file", "", "Filename to be parsed")
flagOutputFilename := flag.String("o", "", "Output filename")
flagPreferredFields := flag.String("p", "", "Comma-separated ordered list of field names to prioritize")
flagWrite := flag.Bool("w", false, "Write to -file")
flag.Parse()
cfg := &config{
Filename: *flagFilename,
OutputFilename: *flagOutputFilename,
WriteToFile: *flagWrite,
}
must(cfg.Parse(*flagPreferredFields))
must(cfg.Rewrite())
must(cfg.Write())
}
// byFieldName implements sort.Interface for []*dst.Field based on
// the field's name.
type byFieldName struct {
Fields []*dst.Field
PreferredFields map[string]int
}
func (a byFieldName) Len() int { return len(a.Fields) }
func (a byFieldName) Swap(i, j int) { a.Fields[i], a.Fields[j] = a.Fields[j], a.Fields[i] }
func (a byFieldName) Less(i, j int) bool {
x := a.Fields[i].Names[0].Name
y := a.Fields[j].Names[0].Name
x1, ok1 := a.PreferredFields[x]
y1, ok2 := a.PreferredFields[y]
// If both fields are preferred, compare their values.
if ok1 && ok2 {
return x1 < y1
} else if ok1 && !ok2 {
// If x is preferred, consider it first.
return true
} else if !ok1 && ok2 {
// Inverse of above.
return false
}
// If all else fails, compare string values.
return x < y
}
// setFieldDecorators manages the commentary around fields.
func setFieldDecorators(fields []*dst.Field) {
for i, field := range fields {
// If the field has a leading comment, add newlines as
// appropriate excl. the first field.
if len(field.Decs.Start.All()) > 0 {
if i != 0 {
field.Decs.Before = dst.EmptyLine
} else {
field.Decs.Before = dst.NewLine
}
field.Decs.After = dst.EmptyLine
} else {
field.Decs.Before = dst.NewLine
field.Decs.After = dst.NewLine
}
}
}
// must exits the process if err is not nil.
func must(err error) {
if err != nil {
log.Fatal(err)
}
}
// checkClose closes the io.Closer.
func checkClose(closer io.Closer) {
must(closer.Close())
}