Merge pull request #29 from mithun/issue-28

Implement `-check` flag and exit codes
This commit is contained in:
alex
2020-02-22 10:56:26 +01:00
committed by GitHub
6 changed files with 191 additions and 42 deletions

View File

@@ -1,13 +1,13 @@
dist: xenial dist: bionic
git: git:
depth: 3 depth: 3
language: go language: go
go: go:
- "1.x" - 1.13.x
- "1.10.x"
before_script: before_script:
- go get golang.org/x/lint/golint - go get golang.org/x/lint/golint
script: script:
- go mod tidy
- gofmt -d -e -l -s . - gofmt -d -e -l -s .
- golint -set_exit_status ./... - golint -set_exit_status ./...
- go test -v ./... - go test -v ./...

View File

@@ -18,6 +18,7 @@ to any file that already has one.
-f custom license file (no default) -f custom license file (no default)
-l license type: apache, bsd, mit (defaults to "apache") -l license type: apache, bsd, mit (defaults to "apache")
-y year (defaults to current year) -y year (defaults to current year)
-check check only mode: verify presence of license headers and exit with non-zero code if missing
The pattern argument can be provided multiple times, and may also refer The pattern argument can be provided multiple times, and may also refer
to single files. to single files.

5
go.mod Normal file
View File

@@ -0,0 +1,5 @@
module github.com/google/addlicense
go 1.13
require golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e

2
go.sum Normal file
View File

@@ -0,0 +1,2 @@
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=

122
main.go
View File

