| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package main |
| |
| import ( |
| "bytes" |
| "flag" |
| "fmt" |
| "go/format" |
| "io" |
| "io/ioutil" |
| "log" |
| "os" |
| "path/filepath" |
| "strings" |
| "sync" |
| "time" |
| |
| "go.fuchsia.dev/infra/gotidy" |
| ) |
| |
| // TidyStep transforms source code into other source code. |
| type TidyStep interface { |
| Tidy([]byte) ([]byte, error) |
| } |
| |
| // TidyFunc is an adapter for functions with the same signature as TidyStep.Tidy. |
| type TidyFunc func([]byte) ([]byte, error) |
| |
| // Tidy implements TidyStep. |
| func (fn TidyFunc) Tidy(input []byte) ([]byte, error) { |
| return fn(input) |
| } |
| |
| var ( |
| // Whether to overwrite the input file contents with tidied output. |
| overwrite bool |
| |
| // Whether to display usage and exit. |
| help bool |
| |
| // The list of tidy steps to run. |
| tidySteps = []TidyStep{ |
| &gotidy.AddCopyrightHeader{Year: time.Now().Year()}, |
| TidyFunc(format.Source), // gofmt. Always do this last. |
| } |
| ) |
| |
| func usage() { |
| fmt.Fprint(os.Stderr, "gotidy [flags] [path...]") |
| flag.PrintDefaults() |
| } |
| |
| func init() { |
| flag.BoolVar(&help, "h", false, "Display usage and exit") |
| flag.BoolVar(&overwrite, "w", false, "Whether to overwrite files with tidied output") |
| flag.Usage = usage |
| } |
| |
| func main() { |
| flag.Parse() |
| |
| if help || flag.NArg() == 0 { |
| flag.Usage() |
| return |
| } |
| |
| os.Exit(execute(flag.Args())) |
| } |
| |
| func execute(paths []string) int { |
| if len(paths) == 0 { |
| paths = []string{"."} |
| } |
| |
| var files []string |
| for _, path := range paths { |
| files = append(files, listGoFilesRecursive(path)...) |
| } |
| |
| return exitCode(tidyFiles(files)) |
| } |
| |
| func listGoFilesRecursive(root string) []string { |
| var paths []string |
| err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { |
| if err != nil { |
| return err |
| } |
| |
| if info.IsDir() && path != root { |
| paths = append(paths, listGoFilesRecursive(path)...) |
| } |
| |
| if strings.HasSuffix(path, ".go") { |
| paths = append(paths, path) |
| } |
| return nil |
| }) |
| |
| if err != nil { |
| return []string{} |
| } |
| |
| return paths |
| } |
| |
| func tidyFiles(files []string) <-chan error { |
| errs := make(chan error) |
| |
| var wg sync.WaitGroup |
| for _, file := range files { |
| wg.Add(1) |
| log.Println("tidying", file) |
| go tidyFile(file, errs, &wg) |
| } |
| |
| go func() { |
| wg.Wait() |
| close(errs) |
| }() |
| |
| return errs |
| } |
| |
| func tidyFile(file string, errs chan<- error, wg *sync.WaitGroup) { |
| input, err := ioutil.ReadFile(file) |
| if err != nil { |
| errs <- fmt.Errorf("failed to read %s: %v", file, err) |
| } |
| |
| tidied, err := tidy(input) |
| if err != nil { |
| errs <- fmt.Errorf("failed to tidy %s: %v", file, err) |
| } |
| |
| reader := bytes.NewReader(tidied) |
| dest := os.Stdout |
| if overwrite { |
| dest, err = os.Create(file) |
| if err != nil { |
| errs <- fmt.Errorf("failed to open %s: %v", file, err) |
| } |
| } |
| |
| if _, err := io.Copy(dest, reader); err != nil { |
| errs <- fmt.Errorf("failed to write output to %s: %v", file, err) |
| } |
| |
| wg.Done() |
| } |
| |
| func tidy(input []byte) ([]byte, error) { |
| source := input |
| |
| for _, tidyStep := range tidySteps { |
| output, err := tidyStep.Tidy(source) |
| if err != nil { |
| return nil, err |
| } |
| source = output |
| } |
| |
| return source, nil |
| } |
| |
| func exitCode(in <-chan error) int { |
| var code int |
| for err := range in { |
| log.Println(err) |
| code = 1 |
| } |
| return code |
| } |