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  }