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 }