Skip to content

Commit

Permalink
Merge pull request #29 from mithun/issue-28
Browse files Browse the repository at this point in the history
Implement `-check` flag and exit codes
  • Loading branch information
x1ddos authored Feb 22, 2020
2 parents 9fa18aa + 27146d5 commit 3db4982
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 42 deletions.
6 changes: 3 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
dist: xenial
dist: bionic
git:
depth: 3
language: go
go:
- "1.x"
- "1.10.x"
- 1.13.x
before_script:
- go get golang.org/x/lint/golint
script:
- go mod tidy
- gofmt -d -e -l -s .
- golint -set_exit_status ./...
- go test -v ./...
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ to any file that already has one.
-f custom license file (no default)
-l license type: apache, bsd, mit (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

The pattern argument can be provided multiple times, and may also refer
to single files.
Expand Down
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/google/addlicense

go 1.13

require golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -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 changes: 83 additions & 39 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package main

import (
"bytes"
"errors"
"flag"
"fmt"
"html/template"
Expand All @@ -26,8 +27,9 @@ import (
"os"
"path/filepath"
"strings"
"sync"
"time"

"golang.org/x/sync/errgroup"
)

const helpText = `Usage: addlicense [flags] pattern [pattern ...]
Expand All @@ -45,11 +47,12 @@ Flags:
`

var (
holder = flag.String("c", "Google LLC", "copyright holder")
license = flag.String("l", "apache", "license type: apache, bsd, mit")
licensef = flag.String("f", "", "license file")
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")
holder = flag.String("c", "Google LLC", "copyright holder")
license = flag.String("l", "apache", "license type: apache, bsd, mit")
licensef = flag.String("f", "", "license file")
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")
checkonly = flag.Bool("check", false, "check only mode: verify presence of license headers and exit with non-zero code if missing")
)

func main() {
Expand Down Expand Up @@ -92,23 +95,48 @@ func main() {
ch := make(chan *file, 1000)
done := make(chan struct{})
go func() {
var wg sync.WaitGroup
var wg errgroup.Group
for f := range ch {
wg.Add(1)
go func(f *file) {
defer wg.Done()
modified, err := addLicense(f.path, f.mode, t, data)
if err != nil {
log.Printf("%s: %v", f.path, err)
return
}
if *verbose && modified {
log.Printf("%s modified", f.path)
f := f // https://golang.org/doc/faq#closures_and_goroutines
wg.Go(func() error {
if *checkonly {
// Check if file extension is known
lic, err := licenseHeader(f.path, t, data)
if err != nil {
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)
}
}
}(f)
return nil
})
}
wg.Wait()
err := wg.Wait()
close(done)
if err != nil {
os.Exit(1)
}
}()

for _, d := range flag.Args() {
Expand Down Expand Up @@ -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) {
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 err error
switch fileExtension(path) {
default:
return false, nil
return nil, nil
case ".c", ".h":
lic, err = prefix(tmpl, data, "/*", " * ", " */")
case ".js", ".jsx", ".tsx", ".css", ".tf", ".ts":
Expand All @@ -164,25 +226,7 @@ func addLicense(path string, fmode os.FileMode, tmpl *template.Template, data *c
case ".ml", ".mli", ".mll", ".mly":
lic, err = prefix(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)
return lic, err
}

func fileExtension(name string) string {
Expand Down
97 changes: 97 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,100 @@ func TestMultiyear(t *testing.T) {
}
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)
}
}

0 comments on commit 3db4982

Please sign in to comment.