github.com/hairyhenderson/gomplate/v4@v4.0.0-pre-2.0.20240520121557-362f058f0c93/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  	"context"
     8  	"fmt"
     9  	"path/filepath"
    10  	"strings"
    11  	"text/template"
    12  	"time"
    13  
    14  	"github.com/hairyhenderson/gomplate/v4/internal/config"
    15  	"github.com/hairyhenderson/gomplate/v4/internal/datafs"
    16  )
    17  
    18  // RunTemplates - run all gomplate templates specified by the given configuration
    19  //
    20  // Deprecated: use the Renderer interface instead
    21  func RunTemplates(o *Config) error {
    22  	cfg, err := o.toNewConfig()
    23  	if err != nil {
    24  		return err
    25  	}
    26  	return Run(context.Background(), cfg)
    27  }
    28  
    29  // Run all gomplate templates specified by the given configuration
    30  func Run(ctx context.Context, cfg *config.Config) error {
    31  	Metrics = newMetrics()
    32  
    33  	// apply defaults before validation
    34  	cfg.ApplyDefaults()
    35  
    36  	err := cfg.Validate()
    37  	if err != nil {
    38  		return fmt.Errorf("failed to validate config: %w\n%+v", err, cfg)
    39  	}
    40  
    41  	funcMap := template.FuncMap{}
    42  	err = bindPlugins(ctx, cfg, funcMap)
    43  	if err != nil {
    44  		return err
    45  	}
    46  
    47  	// if a custom Stdin is set in the config, inject it into the context now
    48  	ctx = datafs.ContextWithStdin(ctx, cfg.Stdin)
    49  
    50  	opts := optionsFromConfig(cfg)
    51  	opts.Funcs = funcMap
    52  	tr := NewRenderer(opts)
    53  
    54  	start := time.Now()
    55  
    56  	namer := chooseNamer(cfg, tr)
    57  	tmpl, err := gatherTemplates(ctx, cfg, namer)
    58  	Metrics.GatherDuration = time.Since(start)
    59  	if err != nil {
    60  		Metrics.Errors++
    61  		return fmt.Errorf("failed to gather templates for rendering: %w", err)
    62  	}
    63  	Metrics.TemplatesGathered = len(tmpl)
    64  
    65  	err = tr.RenderTemplates(ctx, tmpl)
    66  	if err != nil {
    67  		return err
    68  	}
    69  
    70  	return nil
    71  }
    72  
    73  func chooseNamer(cfg *config.Config, tr *Renderer) func(context.Context, string) (string, error) {
    74  	if cfg.OutputMap == "" {
    75  		return simpleNamer(cfg.OutputDir)
    76  	}
    77  	return mappingNamer(cfg.OutputMap, tr)
    78  }
    79  
    80  func simpleNamer(outDir string) func(ctx context.Context, inPath string) (string, error) {
    81  	return func(_ context.Context, inPath string) (string, error) {
    82  		outPath := filepath.Join(outDir, inPath)
    83  		return filepath.Clean(outPath), nil
    84  	}
    85  }
    86  
    87  func mappingNamer(outMap string, tr *Renderer) func(context.Context, string) (string, error) {
    88  	return func(ctx context.Context, inPath string) (string, error) {
    89  		tcontext, err := createTmplContext(ctx, tr.tctxAliases, tr.data)
    90  		if err != nil {
    91  			return "", err
    92  		}
    93  
    94  		// add '.in' to the template context and preserve the original context
    95  		// in '.ctx'
    96  		tctx := &tmplctx{}
    97  		//nolint:gocritic
    98  		switch c := tcontext.(type) {
    99  		case *tmplctx:
   100  			for k, v := range *c {
   101  				if k != "in" && k != "ctx" {
   102  					(*tctx)[k] = v
   103  				}
   104  			}
   105  		}
   106  		(*tctx)["ctx"] = tcontext
   107  		(*tctx)["in"] = inPath
   108  
   109  		out := &bytes.Buffer{}
   110  		err = tr.renderTemplatesWithData(ctx,
   111  			[]Template{{Name: "<OutputMap>", Text: outMap, Writer: out}}, tctx)
   112  		if err != nil {
   113  			return "", fmt.Errorf("failed to render outputMap with ctx %+v and inPath %s: %w", tctx, inPath, err)
   114  		}
   115  
   116  		return filepath.Clean(strings.TrimSpace(out.String())), nil
   117  	}
   118  }