github.com/tooploox/oya@v0.0.21-0.20230524103240-1cda1861aad6/pkg/template/template.go (about)

     1  package template
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  
    10  	"github.com/gobwas/glob"
    11  	"github.com/tooploox/oya/pkg/errors"
    12  )
    13  
    14  type ErrRenderFail struct {
    15  }
    16  
    17  func (e ErrRenderFail) Error() string {
    18  	return "render error"
    19  }
    20  
    21  // Template represents a template that can be rendered using provided values.
    22  type Template interface {
    23  	Render(out io.Writer, values Scope) error
    24  	RenderString(values Scope) (string, error)
    25  }
    26  
    27  // Load loads template from the path.
    28  func Load(path string, delimiters Delimiters) (Template, error) {
    29  	source, err := ioutil.ReadFile(path)
    30  	if err != nil {
    31  		return nil, err
    32  	}
    33  	return Parse(string(source), delimiters)
    34  }
    35  
    36  // Parse parses template in the source string.
    37  func Parse(source string, delimiters Delimiters) (Template, error) {
    38  	return parsePlush(source, delimiters)
    39  }
    40  
    41  // RenderAll renders all templates in the path (directory or a single file) to an output path (directory or file) using the provided value scope.
    42  func RenderAll(templatePath string, excludedPaths []string, outputPath string, values Scope, delimiters Delimiters) error {
    43  	return filepath.Walk(templatePath, func(path string, info os.FileInfo, err error) error {
    44  		if err != nil {
    45  			return err
    46  		}
    47  		if info.IsDir() {
    48  			return nil
    49  		}
    50  		relPath, err := filepath.Rel(templatePath, path)
    51  		if err != nil {
    52  			return err
    53  		}
    54  		if relPath == "." {
    55  			// templatePath is a path to a file.
    56  			relPath = filepath.Base(templatePath)
    57  		}
    58  		if ok, err := pathMatches(excludedPaths, relPath); ok || err != nil {
    59  			return err // err is nil if ok
    60  		}
    61  
    62  		filePath, err := renderString(filepath.Join(outputPath, relPath), values, delimiters)
    63  		if err != nil {
    64  			return errors.Wrap(
    65  				err,
    66  				ErrRenderFail{},
    67  				errors.Location{
    68  					Name:        path,
    69  					VerboseName: fmt.Sprintf("in template %v", path),
    70  				},
    71  			)
    72  		}
    73  		err = renderFile(path, filePath, values, delimiters)
    74  		if err != nil {
    75  			return errors.Wrap(err,
    76  				ErrRenderFail{},
    77  				errors.Location{
    78  					Name:        path,
    79  					VerboseName: fmt.Sprintf("in template %v", path),
    80  				},
    81  			)
    82  		}
    83  		return nil
    84  	})
    85  }
    86  
    87  func renderFile(templatePath, outputPath string, values Scope, delimiters Delimiters) error {
    88  	t, err := Load(templatePath, delimiters)
    89  	if err != nil {
    90  		return err
    91  	}
    92  
    93  	err = os.MkdirAll(filepath.Dir(outputPath), 0700)
    94  	if err != nil {
    95  		return err
    96  	}
    97  
    98  	out, err := os.Create(outputPath)
    99  	if err != nil {
   100  		return err
   101  	}
   102  	defer func() {
   103  		_ = out.Close()
   104  	}()
   105  
   106  	return t.Render(out, values)
   107  }
   108  
   109  func renderString(templateSource string, values Scope, delimiters Delimiters) (string, error) {
   110  	t, err := Parse(templateSource, delimiters)
   111  	if err != nil {
   112  		return "", err
   113  	}
   114  	str, err := t.RenderString(values)
   115  	if err != nil {
   116  		return "", err
   117  	}
   118  	return str, nil
   119  }
   120  
   121  func pathMatches(patterns []string, path string) (bool, error) {
   122  	for _, pattern := range patterns {
   123  		p, err := glob.Compile(pattern)
   124  		if err != nil {
   125  			return false, err
   126  		}
   127  		if p.Match(path) {
   128  			return true, nil
   129  		}
   130  	}
   131  	return false, nil
   132  }