blob: f4216ddb84c4509477779a6996df024fac309de7 [file] [log] [blame]
Colin Cross41c397a2015-01-12 17:43:04 -08001// Mostly copied from Go's src/cmd/gofmt:
2// Copyright 2009 The Go Authors. All rights reserved.
3// Use of this source code is governed by a BSD-style
4// license that can be found in the LICENSE file.
5
6package main
7
8import (
Colin Cross41c397a2015-01-12 17:43:04 -08009 "bytes"
10 "flag"
11 "fmt"
Jamie Gennis6cafc2c2015-03-20 22:39:29 -040012 "github.com/google/blueprint/parser"
Colin Cross41c397a2015-01-12 17:43:04 -080013 "io"
14 "io/ioutil"
15 "os"
16 "os/exec"
17 "path/filepath"
18 "strings"
19 "unicode"
20)
21
22var (
23 // main operation modes
24 list = flag.Bool("l", false, "list files that would be modified by bpmodify")
25 write = flag.Bool("w", false, "write result to (source) file instead of stdout")
26 doDiff = flag.Bool("d", false, "display diffs instead of rewriting files")
27 sortLists = flag.Bool("s", false, "sort touched lists, even if they were unsorted")
28 parameter = flag.String("parameter", "deps", "name of parameter to modify on each module")
29 targetedModules = new(identSet)
30 addIdents = new(identSet)
31 removeIdents = new(identSet)
32)
33
34func init() {
35 flag.Var(targetedModules, "m", "comma or whitespace separated list of modules on which to operate")
36 flag.Var(addIdents, "a", "comma or whitespace separated list of identifiers to add")
37 flag.Var(removeIdents, "r", "comma or whitespace separated list of identifiers to remove")
38}
39
40var (
41 exitCode = 0
42)
43
44func report(err error) {
45 fmt.Fprintln(os.Stderr, err)
46 exitCode = 2
47}
48
49func usage() {
50 fmt.Fprintf(os.Stderr, "usage: bpmodify [flags] [path ...]\n")
51 flag.PrintDefaults()
52 os.Exit(2)
53}
54
55// If in == nil, the source is the contents of the file with the given filename.
56func processFile(filename string, in io.Reader, out io.Writer) error {
57 if in == nil {
58 f, err := os.Open(filename)
59 if err != nil {
60 return err
61 }
62 defer f.Close()
63 in = f
64 }
65
66 src, err := ioutil.ReadAll(in)
67 if err != nil {
68 return err
69 }
70
71 r := bytes.NewBuffer(src)
72
73 file, errs := parser.Parse(filename, r, parser.NewScope(nil))
74 if len(errs) > 0 {
75 for _, err := range errs {
76 fmt.Fprintln(os.Stderr, err)
77 }
78 return fmt.Errorf("%d parsing errors", len(errs))
79 }
80
81 modified, errs := findModules(file)
82 if len(errs) > 0 {
83 for _, err := range errs {
84 fmt.Fprintln(os.Stderr, err)
85 }
86 fmt.Fprintln(os.Stderr, "continuing...")
87 }
88
89 if modified {
90 res, err := parser.Print(file)
91 if err != nil {
92 return err
93 }
94
95 if *list {
96 fmt.Fprintln(out, filename)
97 }
98 if *write {
99 err = ioutil.WriteFile(filename, res, 0644)
100 if err != nil {
101 return err
102 }
103 }
104 if *doDiff {
105 data, err := diff(src, res)
106 if err != nil {
107 return fmt.Errorf("computing diff: %s", err)
108 }
109 fmt.Printf("diff %s bpfmt/%s\n", filename, filename)
110 out.Write(data)
111 }
112
113 if !*list && !*write && !*doDiff {
114 _, err = out.Write(res)
115 }
116 }
117
118 return err
119}
120
121func findModules(file *parser.File) (modified bool, errs []error) {
122
123 for _, def := range file.Defs {
124 if module, ok := def.(*parser.Module); ok {
125 for _, prop := range module.Properties {
126 if prop.Name.Name == "name" && prop.Value.Type == parser.String {
127 if targetedModule(prop.Value.StringValue) {
128 m, newErrs := processModule(module, prop.Name.Name, file)
129 errs = append(errs, newErrs...)
130 modified = modified || m
131 }
132 }
133 }
134 }
135 }
136
137 return modified, errs
138}
139
140func processModule(module *parser.Module, moduleName string,
141 file *parser.File) (modified bool, errs []error) {
142
143 for _, prop := range module.Properties {
144 if prop.Name.Name == *parameter {
145 modified, errs = processParameter(&prop.Value, *parameter, moduleName, file)
146 return
147 }
148 }
149
150 return false, nil
151}
152
153func processParameter(value *parser.Value, paramName, moduleName string,
154 file *parser.File) (modified bool, errs []error) {
155 if value.Type != parser.List {
156 return false, []error{fmt.Errorf("expected parameter %s in module %s to be list, found %s",
157 paramName, moduleName, value.Type.String())}
158 }
159
160 if value.Variable != "" {
161 return false, []error{fmt.Errorf("parameter %s in module %s is a variable, unsupported",
162 paramName, moduleName)}
163 }
164
165 if value.Expression != nil {
166 return false, []error{fmt.Errorf("parameter %s in module %s is an expression, unsupported",
167 paramName, moduleName)}
168 }
169
170 wasSorted := parser.ListIsSorted(*value)
171
172 for _, a := range addIdents.idents {
173 m := parser.AddStringToList(value, a)
174 modified = modified || m
175 }
176
177 for _, r := range removeIdents.idents {
178 m := parser.RemoveStringFromList(value, r)
179 modified = modified || m
180 }
181
182 if (wasSorted || *sortLists) && modified {
183 parser.SortList(file, *value)
184 }
185
186 return modified, nil
187}
188
189func targetedModule(name string) bool {
190 if targetedModules.all {
191 return true
192 }
193 for _, m := range targetedModules.idents {
194 if m == name {
195 return true
196 }
197 }
198
199 return false
200}
201
202func visitFile(path string, f os.FileInfo, err error) error {
203 if err == nil && f.Name() == "Blueprints" {
204 err = processFile(path, nil, os.Stdout)
205 }
206 if err != nil {
207 report(err)
208 }
209 return nil
210}
211
212func walkDir(path string) {
213 filepath.Walk(path, visitFile)
214}
215
216func main() {
217 flag.Parse()
218
219 if flag.NArg() == 0 {
220 if *write {
221 fmt.Fprintln(os.Stderr, "error: cannot use -w with standard input")
222 exitCode = 2
223 return
224 }
225 if err := processFile("<standard input>", os.Stdin, os.Stdout); err != nil {
226 report(err)
227 }
228 return
229 }
230
231 if len(targetedModules.idents) == 0 {
232 report(fmt.Errorf("-m parameter is required"))
233 return
234 }
235
236 if len(addIdents.idents) == 0 && len(removeIdents.idents) == 0 {
237 report(fmt.Errorf("-a or -r parameter is required"))
238 return
239 }
240
241 for i := 0; i < flag.NArg(); i++ {
242 path := flag.Arg(i)
243 switch dir, err := os.Stat(path); {
244 case err != nil:
245 report(err)
246 case dir.IsDir():
247 walkDir(path)
248 default:
249 if err := processFile(path, nil, os.Stdout); err != nil {
250 report(err)
251 }
252 }
253 }
254}
255
256func diff(b1, b2 []byte) (data []byte, err error) {
257 f1, err := ioutil.TempFile("", "bpfmt")
258 if err != nil {
259 return
260 }
261 defer os.Remove(f1.Name())
262 defer f1.Close()
263
264 f2, err := ioutil.TempFile("", "bpfmt")
265 if err != nil {
266 return
267 }
268 defer os.Remove(f2.Name())
269 defer f2.Close()
270
271 f1.Write(b1)
272 f2.Write(b2)
273
274 data, err = exec.Command("diff", "-uw", f1.Name(), f2.Name()).CombinedOutput()
275 if len(data) > 0 {
276 // diff exits with a non-zero status when the files don't match.
277 // Ignore that failure as long as we get output.
278 err = nil
279 }
280 return
281
282}
283
284type identSet struct {
285 idents []string
286 all bool
287}
288
289func (m *identSet) String() string {
290 return strings.Join(m.idents, ",")
291}
292
293func (m *identSet) Set(s string) error {
294 m.idents = strings.FieldsFunc(s, func(c rune) bool {
295 return unicode.IsSpace(c) || c == ','
296 })
297 if len(m.idents) == 1 && m.idents[0] == "*" {
298 m.all = true
299 }
300 return nil
301}
302
303func (m *identSet) Get() interface{} {
304 return m.idents
305}