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  }