github.com/hairyhenderson/templater@v3.5.0+incompatible/gomplate.go (about) 1 // Package gomplate is a template renderer which supports a number of datasources, 2 // and includes hundreds of built-in functions. 3 package gomplate 4 5 import ( 6 "bytes" 7 "io" 8 "os" 9 "path" 10 "path/filepath" 11 "strings" 12 "text/template" 13 "time" 14 15 "github.com/hairyhenderson/gomplate/data" 16 "github.com/pkg/errors" 17 "github.com/spf13/afero" 18 ) 19 20 // gomplate - 21 type gomplate struct { 22 funcMap template.FuncMap 23 leftDelim string 24 rightDelim string 25 nestedTemplates templateAliases 26 rootTemplate *template.Template 27 context interface{} 28 } 29 30 // runTemplate - 31 func (g *gomplate) runTemplate(t *tplate) error { 32 tmpl, err := t.toGoTemplate(g) 33 if err != nil { 34 return err 35 } 36 37 // nolint: gocritic 38 switch t.target.(type) { 39 case io.Closer: 40 if t.target != os.Stdout { 41 // nolint: errcheck 42 defer t.target.(io.Closer).Close() 43 } 44 } 45 err = tmpl.Execute(t.target, g.context) 46 return err 47 } 48 49 type templateAliases map[string]string 50 51 // newGomplate - 52 func newGomplate(d *data.Data, leftDelim, rightDelim string, nested templateAliases, context interface{}) *gomplate { 53 return &gomplate{ 54 leftDelim: leftDelim, 55 rightDelim: rightDelim, 56 funcMap: Funcs(d), 57 nestedTemplates: nested, 58 context: context, 59 } 60 } 61 62 func parseTemplateArgs(templateArgs []string) (templateAliases, error) { 63 nested := templateAliases{} 64 for _, templateArg := range templateArgs { 65 err := parseTemplateArg(templateArg, nested) 66 if err != nil { 67 return nil, err 68 } 69 } 70 return nested, nil 71 } 72 73 func parseTemplateArg(templateArg string, ta templateAliases) error { 74 parts := strings.SplitN(templateArg, "=", 2) 75 pth := parts[0] 76 alias := "" 77 if len(parts) > 1 { 78 alias = parts[0] 79 pth = parts[1] 80 } 81 82 switch fi, err := fs.Stat(pth); { 83 case err != nil: 84 return err 85 case fi.IsDir(): 86 files, err := afero.ReadDir(fs, pth) 87 if err != nil { 88 return err 89 } 90 prefix := pth 91 if alias != "" { 92 prefix = alias 93 } 94 for _, f := range files { 95 if !f.IsDir() { // one-level only 96 ta[path.Join(prefix, f.Name())] = path.Join(pth, f.Name()) 97 } 98 } 99 default: 100 if alias != "" { 101 ta[alias] = pth 102 } else { 103 ta[pth] = pth 104 } 105 } 106 return nil 107 } 108 109 // RunTemplates - run all gomplate templates specified by the given configuration 110 func RunTemplates(o *Config) error { 111 Metrics = newMetrics() 112 defer runCleanupHooks() 113 // make sure config is sane 114 o.defaults() 115 ds := append(o.DataSources, o.Contexts...) 116 d, err := data.NewData(ds, o.DataSourceHeaders) 117 if err != nil { 118 return err 119 } 120 addCleanupHook(d.Cleanup) 121 nested, err := parseTemplateArgs(o.Templates) 122 if err != nil { 123 return err 124 } 125 c, err := createContext(o.Contexts, d) 126 if err != nil { 127 return err 128 } 129 g := newGomplate(d, o.LDelim, o.RDelim, nested, c) 130 131 return g.runTemplates(o) 132 } 133 134 func (g *gomplate) runTemplates(o *Config) error { 135 start := time.Now() 136 tmpl, err := gatherTemplates(o, chooseNamer(o, g)) 137 Metrics.GatherDuration = time.Since(start) 138 if err != nil { 139 Metrics.Errors++ 140 return err 141 } 142 Metrics.TemplatesGathered = len(tmpl) 143 start = time.Now() 144 defer func() { Metrics.TotalRenderDuration = time.Since(start) }() 145 for _, t := range tmpl { 146 tstart := time.Now() 147 err := g.runTemplate(t) 148 Metrics.RenderDuration[t.name] = time.Since(tstart) 149 if err != nil { 150 Metrics.Errors++ 151 return err 152 } 153 Metrics.TemplatesProcessed++ 154 } 155 return nil 156 } 157 158 func chooseNamer(o *Config, g *gomplate) func(string) (string, error) { 159 if o.OutputMap == "" { 160 return simpleNamer(o.OutputDir) 161 } 162 return mappingNamer(o.OutputMap, g) 163 } 164 165 func simpleNamer(outDir string) func(inPath string) (string, error) { 166 return func(inPath string) (string, error) { 167 outPath := filepath.Join(outDir, inPath) 168 return filepath.Clean(outPath), nil 169 } 170 } 171 172 func mappingNamer(outMap string, g *gomplate) func(string) (string, error) { 173 return func(inPath string) (string, error) { 174 out := &bytes.Buffer{} 175 t := &tplate{ 176 name: "<OutputMap>", 177 contents: outMap, 178 target: out, 179 } 180 tpl, err := t.toGoTemplate(g) 181 if err != nil { 182 return "", err 183 } 184 ctx := &context{} 185 // nolint: gocritic 186 switch c := g.context.(type) { 187 case *context: 188 for k, v := range *c { 189 if k != "in" && k != "ctx" { 190 (*ctx)[k] = v 191 } 192 } 193 } 194 (*ctx)["ctx"] = g.context 195 (*ctx)["in"] = inPath 196 197 err = tpl.Execute(t.target, ctx) 198 if err != nil { 199 return "", errors.Wrapf(err, "failed to render outputMap with ctx %+v and inPath %s", ctx, inPath) 200 } 201 202 return filepath.Clean(strings.TrimSpace(out.String())), nil 203 } 204 }