func parseFuncDefs(filename string) ([]string, error) {
	f, err := parser.ParseFile(fset, filename, nil, parser.AllErrors)
	if err != nil {
		return nil, err
	}
	var funcNames []string
	ast.Walk(visitFn(func(n ast.Node) bool {
		switch n := n.(type) {
		case *ast.CompositeLit:
			if isFuncMap(n.Type) {
				for _, e := range n.Elts {
					kv := e.(*ast.KeyValueExpr)
					name, err := strconv.Unquote(kv.Key.(*ast.BasicLit).Value)
					if err != nil {
						log.Fatal(err)
					}
					funcNames = append(funcNames, name)
				}
			}
		}
		return true
	}), f)
	return funcNames, nil
}
parseFuncDefs extracts and returns function names from a Go source
file containing a FuncMap literal.
package main
import (
	"flag"
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"log"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"text/template"
	"text/template/parse"
	"sourcegraph.com/sourcegraph/go-template-lint/tmplwalk"
)
var (
	verbose      = flag.Bool("v", false, "show verbose output")
	funcmapFile  = flag.String("f", "", "Go source file with FuncMap literal (commas separate multiple files)")
	tmplSetsFile = flag.String("t", "", "Go source file containing template set (a [][]string literal)")
	tmplDir      = flag.String("td", "", "base path of templates (prepended to template set filenames)")
	fset = token.NewFileSet()
)
func main() {
	log.SetFlags(0)
	flag.Parse()
	if *funcmapFile == "" {
		log.Fatal("-f is required (run with -h for usage info)")
	}
	if *tmplSetsFile == "" {
		log.Fatal("-t is required (run with -h for usage info)")
	}
	var allDefinedFuncs []string
	for _, f := range strings.Split(*funcmapFile, ",") {
		definedFuncs, err := parseFuncDefs(f)
		if err != nil {
			log.Fatalf("Error parsing FuncMap names from %s: %s", f, err)
		}
		if len(definedFuncs) == 0 {
			log.Fatalf("No func definitions in a FuncMap found in %s.", f)
		}
		allDefinedFuncs = append(allDefinedFuncs, definedFuncs...)
	}
	if *verbose {
		log.Printf("# Found %d template functions:", len(allDefinedFuncs))
		for _, name := range allDefinedFuncs {
			log.Printf("# - %s", name)
		}
	}
	tmplSets, err := parseTmplSet(*tmplSetsFile)
	if err != nil {
		log.Fatalf("Error parsing template sets (a [][]string literal) from %s: %s", *tmplSetsFile, err)
	}
	if len(tmplSets) == 0 {
		log.Fatal("No template files found.")
	}
	if *verbose {
		log.Printf("# Found %d template file sets:", len(tmplSets))
		for _, ts := range tmplSets {
			log.Printf("# - %s", strings.Join(ts, " "))
		}
	}
	invokedFuncs, err := findInvokedFuncs(tmplSets, allDefinedFuncs)
	if err != nil {
		log.Fatalf("Error parsing templates to find invoked template functions: %s", err)
	}
	if len(invokedFuncs) == 0 {
		log.Fatal("No func invocations in templates found.")
	}
	if *verbose {
		log.Printf("# Found %d functions invoked in templates:", len(invokedFuncs))
		for _, name := range invokedFuncs {
			log.Printf("# - %s", name)
		}
	}
	definedFuncMap := sliceToMap(allDefinedFuncs)
	invokedFuncMap := sliceToMap(invokedFuncs)
	fail := false
	unusedFuncs := subtract(definedFuncMap, invokedFuncMap)
	for _, f := range unusedFuncs {
		fmt.Println("unused template func", f)
		fail = true
	}
	addPredefs(definedFuncMap)
	undefinedFuncs := subtract(invokedFuncMap, definedFuncMap)
	for _, f := range undefinedFuncs {
		fmt.Println("undefined template func", f)
		fail = true
	}
	if fail {
		os.Exit(1)
	}
}