Handle other slice and map types

This commit is contained in:
Andinus 2024-11-16 18:20:20 +05:30
parent 07adf2d49e
commit 4a49e901ac
Signed by: andinus
GPG Key ID: B67D55D482A799FD

@ -6,6 +6,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
"regexp" "regexp"
"strings" "strings"
"time" "time"
@ -256,6 +257,10 @@ func (nest *TemplateNest) MustRender(toRender interface{}) string {
} }
func (nest *TemplateNest) Render(toRender interface{}) (string, error) { func (nest *TemplateNest) Render(toRender interface{}) (string, error) {
if reflect.TypeOf(toRender).Kind() == reflect.Slice {
return nest.renderSlice(toRender)
}
switch v := toRender.(type) { switch v := toRender.(type) {
case nil: case nil:
return "", nil return "", nil
@ -272,114 +277,128 @@ func (nest *TemplateNest) Render(toRender interface{}) (string, error) {
case float64, int, int64: case float64, int, int64:
return fmt.Sprintf("%v", v), nil return fmt.Sprintf("%v", v), nil
case []Hash:
var rendered strings.Builder
for _, item := range v {
renderedItem, err := nest.Render(item)
if err != nil {
return "", err
}
rendered.WriteString(renderedItem)
}
return rendered.String(), nil
case Hash: case Hash:
tLabel, ok := v[nest.option.NameLabel] return nest.renderHash(v)
if !ok {
return "", fmt.Errorf(
"encountered hash with no name label (name label: `%s`)", nest.option.NameLabel,
)
}
tName, ok := tLabel.(string) case map[string]interface{}:
if !ok { return nest.renderHash(v)
return "", fmt.Errorf(
"encountered hash with invalid name label type: %+v", tLabel,
)
}
tFile := nest.getTemplateFilePath(tName)
fileInfo, err := os.Stat(tFile)
if err != nil {
return "", fmt.Errorf("error getting file info: %w", err)
}
tIndex, exists := nest.cache[tName]
// If cache doesn't exist or has expired, re-index the file.
if !exists || fileInfo.ModTime().After(tIndex.LastModified) {
newIndex, err := nest.index(tFile)
if err != nil {
return "", fmt.Errorf("file index failed: %w", err)
}
tIndex = newIndex
}
if nest.option.DieOnBadParams {
for k, _ := range v {
// If a variable in template hash is not present in template
// file and it's not the template label then it's a bad param.
_, exists := tIndex.VariableNames[k]
if !exists && k != nest.option.NameLabel {
return "", fmt.Errorf(
"bad params in template hash, variable not present in template file: `%s`", k,
)
}
}
}
rendered := tIndex.Contents
for i := len(tIndex.Variables) - 1; i >= 0; i-- {
variable := tIndex.Variables[i]
if variable.EscapedToken {
rendered = rendered[:variable.StartPosition] + rendered[variable.EndPosition:]
continue
}
// If the variable doesn't exist in template hash then replace it
// with an empty string.
replacement := ""
value, exists := v[variable.Name]
defaultValue, defaultExists := nest.defaultsFlat[variable.Name]
if exists || defaultExists {
if !exists {
value = defaultValue
}
subRender, err := nest.Render(value)
if err != nil {
return "", err
}
if nest.option.FixedIndent && variable.IndentLevel != 0 {
indentReplacement := "\n" + strings.Repeat(" ", int(variable.IndentLevel))
subRender = strings.ReplaceAll(subRender, "\n", indentReplacement)
}
replacement = subRender
}
// Replace in rendered template
rendered = rendered[:variable.StartPosition] + replacement + rendered[variable.EndPosition:]
}
if nest.option.ShowLabels {
labelStart := fmt.Sprintf(
"%s BEGIN %s %s\n",
nest.option.CommentDelimiters[0], tName, nest.option.CommentDelimiters[1],
)
labelEnd := fmt.Sprintf(
"%s END %s %s\n",
nest.option.CommentDelimiters[0], tName, nest.option.CommentDelimiters[1],
)
rendered = labelStart + rendered + labelEnd
}
return strings.TrimSpace(rendered), nil
default: default:
return "", fmt.Errorf("unsupported template hash value type: %+v", v) return "", fmt.Errorf("unsupported template hash value type: %+v", v)
} }
} }
func (nest *TemplateNest) renderSlice(toRender interface{}) (string, error) {
val := reflect.ValueOf(toRender)
if val.Kind() != reflect.Slice {
return "", fmt.Errorf("expected slice, got: %T", toRender)
}
var rendered strings.Builder
for i := 0; i < val.Len(); i++ {
element := val.Index(i).Interface()
subRender, err := nest.Render(element)
if err != nil {
return "", err
}
rendered.WriteString(subRender)
}
return rendered.String(), nil
}
func (nest *TemplateNest) renderHash(hash map[string]interface{}) (string, error) {
tLabel, ok := hash[nest.option.NameLabel]
if !ok {
return "", fmt.Errorf(
"encountered hash with no name label (name label: `%s`)", nest.option.NameLabel,
)
}
tName, ok := tLabel.(string)
if !ok {
return "", fmt.Errorf(
"encountered hash with invalid name label type: %+v", tLabel,
)
}
tFile := nest.getTemplateFilePath(tName)
fileInfo, err := os.Stat(tFile)
if err != nil {
return "", fmt.Errorf("error getting file info: %w", err)
}
tIndex, exists := nest.cache[tName]
// If cache doesn't exist or has expired, re-index the file.
if !exists || fileInfo.ModTime().After(tIndex.LastModified) {
newIndex, err := nest.index(tFile)
if err != nil {
return "", fmt.Errorf("file index failed: %w", err)
}
tIndex = newIndex
}
if nest.option.DieOnBadParams {
for k, _ := range hash {
// If a variable in template hash is not present in template
// file and it's not the template label then it's a bad param.
_, exists := tIndex.VariableNames[k]
if !exists && k != nest.option.NameLabel {
return "", fmt.Errorf(
"bad params in template hash, variable not present in template file: `%s`", k,
)
}
}
}
rendered := tIndex.Contents
for i := len(tIndex.Variables) - 1; i >= 0; i-- {
variable := tIndex.Variables[i]
if variable.EscapedToken {
rendered = rendered[:variable.StartPosition] + rendered[variable.EndPosition:]
continue
}
// If the variable doesn't exist in template hash then replace it
// with an empty string.
replacement := ""
value, exists := hash[variable.Name]
defaultValue, defaultExists := nest.defaultsFlat[variable.Name]
if exists || defaultExists {
if !exists {
value = defaultValue
}
subRender, err := nest.Render(value)
if err != nil {
return "", err
}
if nest.option.FixedIndent && variable.IndentLevel != 0 {
indentReplacement := "\n" + strings.Repeat(" ", int(variable.IndentLevel))
subRender = strings.ReplaceAll(subRender, "\n", indentReplacement)
}
replacement = subRender
}
// Replace in rendered template
rendered = rendered[:variable.StartPosition] + replacement + rendered[variable.EndPosition:]
}
if nest.option.ShowLabels {
labelStart := fmt.Sprintf(
"%s BEGIN %s %s\n",
nest.option.CommentDelimiters[0], tName, nest.option.CommentDelimiters[1],
)
labelEnd := fmt.Sprintf(
"%s END %s %s\n",
nest.option.CommentDelimiters[0], tName, nest.option.CommentDelimiters[1],
)
rendered = labelStart + rendered + labelEnd
}
return strings.TrimSpace(rendered), nil
}