github.com/wuhuizuo/gomplate@v3.5.0+incompatible/template.go (about) 1 package gomplate 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 "text/template" 11 12 "github.com/hairyhenderson/gomplate/tmpl" 13 14 "github.com/hairyhenderson/gomplate/conv" 15 "github.com/hairyhenderson/gomplate/env" 16 "github.com/pkg/errors" 17 18 "github.com/spf13/afero" 19 "github.com/zealic/xignore" 20 ) 21 22 // ignorefile name, like .gitignore 23 const gomplateignore = ".gomplateignore" 24 25 // for overriding in tests 26 var stdin io.ReadCloser = os.Stdin 27 var fs = afero.NewOsFs() 28 29 // Stdout allows overriding the writer to use when templates are written to stdout ("-"). 30 var Stdout io.WriteCloser = os.Stdout 31 32 // tplate - models a gomplate template file... 33 type tplate struct { 34 name string 35 targetPath string 36 target io.Writer 37 contents string 38 mode os.FileMode 39 modeOverride bool 40 } 41 42 func addTmplFuncs(f template.FuncMap, root *template.Template, ctx interface{}) { 43 t := tmpl.New(root, ctx) 44 tns := func() *tmpl.Template { return t } 45 f["tmpl"] = tns 46 f["tpl"] = t.Inline 47 } 48 49 func (t *tplate) toGoTemplate(g *gomplate) (tmpl *template.Template, err error) { 50 if g.rootTemplate != nil { 51 tmpl = g.rootTemplate.New(t.name) 52 } else { 53 tmpl = template.New(t.name) 54 g.rootTemplate = tmpl 55 } 56 tmpl.Option("missingkey=error") 57 // the "tmpl" funcs get added here because they need access to the root template and context 58 addTmplFuncs(g.funcMap, g.rootTemplate, g.context) 59 tmpl.Funcs(g.funcMap) 60 tmpl.Delims(g.leftDelim, g.rightDelim) 61 _, err = tmpl.Parse(t.contents) 62 if err != nil { 63 return nil, err 64 } 65 for alias, path := range g.nestedTemplates { 66 // nolint: gosec 67 b, err := ioutil.ReadFile(path) 68 if err != nil { 69 return nil, err 70 } 71 _, err = tmpl.New(alias).Parse(string(b)) 72 if err != nil { 73 return nil, err 74 } 75 } 76 return tmpl, nil 77 } 78 79 // loadContents - reads the template in _once_ if it hasn't yet been read. Uses the name! 80 func (t *tplate) loadContents() (err error) { 81 if t.contents == "" { 82 t.contents, err = readInput(t.name) 83 } 84 return err 85 } 86 87 func (t *tplate) addTarget() (err error) { 88 if t.name == "<arg>" && t.targetPath == "" { 89 t.targetPath = "-" 90 } 91 if t.target == nil { 92 t.target, err = openOutFile(t.targetPath, t.mode, t.modeOverride) 93 } 94 return err 95 } 96 97 // gatherTemplates - gather and prepare input template(s) and output file(s) for rendering 98 // nolint: gocyclo 99 func gatherTemplates(o *Config, outFileNamer func(string) (string, error)) (templates []*tplate, err error) { 100 o.defaults() 101 mode, modeOverride, err := o.getMode() 102 if err != nil { 103 return nil, err 104 } 105 106 switch { 107 // the arg-provided input string gets a special name 108 case o.Input != "": 109 templates = []*tplate{{ 110 name: "<arg>", 111 contents: o.Input, 112 mode: mode, 113 modeOverride: modeOverride, 114 targetPath: o.OutputFiles[0], 115 }} 116 case o.InputDir != "": 117 // input dirs presume output dirs are set too 118 templates, err = walkDir(o.InputDir, outFileNamer, o.ExcludeGlob, mode, modeOverride) 119 if err != nil { 120 return nil, err 121 } 122 case o.Input == "": 123 templates = make([]*tplate, len(o.InputFiles)) 124 for i := range o.InputFiles { 125 templates[i], err = fileToTemplates(o.InputFiles[i], o.OutputFiles[i], mode, modeOverride) 126 if err != nil { 127 return nil, err 128 } 129 } 130 } 131 132 return processTemplates(templates) 133 } 134 135 func processTemplates(templates []*tplate) ([]*tplate, error) { 136 for _, t := range templates { 137 if err := t.loadContents(); err != nil { 138 return nil, err 139 } 140 141 if err := t.addTarget(); err != nil { 142 return nil, err 143 } 144 } 145 146 return templates, nil 147 } 148 149 // walkDir - given an input dir `dir` and an output dir `outDir`, and a list 150 // of .gomplateignore and exclude globs (if any), walk the input directory and create a list of 151 // tplate objects, and an error, if any. 152 func walkDir(dir string, outFileNamer func(string) (string, error), excludeGlob []string, mode os.FileMode, modeOverride bool) ([]*tplate, error) { 153 dir = filepath.Clean(dir) 154 155 dirStat, err := fs.Stat(dir) 156 if err != nil { 157 return nil, err 158 } 159 dirMode := dirStat.Mode() 160 161 templates := make([]*tplate, 0) 162 matcher := xignore.NewMatcher(fs) 163 matches, err := matcher.Matches(dir, &xignore.MatchesOptions{ 164 Ignorefile: gomplateignore, 165 Nested: true, // allow nested ignorefile 166 AfterPatterns: excludeGlob, 167 }) 168 if err != nil { 169 return nil, err 170 } 171 172 // Unmatched ignorefile rules's files 173 files := matches.UnmatchedFiles 174 for _, file := range files { 175 nextInPath := filepath.Join(dir, file) 176 nextOutPath, err := outFileNamer(file) 177 if err != nil { 178 return nil, err 179 } 180 181 fMode := mode 182 if mode == 0 { 183 stat, perr := fs.Stat(nextInPath) 184 if perr == nil { 185 fMode = stat.Mode() 186 } else { 187 fMode = dirMode 188 } 189 } 190 191 // Ensure file parent dirs 192 if err = fs.MkdirAll(filepath.Dir(nextOutPath), dirMode); err != nil { 193 return nil, err 194 } 195 196 templates = append(templates, &tplate{ 197 name: nextInPath, 198 targetPath: nextOutPath, 199 mode: fMode, 200 modeOverride: modeOverride, 201 }) 202 } 203 204 return templates, nil 205 } 206 207 func fileToTemplates(inFile, outFile string, mode os.FileMode, modeOverride bool) (*tplate, error) { 208 if inFile != "-" { 209 si, err := fs.Stat(inFile) 210 if err != nil { 211 return nil, err 212 } 213 if mode == 0 { 214 mode = si.Mode() 215 } 216 } 217 tmpl := &tplate{ 218 name: inFile, 219 targetPath: outFile, 220 mode: mode, 221 modeOverride: modeOverride, 222 } 223 224 return tmpl, nil 225 } 226 227 func openOutFile(filename string, mode os.FileMode, modeOverride bool) (out io.WriteCloser, err error) { 228 if conv.ToBool(env.Getenv("GOMPLATE_SUPPRESS_EMPTY", "false")) { 229 out = newEmptySkipper(func() (io.WriteCloser, error) { 230 if filename == "-" { 231 return Stdout, nil 232 } 233 return createOutFile(filename, mode, modeOverride) 234 }) 235 return out, nil 236 } 237 238 if filename == "-" { 239 return Stdout, nil 240 } 241 return createOutFile(filename, mode, modeOverride) 242 } 243 244 func createOutFile(filename string, mode os.FileMode, modeOverride bool) (out io.WriteCloser, err error) { 245 out, err = fs.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode.Perm()) 246 if err != nil { 247 return out, err 248 } 249 if modeOverride { 250 err = fs.Chmod(filename, mode.Perm()) 251 } 252 return out, err 253 } 254 255 func readInput(filename string) (string, error) { 256 var err error 257 var inFile io.ReadCloser 258 if filename == "-" { 259 inFile = stdin 260 } else { 261 inFile, err = fs.OpenFile(filename, os.O_RDONLY, 0) 262 if err != nil { 263 return "", fmt.Errorf("failed to open %s\n%v", filename, err) 264 } 265 // nolint: errcheck 266 defer inFile.Close() 267 } 268 bytes, err := ioutil.ReadAll(inFile) 269 if err != nil { 270 err = fmt.Errorf("read failed for %s\n%v", filename, err) 271 return "", err 272 } 273 return string(bytes), nil 274 } 275 276 // emptySkipper is a io.WriteCloser wrapper that will only start writing once a 277 // non-whitespace byte has been encountered. The writer must be provided by the 278 // `open` func 279 type emptySkipper struct { 280 open func() (io.WriteCloser, error) 281 282 // internal 283 w io.WriteCloser 284 buf *bytes.Buffer 285 nw bool 286 } 287 288 func newEmptySkipper(open func() (io.WriteCloser, error)) *emptySkipper { 289 return &emptySkipper{ 290 w: nil, 291 buf: &bytes.Buffer{}, 292 nw: false, 293 open: open, 294 } 295 } 296 297 func (f *emptySkipper) Write(p []byte) (n int, err error) { 298 if !f.nw { 299 if allWhitespace(p) { 300 // buffer the whitespace 301 return f.buf.Write(p) 302 } 303 304 // first time around, so open the writer 305 f.nw = true 306 f.w, err = f.open() 307 if err != nil { 308 return 0, err 309 } 310 if f.w == nil { 311 return 0, errors.New("nil writer returned by open") 312 } 313 // empty the buffer into the wrapped writer 314 _, err = f.buf.WriteTo(f.w) 315 if err != nil { 316 return 0, err 317 } 318 } 319 320 return f.w.Write(p) 321 } 322 323 func (f *emptySkipper) Close() error { 324 if f.w != nil { 325 return f.w.Close() 326 } 327 return nil 328 } 329 330 func allWhitespace(p []byte) bool { 331 for _, b := range p { 332 if b == ' ' || b == '\t' || b == '\n' || b == '\r' || b == '\v' { 333 continue 334 } 335 return false 336 } 337 return true 338 }