perkeep/dev/envvardoc/envvardoc.go

190 lines
3.8 KiB
Go
Raw Normal View History

// Program envvardoc will verify all referenced environment variables in go
// source are properly documented.
package main
import (
"bufio"
"flag"
"fmt"
"io"
"log"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"text/tabwriter"
)
var (
srcDirs = flag.String("srcDirs", "cmd,dev,pkg,server",
"comma separated source directories")
doc = flag.String("doc", "doc/environment-vars.txt",
"file containing environment variable documentation")
all = flag.Bool("all", false, "show all environment vars found")
prefixes = flag.String("prefixes", "CAM,DEV,AWS",
"comma-separated list of env var prefixes we care about. Empty implies all")
Document environment variables usage. Running 'go run dev/envvardoc/envvardoc.go' now shows: 'All environment variables are documented' I also took the liberty of cleaning-up our mishmash of logic for handling boolean environment variables, and cleaned up a couple other spots that didn't seem right. This change adds docmentation for all variables starting with (CAM|DEV|AWS). This leaves some variables still undocumented. If there are variables worth documenting in the following list, maybe we should rename them to have a CAM{LI} prefix for consistency's sake: APPDATA pkg/osutil/paths.go:86 APPDATA pkg/osutil/paths.go:102 DISPLAY pkg/misc/gpgagent/gpgagent.go:126 GOPATH pkg/fileembed/genfileembed/genfileembed.go:321 GOPATH pkg/osutil/paths.go:168 GOPATH pkg/test/world.go:54 GOPATH server/appengine/build_test.go:77 GPGKEY cmd/camput/init.go:77 GPG_AGENT_INFO cmd/camput/init.go:153 GPG_AGENT_INFO pkg/misc/gpgagent/gpgagent.go:50 HOME pkg/jsonsign/keys.go:79 HOME pkg/jsonsign/signhandler/sig.go:64 HOME pkg/osutil/paths.go:36 HOMEPATH pkg/osutil/paths.go:34 PKG_CONFIG_PATH pkg/index/sqlite/dbschema.go:59 RUN_BROKEN_TESTS pkg/fs/fs_test.go:67 SKIP_DEP_TESTS pkg/test/testdep.go:29 TERM pkg/misc/gpgagent/gpgagent.go:133 TERM pkg/misc/pinentry/pinentry.go:99 TESTING_PORT_WRITE_FD pkg/webserver/webserver.go:135 TEST_GPGAGENT_LIB pkg/misc/gpgagent/gpgagent_test.go:27 USER pkg/netutil/ident.go:135 USER pkg/osutil/paths.go:45 USERNAME pkg/jsonconfig/eval.go:228 USERNAME pkg/osutil/paths.go:43 VERBOSE_FUSE pkg/fs/fs_test.go:133 VERBOSE_FUSE_STDERR pkg/fs/fs_test.go:137 XDG_CONFIG_HOME pkg/osutil/paths.go:104 Change-Id: Ief28710d3deefd1e65247cb5d3b1d8dde73e1f2d
2013-09-06 04:07:15 +00:00
docVar = regexp.MustCompile(`^(\w+) \(.+?\):$`)
literalEnvVar = regexp.MustCompile(`os.Getenv\("(\w+)"\)`)
variableEnvVar = regexp.MustCompile(`os.Getenv\((\w+)\)`)
)
type pos struct {
line int
path string
}
func (p pos) String() string {
return fmt.Sprintf("%s:%d", p.path, p.line)
}
type varMap map[string][]pos
func sortedKeys(m varMap) []string {
keys := make([]string, 0, len(m))
for k, _ := range m {
keys = append(keys, k)
}
sort.Strings(keys)
return keys
}
type envCollector struct {
literals varMap
variables varMap
documented map[string]struct{}
}
func newEncCollector() *envCollector {
return &envCollector{
literals: varMap{},
variables: varMap{},
documented: map[string]struct{}{},
}
}
func (ec *envCollector) findEnvVars(path string, r io.Reader) error {
scanner := bufio.NewScanner(r)
line := 1
for scanner.Scan() {
l := scanner.Text()
m := literalEnvVar.FindStringSubmatch(l)
if len(m) == 2 {
p := pos{line: line, path: path}
ec.literals[m[1]] = append(ec.literals[m[1]], p)
}
m = variableEnvVar.FindStringSubmatch(l)
if len(m) == 2 {
p := pos{line: line, path: path}
ec.variables[m[1]] = append(ec.variables[m[1]], p)
}
line++
}
return scanner.Err()
}
func (ec *envCollector) findDocVars(r io.Reader) error {
scanner := bufio.NewScanner(r)
for scanner.Scan() {
l := scanner.Text()
m := docVar.FindStringSubmatch(l)
if len(m) == 2 {
ec.documented[m[1]] = struct{}{}
}
}
return scanner.Err()
}
func (ec *envCollector) walk(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() || !strings.HasSuffix(path, ".go") {
return nil
}
r, err := os.Open(path)
if err != nil {
return err
}
defer r.Close()
return ec.findEnvVars(path, r)
}
func printMap(header string, m varMap) {
w := new(tabwriter.Writer)
w.Init(os.Stdout, 0, 8, 1, ' ', 0)
fmt.Fprintln(w, header)
for _, k := range sortedKeys(m) {
for _, pos := range m[k] {
fmt.Fprintf(w, "%s\t%s\n", k, pos)
}
}
w.Flush()
}
func (ec *envCollector) printAll() {
Document environment variables usage. Running 'go run dev/envvardoc/envvardoc.go' now shows: 'All environment variables are documented' I also took the liberty of cleaning-up our mishmash of logic for handling boolean environment variables, and cleaned up a couple other spots that didn't seem right. This change adds docmentation for all variables starting with (CAM|DEV|AWS). This leaves some variables still undocumented. If there are variables worth documenting in the following list, maybe we should rename them to have a CAM{LI} prefix for consistency's sake: APPDATA pkg/osutil/paths.go:86 APPDATA pkg/osutil/paths.go:102 DISPLAY pkg/misc/gpgagent/gpgagent.go:126 GOPATH pkg/fileembed/genfileembed/genfileembed.go:321 GOPATH pkg/osutil/paths.go:168 GOPATH pkg/test/world.go:54 GOPATH server/appengine/build_test.go:77 GPGKEY cmd/camput/init.go:77 GPG_AGENT_INFO cmd/camput/init.go:153 GPG_AGENT_INFO pkg/misc/gpgagent/gpgagent.go:50 HOME pkg/jsonsign/keys.go:79 HOME pkg/jsonsign/signhandler/sig.go:64 HOME pkg/osutil/paths.go:36 HOMEPATH pkg/osutil/paths.go:34 PKG_CONFIG_PATH pkg/index/sqlite/dbschema.go:59 RUN_BROKEN_TESTS pkg/fs/fs_test.go:67 SKIP_DEP_TESTS pkg/test/testdep.go:29 TERM pkg/misc/gpgagent/gpgagent.go:133 TERM pkg/misc/pinentry/pinentry.go:99 TESTING_PORT_WRITE_FD pkg/webserver/webserver.go:135 TEST_GPGAGENT_LIB pkg/misc/gpgagent/gpgagent_test.go:27 USER pkg/netutil/ident.go:135 USER pkg/osutil/paths.go:45 USERNAME pkg/jsonconfig/eval.go:228 USERNAME pkg/osutil/paths.go:43 VERBOSE_FUSE pkg/fs/fs_test.go:133 VERBOSE_FUSE_STDERR pkg/fs/fs_test.go:137 XDG_CONFIG_HOME pkg/osutil/paths.go:104 Change-Id: Ief28710d3deefd1e65247cb5d3b1d8dde73e1f2d
2013-09-06 04:07:15 +00:00
fmt.Println("All environment variables")
printMap("Literal\tLocation", ec.literals)
fmt.Println()
printMap("Variable\tLocation", ec.variables)
}
func (ec *envCollector) printUndocumented(prefixes []string) bool {
missing := varMap{}
for k, v := range ec.literals {
if _, ok := ec.documented[k]; !ok {
keep := false
for _, p := range prefixes {
if strings.HasPrefix(k, p) {
keep = true
break
}
}
if keep || len(prefixes) == 0 {
missing[k] = v
}
}
}
Document environment variables usage. Running 'go run dev/envvardoc/envvardoc.go' now shows: 'All environment variables are documented' I also took the liberty of cleaning-up our mishmash of logic for handling boolean environment variables, and cleaned up a couple other spots that didn't seem right. This change adds docmentation for all variables starting with (CAM|DEV|AWS). This leaves some variables still undocumented. If there are variables worth documenting in the following list, maybe we should rename them to have a CAM{LI} prefix for consistency's sake: APPDATA pkg/osutil/paths.go:86 APPDATA pkg/osutil/paths.go:102 DISPLAY pkg/misc/gpgagent/gpgagent.go:126 GOPATH pkg/fileembed/genfileembed/genfileembed.go:321 GOPATH pkg/osutil/paths.go:168 GOPATH pkg/test/world.go:54 GOPATH server/appengine/build_test.go:77 GPGKEY cmd/camput/init.go:77 GPG_AGENT_INFO cmd/camput/init.go:153 GPG_AGENT_INFO pkg/misc/gpgagent/gpgagent.go:50 HOME pkg/jsonsign/keys.go:79 HOME pkg/jsonsign/signhandler/sig.go:64 HOME pkg/osutil/paths.go:36 HOMEPATH pkg/osutil/paths.go:34 PKG_CONFIG_PATH pkg/index/sqlite/dbschema.go:59 RUN_BROKEN_TESTS pkg/fs/fs_test.go:67 SKIP_DEP_TESTS pkg/test/testdep.go:29 TERM pkg/misc/gpgagent/gpgagent.go:133 TERM pkg/misc/pinentry/pinentry.go:99 TESTING_PORT_WRITE_FD pkg/webserver/webserver.go:135 TEST_GPGAGENT_LIB pkg/misc/gpgagent/gpgagent_test.go:27 USER pkg/netutil/ident.go:135 USER pkg/osutil/paths.go:45 USERNAME pkg/jsonconfig/eval.go:228 USERNAME pkg/osutil/paths.go:43 VERBOSE_FUSE pkg/fs/fs_test.go:133 VERBOSE_FUSE_STDERR pkg/fs/fs_test.go:137 XDG_CONFIG_HOME pkg/osutil/paths.go:104 Change-Id: Ief28710d3deefd1e65247cb5d3b1d8dde73e1f2d
2013-09-06 04:07:15 +00:00
if len(missing) != 0 {
printMap("Undocumented\tLocation", missing)
} else {
fmt.Println("All environment variables are documented")
}
return len(missing) != 0
}
func main() {
flag.Parse()
ec := newEncCollector()
r, err := os.Open(*doc)
if err != nil {
log.Fatal(err)
}
defer r.Close()
err = ec.findDocVars(r)
if err != nil {
log.Fatal(err)
}
for _, dn := range strings.Split(*srcDirs, ",") {
err := filepath.Walk(dn, ec.walk)
if err != nil {
log.Fatal(err)
}
}
if *all {
ec.printAll()
} else {
if ec.printUndocumented(strings.Split(*prefixes, ",")) {
os.Exit(1)
}
}
}