150 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			150 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Go
		
	
	
	
| package tmpl
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"fmt"
 | |
| 	"path/filepath"
 | |
| 	"strings"
 | |
| 	"text/template"
 | |
| 
 | |
| 	"github.com/Masterminds/sprig/v3"
 | |
| 	"github.com/pkg/errors"
 | |
| )
 | |
| 
 | |
| const recursionMaxNums = 1000
 | |
| 
 | |
| // CreateFuncMap creates a template.FuncMap for the Context struct.
 | |
| // It combines the functions from sprig.TxtFuncMap() with the functions
 | |
| // defined in the Context's createFuncMap() method.
 | |
| // It also adds aliases for certain functions based on the aliases map.
 | |
| // The resulting FuncMap is returned.
 | |
| func (c *Context) CreateFuncMap() template.FuncMap {
 | |
| 	// function aliases
 | |
| 	aliased := template.FuncMap{}
 | |
| 
 | |
| 	// map of function aliases
 | |
| 	aliases := map[string]string{
 | |
| 		"get": "sprigGet",
 | |
| 	}
 | |
| 
 | |
| 	// get the default sprig functions
 | |
| 	funcMap := sprig.TxtFuncMap()
 | |
| 
 | |
| 	// add aliases to the aliased FuncMap
 | |
| 	for orig, alias := range aliases {
 | |
| 		aliased[alias] = funcMap[orig]
 | |
| 	}
 | |
| 
 | |
| 	// add functions from the Context's createFuncMap() method to the funcMap
 | |
| 	for name, f := range c.createFuncMap() {
 | |
| 		funcMap[name] = f
 | |
| 	}
 | |
| 
 | |
| 	// add aliased functions to the funcMap
 | |
| 	for name, f := range aliased {
 | |
| 		funcMap[name] = f
 | |
| 	}
 | |
| 
 | |
| 	return funcMap
 | |
| }
 | |
| 
 | |
| type tplInfo struct {
 | |
| 	name    string
 | |
| 	content string
 | |
| }
 | |
| 
 | |
| // helperTPLs returns the contents of all files with names starting with "_" and ending with ".tpl"
 | |
| // in the root directory of the Context. It reads each file and appends its content to the contents slice.
 | |
| // If any error occurs during the file reading or globbing process, it returns an error.
 | |
| func (c *Context) helperTPLs() ([]tplInfo, error) {
 | |
| 	tplInfos := []tplInfo{}
 | |
| 	files, err := c.fs.Glob(filepath.Join(c.basePath, "_*.tpl"))
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("failed to glob helper templates: %v", err)
 | |
| 	}
 | |
| 	for _, file := range files {
 | |
| 		content, err := c.fs.ReadFile(file)
 | |
| 		if err != nil {
 | |
| 			return nil, fmt.Errorf("failed to read helper template %s: %v", file, err)
 | |
| 		}
 | |
| 		tplInfos = append(tplInfos, tplInfo{name: file, content: string(content)})
 | |
| 	}
 | |
| 	return tplInfos, nil
 | |
| }
 | |
| 
 | |
| // newTemplate creates a new template based on the context.
 | |
| // It initializes the template with the specified options and parses the helper templates.
 | |
| // It also adds the 'include' function to the template's function map.
 | |
| // The 'include' function allows including and rendering nested templates.
 | |
| // The function returns the created template or an error if any occurred.
 | |
| func (c *Context) newTemplate() (*template.Template, error) {
 | |
| 	funcMap := c.CreateFuncMap()
 | |
| 
 | |
| 	tmpl := template.New("stringTemplate")
 | |
| 	if c.preRender {
 | |
| 		tmpl = tmpl.Option("missingkey=zero")
 | |
| 	} else {
 | |
| 		tmpl = tmpl.Option("missingkey=error")
 | |
| 	}
 | |
| 
 | |
| 	tpls, err := c.helperTPLs()
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	for _, tpl := range tpls {
 | |
| 		tmpl, err = tmpl.Parse(tpl.content)
 | |
| 		if err != nil {
 | |
| 			return nil, fmt.Errorf("failed to parse helper template %s: %v", tpl.name, err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	includedNames := make(map[string]int)
 | |
| 
 | |
| 	// Add the 'include' function here so we can close over t.
 | |
| 	funcMap["include"] = func(name string, data interface{}) (string, error) {
 | |
| 		var buf strings.Builder
 | |
| 		if v, ok := includedNames[name]; ok {
 | |
| 			if v > recursionMaxNums {
 | |
| 				return "", errors.Wrapf(fmt.Errorf("unable to execute template"), "rendering template has a nested reference name: %s", name)
 | |
| 			}
 | |
| 			includedNames[name]++
 | |
| 		} else {
 | |
| 			includedNames[name] = 1
 | |
| 		}
 | |
| 		err := tmpl.ExecuteTemplate(&buf, name, data)
 | |
| 		includedNames[name]--
 | |
| 		return buf.String(), err
 | |
| 	}
 | |
| 	tmpl.Funcs(funcMap)
 | |
| 	return tmpl, nil
 | |
| }
 | |
| 
 | |
| // RenderTemplateToBuffer renders the provided template string with the given data and returns the result as a *bytes.Buffer.
 | |
| // The template string is parsed and executed using the Context's newTemplate method.
 | |
| // If an error occurs during parsing or execution, it is returned along with the partially rendered template.
 | |
| // The data parameter is optional and can be used to provide additional data for template rendering.
 | |
| // If no data is provided, the template is rendered with an empty data context.
 | |
| func (c *Context) RenderTemplateToBuffer(s string, data ...any) (*bytes.Buffer, error) {
 | |
| 	t, err := c.newTemplate()
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	t, err = t.Parse(s)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	var tplString bytes.Buffer
 | |
| 	var d any
 | |
| 	if len(data) > 0 {
 | |
| 		d = data[0]
 | |
| 	}
 | |
| 	var execErr = t.Execute(&tplString, d)
 | |
| 
 | |
| 	if execErr != nil {
 | |
| 		return &tplString, execErr
 | |
| 	}
 | |
| 
 | |
| 	return &tplString, nil
 | |
| }
 |