Merge pull request #29 from mithun/issue-28
Implement `-check` flag and exit codes
This commit is contained in:
@@ -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 ./...
|
||||||
|
|||||||
@@ -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
5
go.mod
Normal 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
2
go.sum
Normal 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
122
main.go
@@ -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 {
|
||||||
|
|||||||
97
main_test.go
97
main_test.go
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user