blob: 8dc2992ea8cc03529285b1245e731d1b45ba2905 [file] [log] [blame]
// 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"
"fuchsia.googlesource.com/infra/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
}