From 4a49e901ac57f0b53c3e9dd884b48c9ab4eebe09 Mon Sep 17 00:00:00 2001 From: Andinus Date: Sat, 16 Nov 2024 18:20:20 +0530 Subject: [PATCH] Handle other slice and map types --- template_nest.go | 227 +++++++++++++++++++++++++---------------------- 1 file changed, 123 insertions(+), 104 deletions(-) diff --git a/template_nest.go b/template_nest.go index 6238585..eb3f453 100644 --- a/template_nest.go +++ b/template_nest.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "path/filepath" + "reflect" "regexp" "strings" "time" @@ -256,6 +257,10 @@ func (nest *TemplateNest) MustRender(toRender interface{}) string { } func (nest *TemplateNest) Render(toRender interface{}) (string, error) { + if reflect.TypeOf(toRender).Kind() == reflect.Slice { + return nest.renderSlice(toRender) + } + switch v := toRender.(type) { case nil: return "", nil @@ -272,114 +277,128 @@ func (nest *TemplateNest) Render(toRender interface{}) (string, error) { case float64, int, int64: 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: - tLabel, ok := v[nest.option.NameLabel] - if !ok { - return "", fmt.Errorf( - "encountered hash with no name label (name label: `%s`)", nest.option.NameLabel, - ) - } + return nest.renderHash(v) - 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 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 + case map[string]interface{}: + return nest.renderHash(v) default: 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 +}