Skip to content

Commit 4f7a460

Browse files
committed
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.
1 parent 99ebc9c commit 4f7a460

File tree

6 files changed

+106
-12
lines changed

6 files changed

+106
-12
lines changed

.github/workflows/tests.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ jobs:
55
test:
66
strategy:
77
matrix:
8-
go-version: [1.x, 1.15.x]
8+
go-version: [1.x, 1.16.x]
99
platform: [ubuntu-latest]
1010
include:
1111
# only update test coverage stats with the most recent go version on linux

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@ to any file that already has one.
1919
-l license type: apache, bsd, mit, mpl (defaults to "apache")
2020
-y year (defaults to current year)
2121
-check check only mode: verify presence of license headers and exit with non-zero code if missing
22+
-ignore file patterns to ignore, for example: -ignore **/*.go -ignore vendor/**
2223

2324
The pattern argument can be provided multiple times, and may also refer
2425
to single files.
2526

27+
The `-ignore` flag can use any pattern [supported by
28+
doublestar](https://github.com/bmatcuk/doublestar#patterns).
29+
2630
## Running in a Docker Container
2731

2832
- Clone the repository using `git clone https://github.com/google/addlicense.git`

go.mod

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,7 @@ module github.com/google/addlicense
22

33
go 1.13
44

5-
require golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
5+
require (
6+
github.com/bmatcuk/doublestar/v4 v4.0.2
7+
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
8+
)

go.sum

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

main.go

+35-10
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"text/template"
3131
"time"
3232

33+
doublestar "github.com/bmatcuk/doublestar/v4"
3334
"golang.org/x/sync/errgroup"
3435
)
3536

@@ -48,7 +49,8 @@ Flags:
4849
`
4950

5051
var (
51-
skipExtensionFlags skipExtensionFlag
52+
skipExtensionFlags stringSlice
53+
ignorePatterns stringSlice
5254
spdx spdxFlag
5355

5456
holder = flag.String("c", "Google LLC", "copyright holder")
@@ -64,17 +66,19 @@ func init() {
6466
fmt.Fprintln(os.Stderr, helpText)
6567
flag.PrintDefaults()
6668
}
67-
flag.Var(&skipExtensionFlags, "skip", "To skip files to check/add the header file, for example: -skip rb -skip go")
69+
flag.Var(&skipExtensionFlags, "skip", "[deprecated: see -ignore] file extensions to skip, for example: -skip rb -skip go")
70+
flag.Var(&ignorePatterns, "ignore", "file patterns to ignore, for example: -ignore **/*.go -ignore vendor/**")
6871
flag.Var(&spdx, "s", "Include SPDX identifier in license header. Set -s=only to only include SPDX identifier.")
6972
}
7073

71-
type skipExtensionFlag []string
74+
// stringSlice stores the results of a repeated command line flag as a string slice.
75+
type stringSlice []string
7276

73-
func (i *skipExtensionFlag) String() string {
77+
func (i *stringSlice) String() string {
7478
return fmt.Sprint(*i)
7579
}
7680

77-
func (i *skipExtensionFlag) Set(value string) error {
81+
func (i *stringSlice) Set(value string) error {
7882
*i = append(*i, value)
7983
return nil
8084
}
@@ -109,6 +113,17 @@ func main() {
109113
os.Exit(1)
110114
}
111115

116+
// convert -skip flags to -ignore equivalents
117+
for _, s := range skipExtensionFlags {
118+
ignorePatterns = append(ignorePatterns, fmt.Sprintf("**/*.%s", s))
119+
}
120+
// verify that all ignorePatterns are valid
121+
for _, p := range ignorePatterns {
122+
if !doublestar.ValidatePattern(p) {
123+
log.Fatalf("-ignore pattern %q is not valid", p)
124+
}
125+
}
126+
112127
// map legacy license values
113128
if t, ok := legacyLicenseTypes[*license]; ok {
114129
*license = t
@@ -200,17 +215,27 @@ func walk(ch chan<- *file, start string) error {
200215
if fi.IsDir() {
201216
return nil
202217
}
203-
for _, skip := range skipExtensionFlags {
204-
if strings.TrimPrefix(filepath.Ext(fi.Name()), ".") == skip || fi.Name() == skip {
205-
log.Printf("%s: skipping this file", fi.Name())
206-
return nil
207-
}
218+
if fileMatches(path, ignorePatterns) {
219+
log.Printf("skipping: %s", path)
220+
return nil
208221
}
209222
ch <- &file{path, fi.Mode()}
210223
return nil
211224
})
212225
}
213226

227+
// fileMatches determines if path matches one of the provided file patterns.
228+
// Patterns are assumed to be valid.
229+
func fileMatches(path string, patterns []string) bool {
230+
for _, p := range patterns {
231+
// ignore error, since we assume patterns are valid
232+
if match, _ := doublestar.Match(p, path); match {
233+
return true
234+
}
235+
}
236+
return false
237+
}
238+
214239
// addLicense add a license to the file if missing.
215240
//
216241
// It returns true if the file was updated.

main_test.go

+60
Original file line numberDiff line numberDiff line change
@@ -397,3 +397,63 @@ func TestHasLicense(t *testing.T) {
397397
}
398398
}
399399
}
400+
401+
func TestFileMatches(t *testing.T) {
402+
tests := []struct {
403+
pattern string
404+
path string
405+
wantMatch bool
406+
}{
407+
// basic single directory patterns
408+
{"", "file.c", false},
409+
{"*.c", "file.h", false},
410+
{"*.c", "file.c", true},
411+
412+
// subdirectory patterns
413+
{"*.c", "vendor/file.c", false},
414+
{"**/*.c", "vendor/file.c", true},
415+
{"vendor/**", "vendor/file.c", true},
416+
{"vendor/**/*.c", "vendor/file.c", true},
417+
{"vendor/**/*.c", "vendor/a/b/file.c", true},
418+
419+
// single character "?" match
420+
{"*.?", "file.c", true},
421+
{"*.?", "file.go", false},
422+
{"*.??", "file.c", false},
423+
{"*.??", "file.go", true},
424+
425+
// character classes - sets and ranges
426+
{"*.[ch]", "file.c", true},
427+
{"*.[ch]", "file.h", true},
428+
{"*.[ch]", "file.ch", false},
429+
{"*.[a-z]", "file.c", true},
430+
{"*.[a-z]", "file.h", true},
431+
{"*.[a-z]", "file.go", false},
432+
{"*.[a-z]", "file.R", false},
433+
434+
// character classes - negations
435+
{"*.[^ch]", "file.c", false},
436+
{"*.[^ch]", "file.h", false},
437+
{"*.[^ch]", "file.R", true},
438+
{"*.[!ch]", "file.c", false},
439+
{"*.[!ch]", "file.h", false},
440+
{"*.[!ch]", "file.R", true},
441+
442+
// comma-separated alternative matches
443+
{"*.{c,go}", "file.c", true},
444+
{"*.{c,go}", "file.go", true},
445+
{"*.{c,go}", "file.h", false},
446+
447+
// negating alternative matches
448+
{"*.[^{c,go}]", "file.c", false},
449+
{"*.[^{c,go}]", "file.go", false},
450+
{"*.[^{c,go}]", "file.h", true},
451+
}
452+
453+
for _, tt := range tests {
454+
patterns := []string{tt.pattern}
455+
if got := fileMatches(tt.path, patterns); got != tt.wantMatch {
456+
t.Errorf("fileMatches(%q, %q) returned %v, want %v", tt.path, patterns, got, tt.wantMatch)
457+
}
458+
}
459+
}

0 commit comments

Comments
 (0)