hack/licenseok/main.go
// +build ignore
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Checks if all files have the license header, a lot of this is based off
// https://github.com/google/addlicense.
package main
import (
"bytes"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"sync"
"time"
)
const helpText = `Usage: licenseok [flags] pattern [pattern ...]
This program ensures source code files have copyright license headers
by scanning directory patterns recursively.
The pattern argument can be provided multiple times, and may also refer
to single files.
Flags:
`
const tmpl = `The Go Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.`
var (
update bool
)
type file struct {
path string
mode os.FileMode
}
func init() {
flag.BoolVar(&update, "u", false, "modifies all source files in place and avoids adding a license header to any file that already has one.")
flag.Usage = func() {
fmt.Fprintln(os.Stderr, helpText)
flag.PrintDefaults()
}
flag.Parse()
if flag.NArg() == 0 {
flag.Usage()
os.Exit(1)
}
}
func main() {
exitStatus := 0
// process at most 1000 files in parallel
ch := make(chan *file, 1000)
done := make(chan struct{})
go func() {
var wg sync.WaitGroup
for f := range ch {
wg.Add(1)
go func(f *file) {
b, err := ioutil.ReadFile(f.path)
if err != nil {
log.Printf("%s: %v", f.path, err)
exitStatus = 1
}
if !hasLicense(b) {
if !update {
fmt.Fprintln(os.Stderr, f.path)
exitStatus = 1
} else {
fmt.Fprintln(os.Stdout, f.path)
if err := addLicense(b, f.path, f.mode); err != nil {
log.Printf("%s: %v", f.path, err)
exitStatus = 1
}
}
}
wg.Done()
}(f)
}
wg.Wait()
close(done)
}()
for _, d := range flag.Args() {
walk(ch, d)
}
close(ch)
<-done
os.Exit(exitStatus)
}
func walk(ch chan<- *file, start string) {
filepath.Walk(start, func(path string, fi os.FileInfo, err error) error {
if err != nil {
log.Printf("%s error: %v", path, err)
return nil
}
if fi.IsDir() {
return nil
}
ch <- &file{path, fi.Mode()}
return nil
})
}
func addLicense(b []byte, path string, fmode os.FileMode) error {
var lic []byte
var err error
switch filepath.Ext(path) {
default:
return nil
case ".c", ".h":
lic, err = prefix("/*", " * ", " */")
case ".js", ".css":
lic, err = prefix("/**", " * ", " */")
case ".cc", ".cpp", ".cs", ".go", ".hh", ".hpp", ".java", ".m", ".mm", ".proto", ".rs", ".scala", ".swift", ".dart":
lic, err = prefix("", "// ", "")
case ".py", ".sh":
lic, err = prefix("", "# ", "")
case ".el", ".lisp":
lic, err = prefix("", ";; ", "")
case ".erl":
lic, err = prefix("", "% ", "")
case ".hs":
lic, err = prefix("", "-- ", "")
case ".html", ".xml":
lic, err = prefix("<!--", " ", "-->")
case ".php":
lic, err = prefix("<?php", "// ", "?>")
}
if err != nil || lic == nil {
return 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 ioutil.WriteFile(path, b, fmode)
}
func hashBang(b []byte) []byte {
var line = make([]byte, 0, len(b))
for _, c := range b {
line = append(line, c)
if c == '\n' {
break
}
}
if bytes.HasPrefix(line, []byte("#!")) {
return line
}
return nil
}
func hasLicense(b []byte) bool {
n := 100
if len(b) < 100 {
n = len(b)
}
return bytes.Contains(bytes.ToLower(b[:n]), []byte("copyright"))
}
// prefix will execute a license template and prefix the result with top, middle and bottom.
func prefix(top, mid, bot string) ([]byte, error) {
buf := bytes.NewBufferString(fmt.Sprintf("Copyright %d %s", time.Now().Year(), tmpl))
var out bytes.Buffer
if top != "" {
out.WriteString(top)
out.WriteRune('\n')
}
out.WriteString(mid)
for _, c := range buf.Bytes() {
out.WriteByte(c)
if c == '\n' {
out.WriteString(mid)
}
}
if bot != "" {
out.WriteRune('\n')
out.WriteString(bot)
}
out.Write([]byte{'\n', '\n'})
return out.Bytes(), nil
}