github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/dev/envvardoc/envvardoc.go (about) 1 // Program envvardoc will verify all referenced environment variables in go 2 // source are properly documented. 3 package main 4 5 import ( 6 "bufio" 7 "flag" 8 "fmt" 9 "io" 10 "log" 11 "os" 12 "path/filepath" 13 "regexp" 14 "sort" 15 "strings" 16 "text/tabwriter" 17 ) 18 19 var ( 20 srcDirs = flag.String("srcDirs", "cmd,dev,pkg,server", 21 "comma separated source directories") 22 doc = flag.String("doc", "doc/environment-vars.txt", 23 "file containing environment variable documentation") 24 all = flag.Bool("all", false, "show all environment vars found") 25 prefixes = flag.String("prefixes", "CAM,DEV,AWS", 26 "comma-separated list of env var prefixes we care about. Empty implies all") 27 28 docVar = regexp.MustCompile(`^(\w+) \(.+?\):$`) 29 literalEnvVar = regexp.MustCompile(`os.Getenv\("(\w+)"\)`) 30 variableEnvVar = regexp.MustCompile(`os.Getenv\((\w+)\)`) 31 ) 32 33 type pos struct { 34 line int 35 path string 36 } 37 38 func (p pos) String() string { 39 return fmt.Sprintf("%s:%d", p.path, p.line) 40 } 41 42 type varMap map[string][]pos 43 44 func sortedKeys(m varMap) []string { 45 keys := make([]string, 0, len(m)) 46 for k, _ := range m { 47 keys = append(keys, k) 48 } 49 sort.Strings(keys) 50 return keys 51 } 52 53 type envCollector struct { 54 literals varMap 55 variables varMap 56 documented map[string]struct{} 57 } 58 59 func newEncCollector() *envCollector { 60 return &envCollector{ 61 literals: varMap{}, 62 variables: varMap{}, 63 documented: map[string]struct{}{}, 64 } 65 } 66 67 func (ec *envCollector) findEnvVars(path string, r io.Reader) error { 68 scanner := bufio.NewScanner(r) 69 line := 1 70 for scanner.Scan() { 71 l := scanner.Text() 72 m := literalEnvVar.FindStringSubmatch(l) 73 if len(m) == 2 { 74 p := pos{line: line, path: path} 75 ec.literals[m[1]] = append(ec.literals[m[1]], p) 76 } 77 78 m = variableEnvVar.FindStringSubmatch(l) 79 if len(m) == 2 { 80 p := pos{line: line, path: path} 81 ec.variables[m[1]] = append(ec.variables[m[1]], p) 82 } 83 line++ 84 } 85 return scanner.Err() 86 } 87 88 func (ec *envCollector) findDocVars(r io.Reader) error { 89 scanner := bufio.NewScanner(r) 90 for scanner.Scan() { 91 l := scanner.Text() 92 m := docVar.FindStringSubmatch(l) 93 if len(m) == 2 { 94 ec.documented[m[1]] = struct{}{} 95 } 96 } 97 return scanner.Err() 98 } 99 100 func (ec *envCollector) walk(path string, info os.FileInfo, err error) error { 101 if err != nil { 102 return err 103 } 104 105 if info.IsDir() || !strings.HasSuffix(path, ".go") { 106 return nil 107 } 108 109 r, err := os.Open(path) 110 if err != nil { 111 return err 112 } 113 defer r.Close() 114 return ec.findEnvVars(path, r) 115 } 116 117 func printMap(header string, m varMap) { 118 w := new(tabwriter.Writer) 119 w.Init(os.Stdout, 0, 8, 1, ' ', 0) 120 fmt.Fprintln(w, header) 121 for _, k := range sortedKeys(m) { 122 for _, pos := range m[k] { 123 fmt.Fprintf(w, "%s\t%s\n", k, pos) 124 } 125 } 126 w.Flush() 127 } 128 129 func (ec *envCollector) printAll() { 130 fmt.Println("All environment variables") 131 printMap("Literal\tLocation", ec.literals) 132 fmt.Println() 133 printMap("Variable\tLocation", ec.variables) 134 } 135 136 func (ec *envCollector) printUndocumented(prefixes []string) bool { 137 missing := varMap{} 138 for k, v := range ec.literals { 139 if _, ok := ec.documented[k]; !ok { 140 keep := false 141 for _, p := range prefixes { 142 if strings.HasPrefix(k, p) { 143 keep = true 144 break 145 } 146 } 147 if keep || len(prefixes) == 0 { 148 missing[k] = v 149 } 150 } 151 } 152 153 if len(missing) != 0 { 154 printMap("Undocumented\tLocation", missing) 155 } else { 156 fmt.Println("All environment variables are documented") 157 } 158 return len(missing) != 0 159 } 160 161 func main() { 162 flag.Parse() 163 ec := newEncCollector() 164 165 r, err := os.Open(*doc) 166 if err != nil { 167 log.Fatal(err) 168 } 169 defer r.Close() 170 err = ec.findDocVars(r) 171 if err != nil { 172 log.Fatal(err) 173 } 174 175 for _, dn := range strings.Split(*srcDirs, ",") { 176 err := filepath.Walk(dn, ec.walk) 177 if err != nil { 178 log.Fatal(err) 179 } 180 } 181 182 if *all { 183 ec.printAll() 184 } else { 185 if ec.printUndocumented(strings.Split(*prefixes, ",")) { 186 os.Exit(1) 187 } 188 } 189 }