@@ -18,6 +18,7 @@ package main
import ( import (
"bytes" "bytes"
"errors"
"flag" "flag"
"fmt" "fmt"
"html/template" "html/template"
@@ -26,8 +27,9 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"sync"
"time" "time"
"golang.org/x/sync/errgroup"
) )
const helpText = `Usage: addlicense [flags] pattern [pattern ...] const helpText = `Usage: addlicense [flags] pattern [pattern ...]
@@ -45,11 +47,12 @@ Flags:
` `
var ( var (
holder = flag.String("c", "Google LLC", "copyright holder") holder = flag.String("c", "Google LLC", "copyright holder")
license = flag.String("l", "apache", "license type: apache, bsd, mit") license = flag.String("l", "apache", "license type: apache, bsd, mit")
licensef = flag.String("f", "", "license file") licensef = flag.String("f", "", "license file")
year = flag.String("y", fmt.Sprint(time.Now().Year()), "copyright year(s)") year = flag.String("y", fmt.Sprint(time.Now().Year()), "copyright year(s)")
verbose = flag.Bool("v", false, "verbose mode: print the name of the files that are modified") verbose = flag.Bool("v", false, "verbose mode: print the name of the files that are modified")
checkonly = flag.Bool("check", false, "check only mode: verify presence of license headers and exit with non-zero code if missing")
) )
func main() { func main() {
@@ -92,23 +95,48 @@ func main() {
ch := make(chan *file, 1000) ch := make(chan *file, 1000)
done := make(chan struct{}) done := make(chan struct{})
go func() { go func() {
var wg sync.WaitGroup var wg errgroup.Group
for f := range ch { for f := range ch {
wg.Add(1) f := f // https://golang.org/doc/faq#closures_and_goroutines
go func(f *file) { wg.Go(func() error {
defer wg.Done() if *checkonly {
modified, err := addLicense(f.path, f.mode, t, data) // Check if file extension is known
if err != nil { lic, err := licenseHeader(f.path, t, data)
log.Printf("%s: %v", f.path, err) if err != nil {
return log.Printf("%s: %v", f.path, err)
return err
}
if lic == nil { // Unknown fileExtension
return nil
}
// Check if file has a license
isMissingLicenseHeader, err := fileHasLicense(f.path)
if err != nil {
log.Printf("%s: %v", f.path, err)
return err
}
if isMissingLicenseHeader {
fmt.Printf("%s\n", f.path)
return errors.New("missing license header")
}
} else {
modified, err := addLicense(f.path, f.mode, t, data)
if err != nil {
log.Printf("%s: %v", f.path, err)
return err
}
if *verbose && modified {
log.Printf("%s modified", f.path)
}
} }
if *verbose && modified { return nil
log.Printf("%s modified", f.path) })
}
}(f)
} }
wg.Wait() err := wg.Wait()
close(done) close(done)
if err != nil {
os.Exit(1)
}
}() }()
for _, d := range flag.Args() { for _, d := range flag.Args() {
@@ -138,11 +166,45 @@ func walk(ch chan<- *file, start string) {
} }
func addLicense(path string, fmode os.FileMode, tmpl *template.Template, data *copyrightData) (bool, error) { func addLicense(path string, fmode os.FileMode, tmpl *template.Template, data *copyrightData) (bool, error) {
var lic []byte
var err error
lic, err = licenseHeader(path, tmpl, data)
if err != nil || lic == nil {
return false, err
}
b, err := ioutil.ReadFile(path)
if err != nil || hasLicense(b) {
return false, err
}
line := hashBang(b)
if len(line) > 0 {
b = b[len(line):]
if line[len(line)-1] != '\n' {
line = append(line, '\n')
}
lic = append(line, lic...)
}
b = append(lic, b...)
return true, ioutil.WriteFile(path, b, fmode)
}
// fileHasLicense reports whether the file at path contains a license header.
func fileHasLicense(path string) (bool, error) {
b, err := ioutil.ReadFile(path)
if err != nil || hasLicense(b) {
return false, err
}
return true, nil
}
func licenseHeader(path string, tmpl *template.Template, data *copyrightData) ([]byte, error) {
var lic []byte var lic []byte
var err error var err error
switch fileExtension(path) { switch fileExtension(path) {
default: default:
return false, nil return nil, nil
case ".c", ".h": case ".c", ".h":
lic, err = prefix(tmpl, data, "/*", " * ", " */") lic, err = prefix(tmpl, data, "/*", " * ", " */")
case ".js", ".jsx", ".tsx", ".css", ".tf", ".ts": case ".js", ".jsx", ".tsx", ".css", ".tf", ".ts":
@@ -164,25 +226,7 @@ func addLicense(path string, fmode os.FileMode, tmpl *template.Template, data *c
case ".ml", ".mli", ".mll", ".mly": case ".ml", ".mli", ".mll", ".mly":
lic, err = prefix(tmpl, data, "(**", " ", "*)") lic, err = prefix(tmpl, data, "(**", " ", "*)")
} }
if err != nil || lic == nil { return lic, err
return false, err
}
b, err := ioutil.ReadFile(path)
if err != nil || hasLicense(b) {
return false, err
}
line := hashBang(b)
if len(line) > 0 {
b = b[len(line):]
if line[len(line)-1] != '\n' {
line = append(line, '\n')
}
lic = append(line, lic...)
}
b = append(lic, b...)
return true, ioutil.WriteFile(path, b, fmode)
} }
func fileExtension(name string) string { func fileExtension(name string) string {

View File

@@ -87,3 +87,100 @@ func TestMultiyear(t *testing.T) {
} }
run(t, "diff", samplefile, sampleLicensed) run(t, "diff", samplefile, sampleLicensed)
} }
func TestWriteErrors(t *testing.T) {
if os.Getenv("RUNME") != "" {
main()
return
}
tmp := tempDir(t)
t.Logf("tmp dir: %s", tmp)
samplefile := filepath.Join(tmp, "file.c")
run(t, "cp", "testdata/initial/file.c", samplefile)
run(t, "chmod", "0444", samplefile)
cmd := exec.Command(os.Args[0],
"-test.run=TestWriteErrors",
"-l", "apache", "-c", "Google LLC", "-y", "2018",
samplefile,
)
cmd.Env = []string{"RUNME=1"}
out, err := cmd.CombinedOutput()
if err == nil {
run(t, "chmod", "0644", samplefile)
t.Fatalf("TestWriteErrors exited with a zero exit code.\n%s", out)
}
run(t, "chmod", "0644", samplefile)
}
func TestReadErrors(t *testing.T) {
if os.Getenv("RUNME") != "" {
main()
return
}
tmp := tempDir(t)
t.Logf("tmp dir: %s", tmp)
samplefile := filepath.Join(tmp, "file.c")
run(t, "cp", "testdata/initial/file.c", samplefile)
run(t, "chmod", "a-r", samplefile)
cmd := exec.Command(os.Args[0],
"-test.run=TestReadErrors",
"-l", "apache", "-c", "Google LLC", "-y", "2018",
samplefile,
)
cmd.Env = []string{"RUNME=1"}
out, err := cmd.CombinedOutput()
if err == nil {
run(t, "chmod", "0644", samplefile)
t.Fatalf("TestWriteErrors exited with a zero exit code.\n%s", out)
}
run(t, "chmod", "0644", samplefile)
}
func TestCheckSuccess(t *testing.T) {
if os.Getenv("RUNME") != "" {
main()
return
}
tmp := tempDir(t)
t.Logf("tmp dir: %s", tmp)
samplefile := filepath.Join(tmp, "file.c")
run(t, "cp", "testdata/expected/file.c", samplefile)
cmd := exec.Command(os.Args[0],
"-test.run=TestCheckSuccess",
"-l", "apache", "-c", "Google LLC", "-y", "2018",
"-check", samplefile,
)
cmd.Env = []string{"RUNME=1"}
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("%v\n%s", err, out)
}
}
func TestCheckFail(t *testing.T) {
if os.Getenv("RUNME") != "" {
main()
return
}
tmp := tempDir(t)
t.Logf("tmp dir: %s", tmp)
samplefile := filepath.Join(tmp, "file.c")
run(t, "cp", "testdata/initial/file.c", samplefile)
cmd := exec.Command(os.Args[0],
"-test.run=TestCheckFail",
"-l", "apache", "-c", "Google LLC", "-y", "2018",
"-check", samplefile,
)
cmd.Env = []string{"RUNME=1"}
out, err := cmd.CombinedOutput()
if err == nil {
t.Fatalf("TestCheckFail exited with a zero exit code.\n%s", out)
}
}