bou.ke/statictemplate@v0.0.0-20180821122055-510411a5e7dd/main.go (about)

     1  package main // import "bou.ke/statictemplate"
     2  
     3  import (
     4  	"bytes"
     5  	"flag"
     6  	"fmt"
     7  	"go/format"
     8  	"go/types"
     9  	"io/ioutil"
    10  	"log"
    11  	"os"
    12  	"path/filepath"
    13  	"regexp"
    14  
    15  	htmlTemplate "html/template"
    16  	textTemplate "text/template"
    17  
    18  	"bou.ke/statictemplate/internal"
    19  	"bou.ke/statictemplate/statictemplate"
    20  	"golang.org/x/tools/go/loader"
    21  )
    22  
    23  type dotType struct {
    24  	packagePath string
    25  	typeName    string
    26  	prefix      string
    27  }
    28  
    29  type compilationTarget struct {
    30  	functionName string
    31  	templateName string
    32  	dot          dotType
    33  }
    34  
    35  type compilationTargets []compilationTarget
    36  
    37  func (c *compilationTargets) String() string {
    38  	return ""
    39  }
    40  
    41  var typeNameRe = regexp.MustCompile(`^([^:]+):([^:]+):([\*\[\]]*)(?:(.+)\.)?([A-Za-z][A-Za-z0-9]*)$`)
    42  
    43  func (c *compilationTargets) Set(value string) error {
    44  	values := typeNameRe.FindStringSubmatch(value)
    45  	if values == nil {
    46  		return fmt.Errorf("expect compilation target in functionName:templateName:typeName format, got %q", value)
    47  	}
    48  	*c = append(*c, compilationTarget{values[1], values[2], dotType{
    49  		prefix:      values[3],
    50  		packagePath: values[4],
    51  		typeName:    values[5],
    52  	}})
    53  	return nil
    54  }
    55  
    56  func (c compilationTargets) ToInstructions() (ins []statictemplate.TranslateInstruction, err error) {
    57  	var conf loader.Config
    58  	conf.Import("runtime")
    59  	for _, t := range c {
    60  		if p := t.dot.packagePath; p != "" {
    61  			conf.Import(p)
    62  		}
    63  	}
    64  	var prog *loader.Program
    65  	prog, err = conf.Load()
    66  	if err != nil {
    67  		return
    68  	}
    69  	for _, t := range c {
    70  		var pack *types.Package
    71  		if t.dot.packagePath != "" {
    72  			pack = prog.Package(t.dot.packagePath).Pkg
    73  		}
    74  		typVal, err := types.Eval(conf.Fset, pack, 0, t.dot.prefix+t.dot.typeName)
    75  		if err != nil {
    76  			return nil, err
    77  		}
    78  		ins = append(ins, statictemplate.TranslateInstruction{
    79  			FunctionName: t.functionName,
    80  			TemplateName: t.templateName,
    81  			Dot:          typVal.Type,
    82  		})
    83  	}
    84  	return
    85  }
    86  
    87  var (
    88  	targets       compilationTargets
    89  	packageName   string
    90  	outputFile    string
    91  	devOutputFile string
    92  	glob          string
    93  	html          bool
    94  	funcMap       string
    95  )
    96  
    97  func init() {
    98  	flag.Var(&targets, "t", "Target to process, supports multiple. The format is <function name>:<template name>:<type of the template argument>")
    99  	flag.StringVar(&packageName, "package", "", "Name of the package of the result file. Defaults to name of the folder of the output file")
   100  	flag.StringVar(&outputFile, "o", "template.go", "Name of the output file")
   101  	flag.StringVar(&devOutputFile, "dev", "", "Name of the dev output file")
   102  	flag.BoolVar(&html, "html", false, "Interpret templates as HTML, to enable Go's automatic HTML escaping")
   103  	flag.StringVar(&funcMap, "funcs", "", "A reference to a custom Funcs map to include")
   104  }
   105  
   106  func parse(html bool, funcs map[string]*types.Func, files ...string) (interface{}, error) {
   107  	var dummyFuncs map[string]interface{}
   108  	if funcs != nil {
   109  		dummyFuncs = make(map[string]interface{})
   110  		for key := range funcs {
   111  			dummyFuncs[key] = func() string {
   112  				return ""
   113  			}
   114  		}
   115  	}
   116  	if html {
   117  		t := htmlTemplate.New("")
   118  		if dummyFuncs != nil {
   119  			t.Funcs(dummyFuncs)
   120  		}
   121  		return t.ParseFiles(files...)
   122  	} else {
   123  		t := textTemplate.New("")
   124  		if dummyFuncs != nil {
   125  			t.Funcs(dummyFuncs)
   126  		}
   127  		return t.ParseFiles(files...)
   128  	}
   129  }
   130  
   131  func main() {
   132  	flag.Parse()
   133  	if len(targets) == 0 || flag.NArg() < 1 {
   134  		flag.Usage()
   135  		os.Exit(2)
   136  	}
   137  
   138  	if err := work(); err != nil {
   139  		log.Fatal(err)
   140  	}
   141  }
   142  
   143  func work() error {
   144  	if packageName == "" {
   145  		absOutputFile, err := filepath.Abs(outputFile)
   146  		if err != nil {
   147  			return err
   148  		}
   149  		packageName = filepath.Base(filepath.Dir(absOutputFile))
   150  	}
   151  
   152  	var templateFiles []string
   153  	for i := 0; i < flag.NArg(); i++ {
   154  		matches, err := filepath.Glob(flag.Arg(i))
   155  		if err != nil {
   156  			return err
   157  		}
   158  		templateFiles = append(templateFiles, matches...)
   159  	}
   160  	if len(templateFiles) == 0 {
   161  		log.Fatal("no files found matching glob")
   162  	}
   163  
   164  	funcMapImport, funcMapName, funcs, err := internal.ImportFuncMap(funcMap)
   165  	if err != nil {
   166  		return err
   167  	}
   168  
   169  	var buf bytes.Buffer
   170  	if devOutputFile != "" {
   171  		buf.WriteString("// +build !dev\n\n")
   172  	}
   173  
   174  	template, err := parse(html, funcs, templateFiles...)
   175  	if err != nil {
   176  		return err
   177  	}
   178  
   179  	translator := statictemplate.New(template)
   180  	translator.Funcs = funcs
   181  	ins, err := targets.ToInstructions()
   182  	if err != nil {
   183  		return err
   184  	}
   185  	byts, err := translator.Translate(packageName, ins)
   186  	if err != nil {
   187  		return err
   188  	}
   189  	buf.Write(byts)
   190  
   191  	src, err := format.Source(buf.Bytes())
   192  	if err != nil {
   193  		return err
   194  	}
   195  
   196  	if err := os.MkdirAll(filepath.Dir(outputFile), 0755); err != nil {
   197  		return err
   198  	}
   199  
   200  	file, err := os.Create(outputFile)
   201  	if err != nil {
   202  		return err
   203  	}
   204  
   205  	if _, err = file.Write(src); err != nil {
   206  		return err
   207  	}
   208  	file.Close()
   209  
   210  	if devOutputFile != "" {
   211  		buf.Reset()
   212  		if err = writeDevTemplate(&buf, targets, templateFiles, html, funcMapImport, funcMapName, packageName); err != nil {
   213  			return err
   214  		}
   215  		src, err := format.Source(buf.Bytes())
   216  		if err != nil {
   217  			return err
   218  		}
   219  
   220  		if contents, err := ioutil.ReadFile(devOutputFile); err != nil || !bytes.Equal(contents, src) {
   221  			if err := os.MkdirAll(filepath.Dir(devOutputFile), 0755); err != nil {
   222  				return err
   223  			}
   224  			file, err := os.Create(devOutputFile)
   225  			if err != nil {
   226  				return err
   227  			}
   228  			if _, err = file.Write(src); err != nil {
   229  				return err
   230  			}
   231  			file.Close()
   232  		}
   233  	}
   234  	return nil
   235  }