github.com/colincross/blueprint@v0.0.0-20150626231830-9c067caf2eb5/bpmodify/bpmodify.go (about)

     1  // 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  
     6  package main
     7  
     8  import (
     9  	"bytes"
    10  	"flag"
    11  	"fmt"
    12  	"github.com/google/blueprint/parser"
    13  	"io"
    14  	"io/ioutil"
    15  	"os"
    16  	"os/exec"
    17  	"path/filepath"
    18  	"strings"
    19  	"unicode"
    20  )
    21  
    22  var (
    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  
    34  func 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  
    40  var (
    41  	exitCode = 0
    42  )
    43  
    44  func report(err error) {
    45  	fmt.Fprintln(os.Stderr, err)
    46  	exitCode = 2
    47  }
    48  
    49  func 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.
    56  func 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  
   121  func 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  
   140  func 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  
   153  func 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  
   189  func 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  
   202  func 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  
   212  func walkDir(path string) {
   213  	filepath.Walk(path, visitFile)
   214  }
   215  
   216  func 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  
   256  func 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  
   284  type identSet struct {
   285  	idents []string
   286  	all    bool
   287  }
   288  
   289  func (m *identSet) String() string {
   290  	return strings.Join(m.idents, ",")
   291  }
   292  
   293  func (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  
   303  func (m *identSet) Get() interface{} {
   304  	return m.idents
   305  }