github.com/hairyhenderson/gomplate/v3@v3.11.7/render.go (about)

     1  package gomplate
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"net/url"
     9  	"os"
    10  	"text/template"
    11  	"time"
    12  
    13  	"github.com/hairyhenderson/gomplate/v3/data"
    14  	"github.com/hairyhenderson/gomplate/v3/funcs" //nolint:staticcheck
    15  	"github.com/hairyhenderson/gomplate/v3/internal/config"
    16  )
    17  
    18  // Options for template rendering.
    19  //
    20  // Experimental: subject to breaking changes before the next major release
    21  type Options struct {
    22  	// Datasources - map of datasources to be read on demand when the
    23  	// 'datasource'/'ds'/'include' functions are used.
    24  	Datasources map[string]Datasource
    25  	// Context - map of datasources to be read immediately and added to the
    26  	// template's context
    27  	Context map[string]Datasource
    28  	// Templates - map of templates that can be referenced as nested templates
    29  	Templates map[string]Datasource
    30  
    31  	// Extra HTTP headers not attached to pre-defined datsources. Potentially
    32  	// used by datasources defined in the template.
    33  	ExtraHeaders map[string]http.Header
    34  
    35  	// Funcs - map of functions to be added to the default template functions.
    36  	// Duplicate functions will be overwritten by entries in this map.
    37  	Funcs template.FuncMap
    38  
    39  	// LeftDelim - set the left action delimiter for the template and all nested
    40  	// templates to the specified string. Defaults to "{{"
    41  	LDelim string
    42  	// RightDelim - set the right action delimiter for the template and all nested
    43  	// templates to the specified string. Defaults to "{{"
    44  	RDelim string
    45  
    46  	// Experimental - enable experimental features
    47  	Experimental bool
    48  }
    49  
    50  // optionsFromConfig - create a set of options from the internal config struct.
    51  // Does not set the Funcs field.
    52  func optionsFromConfig(cfg *config.Config) Options {
    53  	ds := make(map[string]Datasource, len(cfg.DataSources))
    54  	for k, v := range cfg.DataSources {
    55  		ds[k] = Datasource{
    56  			URL:    v.URL,
    57  			Header: v.Header,
    58  		}
    59  	}
    60  	cs := make(map[string]Datasource, len(cfg.Context))
    61  	for k, v := range cfg.Context {
    62  		cs[k] = Datasource{
    63  			URL:    v.URL,
    64  			Header: v.Header,
    65  		}
    66  	}
    67  	ts := make(map[string]Datasource, len(cfg.Templates))
    68  	for k, v := range cfg.Templates {
    69  		ts[k] = Datasource{
    70  			URL:    v.URL,
    71  			Header: v.Header,
    72  		}
    73  	}
    74  
    75  	opts := Options{
    76  		Datasources:  ds,
    77  		Context:      cs,
    78  		Templates:    ts,
    79  		ExtraHeaders: cfg.ExtraHeaders,
    80  		LDelim:       cfg.LDelim,
    81  		RDelim:       cfg.RDelim,
    82  		Experimental: cfg.Experimental,
    83  	}
    84  
    85  	return opts
    86  }
    87  
    88  // Datasource - a datasource URL with optional headers
    89  //
    90  // Experimental: subject to breaking changes before the next major release
    91  type Datasource struct {
    92  	URL    *url.URL
    93  	Header http.Header
    94  }
    95  
    96  // Renderer provides gomplate's core template rendering functionality.
    97  // It should be initialized with NewRenderer.
    98  //
    99  // Experimental: subject to breaking changes before the next major release
   100  type Renderer struct {
   101  	data        *data.Data
   102  	nested      config.Templates
   103  	funcs       template.FuncMap
   104  	lDelim      string
   105  	rDelim      string
   106  	tctxAliases []string
   107  }
   108  
   109  // NewRenderer creates a new template renderer with the specified options.
   110  // The returned renderer can be reused, but it is not (yet) safe for concurrent
   111  // use.
   112  //
   113  // Experimental: subject to breaking changes before the next major release
   114  func NewRenderer(opts Options) *Renderer {
   115  	if Metrics == nil {
   116  		Metrics = newMetrics()
   117  	}
   118  
   119  	tctxAliases := []string{}
   120  	sources := map[string]*data.Source{}
   121  
   122  	for alias, ds := range opts.Context {
   123  		tctxAliases = append(tctxAliases, alias)
   124  		sources[alias] = &data.Source{
   125  			Alias:  alias,
   126  			URL:    ds.URL,
   127  			Header: ds.Header,
   128  		}
   129  	}
   130  	for alias, ds := range opts.Datasources {
   131  		sources[alias] = &data.Source{
   132  			Alias:  alias,
   133  			URL:    ds.URL,
   134  			Header: ds.Header,
   135  		}
   136  	}
   137  
   138  	// convert the internal config.Templates to a map[string]Datasource
   139  	// TODO: simplify when config.Templates is removed
   140  	nested := config.Templates{}
   141  	for alias, ds := range opts.Templates {
   142  		nested[alias] = config.DataSource{
   143  			URL:    ds.URL,
   144  			Header: ds.Header,
   145  		}
   146  	}
   147  
   148  	d := &data.Data{
   149  		ExtraHeaders: opts.ExtraHeaders,
   150  		Sources:      sources,
   151  	}
   152  
   153  	// make sure data cleanups are run on exit
   154  	addCleanupHook(d.Cleanup)
   155  
   156  	if opts.Funcs == nil {
   157  		opts.Funcs = template.FuncMap{}
   158  	}
   159  
   160  	return &Renderer{
   161  		nested:      nested,
   162  		data:        d,
   163  		funcs:       opts.Funcs,
   164  		tctxAliases: tctxAliases,
   165  		lDelim:      opts.LDelim,
   166  		rDelim:      opts.RDelim,
   167  	}
   168  }
   169  
   170  // Template contains the basic data needed to render a template with a Renderer
   171  //
   172  // Experimental: subject to breaking changes before the next major release
   173  type Template struct {
   174  	// Writer is the writer to output the rendered template to. If this writer
   175  	// is a non-os.Stdout io.Closer, it will be closed after the template is
   176  	// rendered.
   177  	Writer io.Writer
   178  	// Name is the name of the template - used for error messages
   179  	Name string
   180  	// Text is the template text
   181  	Text string
   182  }
   183  
   184  // RenderTemplates renders a list of templates, parsing each template's Text
   185  // and executing it, outputting to its Writer. If a template's Writer is a
   186  // non-os.Stdout io.Closer, it will be closed after the template is rendered.
   187  //
   188  // Experimental: subject to breaking changes before the next major release
   189  func (t *Renderer) RenderTemplates(ctx context.Context, templates []Template) error {
   190  	// we need to inject the current context into the Data value, because
   191  	// the Datasource method may need it
   192  	// TODO: remove this in v4
   193  	t.data.Ctx = ctx
   194  
   195  	// configure the template context with the refreshed Data value
   196  	// only done here because the data context may have changed
   197  	tmplctx, err := createTmplContext(ctx, t.tctxAliases, t.data)
   198  	if err != nil {
   199  		return err
   200  	}
   201  
   202  	return t.renderTemplatesWithData(ctx, templates, tmplctx)
   203  }
   204  
   205  func (t *Renderer) renderTemplatesWithData(ctx context.Context, templates []Template, tmplctx interface{}) error {
   206  	// update funcs with the current context
   207  	// only done here to ensure the context is properly set in func namespaces
   208  	f := template.FuncMap{}
   209  	addToMap(f, funcs.CreateDataFuncs(ctx, t.data))
   210  	addToMap(f, funcs.CreateAWSFuncs(ctx))
   211  	addToMap(f, funcs.CreateGCPFuncs(ctx))
   212  	addToMap(f, funcs.CreateBase64Funcs(ctx))
   213  	addToMap(f, funcs.CreateNetFuncs(ctx))
   214  	addToMap(f, funcs.CreateReFuncs(ctx))
   215  	addToMap(f, funcs.CreateStringFuncs(ctx))
   216  	addToMap(f, funcs.CreateEnvFuncs(ctx))
   217  	addToMap(f, funcs.CreateConvFuncs(ctx))
   218  	addToMap(f, funcs.CreateTimeFuncs(ctx))
   219  	addToMap(f, funcs.CreateMathFuncs(ctx))
   220  	addToMap(f, funcs.CreateCryptoFuncs(ctx))
   221  	addToMap(f, funcs.CreateFileFuncs(ctx))
   222  	addToMap(f, funcs.CreateFilePathFuncs(ctx))
   223  	addToMap(f, funcs.CreatePathFuncs(ctx))
   224  	addToMap(f, funcs.CreateSockaddrFuncs(ctx))
   225  	addToMap(f, funcs.CreateTestFuncs(ctx))
   226  	addToMap(f, funcs.CreateCollFuncs(ctx))
   227  	addToMap(f, funcs.CreateUUIDFuncs(ctx))
   228  	addToMap(f, funcs.CreateRandomFuncs(ctx))
   229  
   230  	// add user-defined funcs last so they override the built-in funcs
   231  	addToMap(f, t.funcs)
   232  
   233  	// track some metrics for debug output
   234  	start := time.Now()
   235  	defer func() { Metrics.TotalRenderDuration = time.Since(start) }()
   236  	for _, template := range templates {
   237  		if template.Writer != nil {
   238  			wr, ok := template.Writer.(io.Closer)
   239  			if ok && wr != os.Stdout {
   240  				defer wr.Close()
   241  			}
   242  		}
   243  
   244  		tstart := time.Now()
   245  		tmpl, err := parseTemplate(ctx, template.Name, template.Text,
   246  			f, tmplctx, t.nested, t.lDelim, t.rDelim)
   247  		if err != nil {
   248  			return err
   249  		}
   250  
   251  		err = tmpl.Execute(template.Writer, tmplctx)
   252  		Metrics.RenderDuration[template.Name] = time.Since(tstart)
   253  		if err != nil {
   254  			Metrics.Errors++
   255  			return fmt.Errorf("failed to render template %s: %w", template.Name, err)
   256  		}
   257  		Metrics.TemplatesProcessed++
   258  	}
   259  	return nil
   260  }
   261  
   262  // Render is a convenience method for rendering a single template. For more
   263  // than one template, use RenderTemplates. If wr is a non-os.Stdout
   264  // io.Closer, it will be closed after the template is rendered.
   265  //
   266  // Experimental: subject to breaking changes before the next major release
   267  func (t *Renderer) Render(ctx context.Context, name, text string, wr io.Writer) error {
   268  	return t.RenderTemplates(ctx, []Template{
   269  		{Name: name, Text: text, Writer: wr},
   270  	})
   271  }