support ignoring file or directories

use the doublestar library to support pattern matching of files or
directories to ignore. This replaces (and deprecates) the previous
-skip flag which only supported file extensions.
This commit is contained in:
Will Norris
2021-07-28 02:41:40 -07:00
parent 99ebc9c9db
commit 4f7a460357
6 changed files with 106 additions and 12 deletions

View File

@@ -5,7 +5,7 @@ jobs:
test:
strategy:
matrix:
go-version: [1.x, 1.15.x]
go-version: [1.x, 1.16.x]
platform: [ubuntu-latest]
include:
# only update test coverage stats with the most recent go version on linux

View File

@@ -19,10 +19,14 @@ to any file that already has one.
-l license type: apache, bsd, mit, mpl (defaults to "apache")
-y year (defaults to current year)
-check check only mode: verify presence of license headers and exit with non-zero code if missing
-ignore file patterns to ignore, for example: -ignore **/*.go -ignore vendor/**
The pattern argument can be provided multiple times, and may also refer
to single files.
The `-ignore` flag can use any pattern [supported by
doublestar](https://github.com/bmatcuk/doublestar#patterns).
## Running in a Docker Container
- Clone the repository using `git clone https://github.com/google/addlicense.git`

5
go.mod
View File

@@ -2,4 +2,7 @@ module github.com/google/addlicense
go 1.13
require golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
require (
github.com/bmatcuk/doublestar/v4 v4.0.2
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
)

2
go.sum
View File

@@ -1,2 +1,4 @@
github.com/bmatcuk/doublestar/v4 v4.0.2 h1:X0krlUVAVmtr2cRoTqR8aDMrDqnB36ht8wpWTiQ3jsA=
github.com/bmatcuk/doublestar/v4 v4.0.2/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

45
main.go
View File

@@ -30,6 +30,7 @@ import (
"text/template"
"time"
doublestar "github.com/bmatcuk/doublestar/v4"
"golang.org/x/sync/errgroup"
)
@@ -48,7 +49,8 @@ Flags:
`
var (
skipExtensionFlags skipExtensionFlag
skipExtensionFlags stringSlice
ignorePatterns stringSlice
spdx spdxFlag
holder = flag.String("c", "Google LLC", "copyright holder")
@@ -64,17 +66,19 @@ func init() {
fmt.Fprintln(os.Stderr, helpText)
flag.PrintDefaults()
}
flag.Var(&skipExtensionFlags, "skip", "To skip files to check/add the header file, for example: -skip rb -skip go")
flag.Var(&skipExtensionFlags, "skip", "[deprecated: see -ignore] file extensions to skip, for example: -skip rb -skip go")
flag.Var(&ignorePatterns, "ignore", "file patterns to ignore, for example: -ignore **/*.go -ignore vendor/**")
flag.Var(&spdx, "s", "Include SPDX identifier in license header. Set -s=only to only include SPDX identifier.")
}
type skipExtensionFlag []string
// stringSlice stores the results of a repeated command line flag as a string slice.
type stringSlice []string
func (i *skipExtensionFlag) String() string {
func (i *stringSlice) String() string {
return fmt.Sprint(*i)
}
func (i *skipExtensionFlag) Set(value string) error {
func (i *stringSlice) Set(value string) error {
*i = append(*i, value)
return nil
}
@@ -109,6 +113,17 @@ func main() {
os.Exit(1)
}
// convert -skip flags to -ignore equivalents
for _, s := range skipExtensionFlags {
ignorePatterns = append(ignorePatterns, fmt.Sprintf("**/*.%s", s))
}
// verify that all ignorePatterns are valid
for _, p := range ignorePatterns {
if !doublestar.ValidatePattern(p) {
log.Fatalf("-ignore pattern %q is not valid", p)
}
}
// map legacy license values
if t, ok := legacyLicenseTypes[*license]; ok {
*license = t
@@ -200,17 +215,27 @@ func walk(ch chan<- *file, start string) error {
if fi.IsDir() {
return nil
}
for _, skip := range skipExtensionFlags {
if strings.TrimPrefix(filepath.Ext(fi.Name()), ".") == skip || fi.Name() == skip {
log.Printf("%s: skipping this file", fi.Name())
return nil
}
if fileMatches(path, ignorePatterns) {
log.Printf("skipping: %s", path)
return nil
}
ch <- &file{path, fi.Mode()}
return nil
})
}
// fileMatches determines if path matches one of the provided file patterns.
// Patterns are assumed to be valid.
func fileMatches(path string, patterns []string) bool {
for _, p := range patterns {
// ignore error, since we assume patterns are valid
if match, _ := doublestar.Match(p, path); match {
return true
}
}
return false
}
// addLicense add a license to the file if missing.
//
// It returns true if the file was updated.

View File

@@ -397,3 +397,63 @@ func TestHasLicense(t *testing.T) {
}
}
}
func TestFileMatches(t *testing.T) {
tests := []struct {
pattern string
path string
wantMatch bool
}{
// basic single directory patterns
{"", "file.c", false},
{"*.c", "file.h", false},
{"*.c", "file.c", true},
// subdirectory patterns
{"*.c", "vendor/file.c", false},
{"**/*.c", "vendor/file.c", true},
{"vendor/**", "vendor/file.c", true},
{"vendor/**/*.c", "vendor/file.c", true},
{"vendor/**/*.c", "vendor/a/b/file.c", true},
// single character "?" match
{"*.?", "file.c", true},
{"*.?", "file.go", false},
{"*.??", "file.c", false},
{"*.??", "file.go", true},
// character classes - sets and ranges
{"*.[ch]", "file.c", true},
{"*.[ch]", "file.h", true},
{"*.[ch]", "file.ch", false},
{"*.[a-z]", "file.c", true},
{"*.[a-z]", "file.h", true},
{"*.[a-z]", "file.go", false},
{"*.[a-z]", "file.R", false},
// character classes - negations
{"*.[^ch]", "file.c", false},
{"*.[^ch]", "file.h", false},
{"*.[^ch]", "file.R", true},
{"*.[!ch]", "file.c", false},
{"*.[!ch]", "file.h", false},
{"*.[!ch]", "file.R", true},
// comma-separated alternative matches
{"*.{c,go}", "file.c", true},
{"*.{c,go}", "file.go", true},
{"*.{c,go}", "file.h", false},
// negating alternative matches
{"*.[^{c,go}]", "file.c", false},
{"*.[^{c,go}]", "file.go", false},
{"*.[^{c,go}]", "file.h", true},
}
for _, tt := range tests {
patterns := []string{tt.pattern}
if got := fileMatches(tt.path, patterns); got != tt.wantMatch {
t.Errorf("fileMatches(%q, %q) returned %v, want %v", tt.path, patterns, got, tt.wantMatch)
}
}
}