From 7c392cb011ba30a1ef60156af956af1d4c5ada24 Mon Sep 17 00:00:00 2001 From: Andrew Carter Date: Thu, 23 Jul 2015 15:39:07 -0700 Subject: [PATCH] Add exclude option Added --exclude option that mimics ctag's exclude flag. The pattern is expected to be a shell glob pattern (which is what Go's filepath.Match method expects). It can be set multiple times to set several filters. Once the file list is built, the patterns will be applied to each file to filter out any names that match the exclude patterns before processing. Note: Glob pattern '**' does not work recursively. See open issue https://github.com/golang/go/issues/11862 --- README.md | 22 ++++++++------ files_test.go | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 84 +++++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 168 insertions(+), 21 deletions(-) create mode 100644 files_test.go diff --git a/README.md b/README.md index 9b92d0c..2554efa 100644 --- a/README.md +++ b/README.md @@ -17,15 +17,19 @@ Or using package manager `brew` on OS X ## Usage - gotags [options] file(s) - - -L="": source file names are read from the specified file. If file is "-", input is read from standard in. - -R=false: recurse into directories in the file list. - -f="": write output to specified file. If file is "-", output is written to standard out. - -silent=false: do not produce any output on error. - -sort=true: sort tags. - -tag-relative=false: file paths should be relative to the directory containing the tag file. - -v=false: print version. + gotags [options] file(s) + + -L="": source file names are read from the specified file. If file is "-", input is read from standard in. + -R=false: recurse into directories in the file list. + -exclude=[]: exclude files and directories matching 'pattern'. May be called multiple times. + -f="": write output to specified file. If file is "-", output is written to standard out. + -fields="": include selected extension fields (only +l). + -list-languages=false: list supported languages. + -silent=false: do not produce any output on error. + -sort=true: sort tags. + -tag-relative=false: file paths should be relative to the directory containing the tag file. + -v=false: print version. + ## Vim [Tagbar][] configuration diff --git a/files_test.go b/files_test.go new file mode 100644 index 0000000..f30cf71 --- /dev/null +++ b/files_test.go @@ -0,0 +1,83 @@ +package main + +import ( + "reflect" + "strings" + "testing" +) + +var sources = []string{ + "LICENSE", + "README.md", + "fields.go", + "fields_test.go", + "files_test.go", + "main.go", + "parser.go", + "parser_test.go", + "tag.go", + "tag_test.go", + "tags", + "tests/const.go-src", + "tests/func.go-src", + "tests/import.go-src", + "tests/interface.go-src", + "tests/range.go-src", + "tests/simple.go-src", + "tests/struct.go-src", + "tests/type.go-src", + "tests/var.go-src", +} + +func TestCommandLineFiles(t *testing.T) { + patterns := patternList{} + + files, err := getFileNames(sources, false, patterns) + if err != nil { + t.Error(err) + } + + if !reflect.DeepEqual(sources, files) { + t.Errorf("%+v != %+v", sources, files) + } +} + +func TestSingleExcludePattern(t *testing.T) { + // Single pattern - exclude *_test.go files + patterns := []string{"*_test.go"} + files, err := getFileNames(sources, false, patterns) + if err != nil { + t.Error(err) + } + + for _, f := range files { + if strings.HasSuffix(f, "_test.go") { + t.Errorf("%v should not be included", f) + } + } +} + +func TestRecursiveExcludes(t *testing.T) { + input := []string{"/usr/local/go/src"} + patterns := []string{ + "*_test.go", + "/usr/local/go/src/*/*/testdata/*", + "/usr/local/go/src/*/*/testdata/*/*", + "/usr/local/go/src/*/*/testdata/*/*/*", + "/usr/local/go/src/*/*/testdata/*/*/*/*", + "/usr/local/go/src/*/*/testdata/*/*/*/*/*", + "/usr/local/go/src/*/*/testdata/*/*/*/*/*/*", + "/usr/local/go/src/*/*/testdata/*/*/*/*/*/*/*", + "/usr/local/go/src/*/*/*/testdata/*", + } + files, err := getFileNames(input, true, patterns) + if err != nil { + t.Error(err) + } + for _, f := range files { + if strings.HasSuffix(f, "_test.go") || strings.Contains(f, "/testdata/") { + t.Errorf("%v should not be included", f) + } + } + t.Log(files) +} diff --git a/main.go b/main.go index c954a01..f4f499b 100644 --- a/main.go +++ b/main.go @@ -21,16 +21,28 @@ const ( AuthorEmail = "stemmertech@gmail.com" ) +type patternList []string + +func (p *patternList) String() string { + return fmt.Sprint(*p) +} + +func (p *patternList) Set(value string) error { + *p = append(*p, value) + return nil +} + var ( - printVersion bool - inputFile string - outputFile string - recurse bool - sortOutput bool - silent bool - relative bool - listLangs bool - fields string + printVersion bool + inputFile string + outputFile string + recurse bool + sortOutput bool + silent bool + relative bool + listLangs bool + fields string + excludePatterns patternList ) // ignore unknown flags @@ -47,6 +59,7 @@ func init() { flags.BoolVar(&relative, "tag-relative", false, "file paths should be relative to the directory containing the tag file.") flags.BoolVar(&listLangs, "list-languages", false, "list supported languages.") flags.StringVar(&fields, "fields", "", "include selected extension fields (only +l).") + flags.Var(&excludePatterns, "exclude", "exclude files and directories matching 'pattern'. May be called multiple times.") flags.Usage = func() { fmt.Fprintf(os.Stderr, "gotags version %s\n\n", Version) @@ -69,6 +82,43 @@ func walkDir(names []string, dir string) ([]string, error) { return names, e } +// includeName checks if the name should be allowed or not based on the exclude patterns +func includeName(name string, patterns []string) (bool, error) { + for _, p := range patterns { + // Compare pattern to full path and then to base filename + for _, v := range []string{name, filepath.Base(name)} { + m, err := filepath.Match(p, v) + if err != nil { + // Error - exclude + return false, err + } + if m { + // Matches filepath - exclude + return false, nil + } + } + } + + // No filters matched - include the file + return true, nil +} + +func filterNames(names []string, patterns []string) ([]string, error) { + var ret []string + + for _, f := range names { + ok, err := includeName(f, patterns) + if err != nil { + return nil, err + } + if ok { + ret = append(ret, f) + } + } + + return ret, nil +} + func recurseNames(names []string) ([]string, error) { var ret []string for _, name := range names { @@ -113,15 +163,19 @@ func readNames(names []string) ([]string, error) { return names, nil } -func getFileNames() ([]string, error) { +func getFileNames(files []string, recurse bool, excludes patternList) ([]string, error) { var names []string - names = append(names, flags.Args()...) + // Start with list of supplied file names + names = append(names, files...) + + // Read filenames from input file if provided names, err := readNames(names) if err != nil { return nil, err } + // Recurse into directories in the file list if recurse { names, err = recurseNames(names) if err != nil { @@ -129,6 +183,12 @@ func getFileNames() ([]string, error) { } } + // Apply excludes patterns + names, err = filterNames(names, excludes) + if err != nil { + return nil, err + } + return names, nil } @@ -147,7 +207,7 @@ func main() { return } - files, err := getFileNames() + files, err := getFileNames(flags.Args(), recurse, excludePatterns) if err != nil { fmt.Fprintf(os.Stderr, "cannot get specified files\n\n") flags.Usage()