diff --git a/.stylize.yml b/.stylize.yml index a3c0112..8c07d78 100644 --- a/.stylize.yml +++ b/.stylize.yml @@ -2,7 +2,7 @@ formatters: .py: yapf .go: gofmt -exclude_dirs: +exclude: - testdata clang_style: google yapf_style: pep8 diff --git a/config.go b/config.go index 8ca5b27..2505ea6 100644 --- a/config.go +++ b/config.go @@ -8,7 +8,7 @@ import ( // This type defines the structure of the yml config file for stylize. type Config struct { FormattersByExt map[string]string `yaml:"formatters"` - ExcludeDirs []string `yaml:"exclude_dirs"` + ExcludePatterns []string `yaml:"exclude"` // TODO: do better ClangStyle string `yaml:"clang_style"` diff --git a/main.go b/main.go index 5f9f32d..b1ad70f 100644 --- a/main.go +++ b/main.go @@ -14,7 +14,7 @@ func main() { patchFileFlag := flag.String("patch_output", "", "Path to output patch to. If '-', writes to stdout.") configFileFlag := flag.String("config", ".stylize.yml", "Optional config file (defaults to .stylize.yml).") dirFlag := flag.String("dir", ".", "Directory to recursively format.") - excludeDirFlag := flag.String("exclude_dirs", "", "Directories to exclude (comma-separated).") + excludeFlag := flag.String("exclude", "", "A list of exclude patterns (comma-separated).") diffbaseFlag := flag.String("git_diffbase", "", "If provided, stylize only looks at files that differ from the given commit/branch.") parallelismFlag := flag.Int("j", 8, "Number of files to process in parallel.") flag.Parse() @@ -31,7 +31,10 @@ func main() { log.Printf("Loaded config from file %s", *configFileFlag) } - rootDir := absPathOrFail(*dirFlag) + rootDir, err := filepath.Abs(*dirFlag) + if err != nil { + log.Fatal(err) + } // set style configs from config file // TODO: do better @@ -44,20 +47,14 @@ func main() { } } - var excludeDirs []string + var ExcludePatterns []string // exclude dirs from config if cfg != nil { - excludeDirs = append(excludeDirs, cfg.ExcludeDirs...) + ExcludePatterns = append(ExcludePatterns, cfg.ExcludePatterns...) } // exclude dirs from flag - if len(*excludeDirFlag) > 0 { - excludeDirs = append(excludeDirs, strings.Split(*excludeDirFlag, ",")...) - } - // make exclude dirs absolute - if they're not already, they're assumed to be relative to the root directory - for i, edir := range excludeDirs { - if !filepath.IsAbs(edir) { - excludeDirs[i] = filepath.Join(rootDir, edir) - } + if len(*excludeFlag) > 0 { + ExcludePatterns = append(ExcludePatterns, strings.Split(*excludeFlag, ",")...) } // setup formatters @@ -86,9 +83,9 @@ func main() { defer patchFileOut.Close() log.Printf("Writing patch to file %s", *patchFileFlag) } - stats = StylizeMain(formatters, rootDir, excludeDirs, *diffbaseFlag, patchOut, *inPlaceFlag, *parallelismFlag) + stats = StylizeMain(formatters, rootDir, ExcludePatterns, *diffbaseFlag, patchOut, *inPlaceFlag, *parallelismFlag) } else { - stats = StylizeMain(formatters, rootDir, excludeDirs, *diffbaseFlag, nil, *inPlaceFlag, *parallelismFlag) + stats = StylizeMain(formatters, rootDir, ExcludePatterns, *diffbaseFlag, nil, *inPlaceFlag, *parallelismFlag) } if stats.Error != 0 { diff --git a/stylize.go b/stylize.go index e850a19..56c1047 100644 --- a/stylize.go +++ b/stylize.go @@ -22,19 +22,10 @@ type FormattingResult struct { Error error } -func fileIsExcluded(file string, excludeDirs []string) bool { - for _, eDir := range excludeDirs { - if filepath.HasPrefix(file, eDir) { - return true - } - } - return false -} - // Walks the given directory and sends all non-excluded files to the returned channel. // @param rootDir absolute path to root directory // @return file paths relative to rootDir -func IterateAllFiles(rootDir string, excludeDirs []string) <-chan string { +func IterateAllFiles(rootDir string, exclude []string) <-chan string { files := make(chan string) go func() { @@ -44,16 +35,21 @@ func IterateAllFiles(rootDir string, excludeDirs []string) <-chan string { return nil } - excludeDirs = append(excludeDirs, absPathOrFail(".git"), absPathOrFail(".hg")) - if fi.IsDir() && fileIsExcluded(path, excludeDirs) { + relPath, _ := filepath.Rel(rootDir, path) + + exclude = append(exclude, ".git", ".hg") + if fi.IsDir() && fileIsExcluded(relPath, exclude) { return filepath.SkipDir } + if fileIsExcluded(relPath, exclude) { + return nil + } + if fi.IsDir() { return nil } - relPath, _ := filepath.Rel(rootDir, path) files <- relPath return nil @@ -66,7 +62,7 @@ func IterateAllFiles(rootDir string, excludeDirs []string) <-chan string { // Finds files that have been modified since the common ancestor of HEAD and // diffbase and sends them onto the returned channel. // @return file paths relative to rootDir -func IterateGitChangedFiles(rootDir string, excludeDirs []string, diffbase string) (<-chan string, error) { +func IterateGitChangedFiles(rootDir string, exclude []string, diffbase string) (<-chan string, error) { changedFiles, err := gitChangedFiles(rootDir, diffbase) if err != nil { return nil, err @@ -90,8 +86,14 @@ func IterateGitChangedFiles(rootDir string, excludeDirs []string, diffbase strin for _, file := range changedFiles { absPath := filepath.Join(gitRoot, file) - if fileIsExcluded(absPath, excludeDirs) { - // log.Printf("Excluding file: %s", absPath) + + // get file path relative to root directory + relPath, err := filepath.Rel(rootDir, absPath) + if err != nil { + log.Fatal(err) + } + + if fileIsExcluded(relPath, exclude) { continue } @@ -102,12 +104,6 @@ func IterateGitChangedFiles(rootDir string, excludeDirs []string, diffbase strin continue } - // get file path relative to root directory - relPath, err := filepath.Rel(rootDir, absPath) - if err != nil { - log.Fatal(err) - } - files <- relPath } }() @@ -263,7 +259,7 @@ func LogActionsAndCollectStats(results <-chan FormattingResult, inPlace bool) Ru // diffbase. Otherwise looks at all files. // @param formatters A map of file extension -> formatter // @return (changeCount, totalCount, errCount) -func StylizeMain(formatters map[string]Formatter, rootDir string, excludeDirs []string, gitDiffbase string, patchOut io.Writer, inPlace bool, parallelism int) RunStats { +func StylizeMain(formatters map[string]Formatter, rootDir string, exclude []string, gitDiffbase string, patchOut io.Writer, inPlace bool, parallelism int) RunStats { if inPlace && patchOut != nil { log.Fatal("Patch output writer should only be provided in non-inplace runs") } @@ -271,9 +267,9 @@ func StylizeMain(formatters map[string]Formatter, rootDir string, excludeDirs [] log.Fatalf("root directory should be an absolute path: '%s'", rootDir) } - for _, excl := range excludeDirs { - if !filepath.IsAbs(excl) { - log.Fatal("exclude directories should be absolute") + for _, excl := range exclude { + if filepath.IsAbs(excl) { + log.Fatal("exclude directories should not be absolute") } } @@ -281,12 +277,12 @@ func StylizeMain(formatters map[string]Formatter, rootDir string, excludeDirs [] var err error var fileChan <-chan string if len(gitDiffbase) > 0 { - fileChan, err = IterateGitChangedFiles(rootDir, excludeDirs, gitDiffbase) + fileChan, err = IterateGitChangedFiles(rootDir, exclude, gitDiffbase) if err != nil { log.Fatal(err) } } else { - fileChan = IterateAllFiles(rootDir, excludeDirs) + fileChan = IterateAllFiles(rootDir, exclude) } // run formatter on all files diff --git a/stylize_test.go b/stylize_test.go index d0b02ff..1792bbd 100644 --- a/stylize_test.go +++ b/stylize_test.go @@ -24,6 +24,18 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } +func TestMatch(t *testing.T) { + m := filePatternMatch("exclude/*", "exclude/main.cpp") + if !m { + t.Fatal("Should have matched") + } + + m = filePatternMatch("exclude", "exclude") + if !m { + t.Fatal("Should have matched") + } +} + func TestCreatePatch(t *testing.T) { goldenFile := "testdata/patch.golden" @@ -42,7 +54,8 @@ func TestCreatePatch(t *testing.T) { patchOut = &patchBuffer } - StylizeMain(LoadDefaultFormatters(), absPathOrFail("testdata"), []string{absPathOrFail("testdata/exclude")}, "", patchOut, false, PARALLELISM) + absDirPath, _ := filepath.Abs("testdata") + StylizeMain(LoadDefaultFormatters(), absDirPath, []string{"exclude"}, "", patchOut, false, PARALLELISM) if !*generateGoldens { assertGoldenMatch(t, goldenFile, patchBuffer.String()) @@ -60,7 +73,7 @@ func TestInPlace(t *testing.T) { tmp := mktmp(t) dir := copyTestData(t, tmp) - exclude := []string{path.Join(dir, "exclude")} + exclude := []string{"exclude"} t.Log("exclude: " + strings.Join(exclude, ",")) // run in-place formatting @@ -83,30 +96,26 @@ func TestInPlaceWithConfig(t *testing.T) { dir := copyTestData(t, tmp) cfgPath := path.Join(dir, ".stylize.yml") - err := ioutil.WriteFile(cfgPath, []byte("---\nformatters:\n .py: yapf\nexclude_dirs:\n - exclude"), 0644) + err := ioutil.WriteFile(cfgPath, []byte("---\nformatters:\n .py: yapf\nexclude:\n - exclude"), 0644) tCheckErr(t, err) t.Logf("Wrote config file: %s", cfgPath) cfg, err := LoadConfig(cfgPath) tCheckErr(t, err) t.Log("Read config file") - - for i, edir := range cfg.ExcludeDirs { - cfg.ExcludeDirs[i] = filepath.Join(dir, edir) - } - t.Log("exclude: " + strings.Join(cfg.ExcludeDirs, ",")) + t.Log("exclude: " + strings.Join(cfg.ExcludePatterns, ",")) formatters := LoadFormattersFromMapping(cfg.FormattersByExt) // run in-place formatting - stats := StylizeMain(formatters, dir, cfg.ExcludeDirs, "", nil, true, PARALLELISM) + stats := StylizeMain(formatters, dir, cfg.ExcludePatterns, "", nil, true, PARALLELISM) t.Logf("Stylize results: %d, %d, %d", stats.Change, stats.Total, stats.Error) if stats.Change != 1 { t.Fatal("One file should have changed") } - stats = StylizeMain(formatters, dir, cfg.ExcludeDirs, "", nil, true, PARALLELISM) + stats = StylizeMain(formatters, dir, cfg.ExcludePatterns, "", nil, true, PARALLELISM) t.Logf("Stylize results: %d, %d, %d", stats.Change, stats.Total, stats.Error) if stats.Change != 0 { @@ -135,7 +144,7 @@ func TestGitDiffbase(t *testing.T) { runCmd(t, dir, "git", "add", ".") runCmd(t, dir, "git", "commit", "-m", "added files") - exclude := []string{path.Join(dir, "exclude")} + exclude := []string{"exclude"} t.Log("exclude: " + strings.Join(exclude, ",")) // run stylize with diffbase provided diff --git a/util.go b/util.go index c4ae9c3..f921896 100644 --- a/util.go +++ b/util.go @@ -9,7 +9,7 @@ import ( "log" "os" "os/exec" - "path/filepath" + "path" "strings" "syscall" "unsafe" @@ -30,14 +30,6 @@ func runIOCommand(args []string, in io.Reader, out io.Writer) error { return nil } -func absPathOrFail(path string) string { - absPath, err := filepath.Abs(path) - if err != nil { - log.Fatal(err) - } - return absPath -} - type winsize struct { Row uint16 Col uint16 @@ -90,3 +82,29 @@ func gitChangedFiles(rootDir, diffbase string) ([]string, error) { return changedFiles, nil } + +// TODO: this implementation has a lot of flaws +// It would be nice to do something similar to gitignore +func filePatternMatch(pattern, file string) bool { + match, err := path.Match(pattern, file) + if match { + return true + } + if err != nil { + log.Fatal(err) + } + + if strings.HasPrefix(file, pattern) { + return true + } + return false +} + +func fileIsExcluded(file string, exclude []string) bool { + for _, e := range exclude { + if filePatternMatch(e, file) { + return true + } + } + return false +}