diff --git a/template_nest.go b/template_nest.go index 0429fae..d3920f1 100644 --- a/template_nest.go +++ b/template_nest.go @@ -16,11 +16,12 @@ type Hash map[string]interface{} type Option struct { Delimiters [2]string NameLabel string // Identify the template to be used - TemplateDir string // directory where templates are located - TemplateExtension string // appended on the label to idenfity the template - DieOnBadParams bool // attempt to populate a variable that doesn't exist should result in an error + TemplateDir string // Directory where templates are located + TemplateExtension string // Appended on the label to idenfity the template + DieOnBadParams bool // Attempt to populate a variable that doesn't exist should result in an error ShowLabels bool // Prepend & Append a string to every template, helpful in debugging CommentDelimiters [2]string // Used in conjunction with ShowLabels, if HTML then use '' + FixedIndent bool // Intended to improve readability when inspecting nested templates } type TemplateNest struct { @@ -118,6 +119,7 @@ func (nest *TemplateNest) index(filePath string) (TemplateFileIndex, error) { if err != nil { return TemplateFileIndex{}, fmt.Errorf("error reading file (`%s`): %w", filePath, err) } + // Capture last modified time fileInfo, err := os.Stat(filePath) if err != nil { @@ -134,6 +136,7 @@ func (nest *TemplateNest) index(filePath string) (TemplateFileIndex, error) { "%s\\s*(.+?)\\s*%s", regexp.QuoteMeta(delimiterStart), regexp.QuoteMeta(delimiterEnd), )) + contentsStr := string(contents) matches := re.FindAllStringSubmatchIndex(string(contents), -1) for _, match := range matches { if len(match) < 4 { @@ -148,15 +151,33 @@ func (nest *TemplateNest) index(filePath string) (TemplateFileIndex, error) { varName := string(contents[nameStartIdx:nameEndIdx]) variableNames[varName] = struct{}{} + // If fixed indent is enabled then record the indent level for this + // variable. To get the indent level we look at each character in + // reverse from the start position of the variable until we find a + // newline character. + indentLevel := 0 + if nest.option.FixedIndent { + // If we do not encounter a newline then that means this variable is + // on the first line, we take the start position as the indent + // level. + lineStartIdx := strings.LastIndex(contentsStr[:startIdx], "\n") + if lineStartIdx == -1 { + indentLevel = startIdx + } else { + indentLevel = startIdx - lineStartIdx - 1 + } + } + variables = append(variables, TemplateFileVariable{ Name: varName, StartPosition: uint(startIdx), EndPosition: uint(endIdx), + IndentLevel: uint(indentLevel), }) } fileIndex := TemplateFileIndex{ - Contents: string(contents), + Contents: contentsStr, LastModified: fileInfo.ModTime(), VariableNames: variableNames, Variables: variables, @@ -261,15 +282,17 @@ func (nest *TemplateNest) Render(toRender interface{}) (string, error) { replacement := "" if value, exists := v[variable.Name]; exists { - if text, ok := value.(string); ok { - replacement = text - } else { - subRender, err := nest.Render(value) - if err != nil { - return "", err - } - replacement = subRender + 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 diff --git a/tests/05_fixed_indent_test.go b/tests/05_fixed_indent_test.go new file mode 100644 index 0000000..be97acc --- /dev/null +++ b/tests/05_fixed_indent_test.go @@ -0,0 +1,92 @@ +package tests + +import ( + "git.virtual.blue/tomgracey/template-nest-go" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestRenderSimplePageWithFixedIndent(t *testing.T) { + nest, err := templatenest.New(templatenest.Option{ + TemplateDir: "templates", + FixedIndent: true, + }) + if err != nil { + t.Fatalf("Failed to initialize TemplateNest: %+v", err) + } + + page := templatenest.Hash{ + "TEMPLATE": "00-simple-page", + "variable": "Simple Variable", + "simple_component": templatenest.Hash{ + "TEMPLATE": "02-simple-component-multi-line", + }, + } + outputPage := templatenest.Hash{"TEMPLATE": "output/07-simple-page-fixed-indent"} + + render := nest.MustRender(page) + outputRender := nest.MustRender(outputPage) + + assert.Equal(t, outputRender, render, "Rendered output does not match expected output") +} + +func TestRenderComplexPageWithFixedIndent(t *testing.T) { + nest, err := templatenest.New(templatenest.Option{ + TemplateDir: "templates", + FixedIndent: true, + }) + if err != nil { + t.Fatalf("Failed to initialize TemplateNest: %+v", err) + } + + page := templatenest.Hash{ + "TEMPLATE": "10-complex-page", + "title": "Complex Page", + "pre_body": templatenest.Hash{ + "TEMPLATE": "18-styles", + }, + "navigation": templatenest.Hash{ + "TEMPLATE": "11-navigation", + "banner": templatenest.Hash{ + "TEMPLATE": "12-navigation-banner", + }, + "items": []templatenest.Hash{ + templatenest.Hash{"TEMPLATE": "13-navigation-item-00-services"}, + templatenest.Hash{"TEMPLATE": "13-navigation-item-01-resources"}, + }, + }, + "hero_section": templatenest.Hash{ + "TEMPLATE": "14-hero-section", + }, + "main_content": []templatenest.Hash{ + templatenest.Hash{"TEMPLATE": "15-isdc-card"}, + templatenest.Hash{ + "TEMPLATE": "16-vb-brand-cards", + "cards": []templatenest.Hash{ + templatenest.Hash{ + "TEMPLATE": "17-vb-brand-card-00", + "parent_classes": "p-card brand-card col-4", + }, + templatenest.Hash{ + "TEMPLATE": "17-vb-brand-card-01", + "parent_classes": "p-card brand-card col-4", + }, + templatenest.Hash{ + "TEMPLATE": "17-vb-brand-card-02", + "parent_classes": "p-card brand-card col-4", + }, + }, + }, + }, + "post_footer": templatenest.Hash{ + "TEMPLATE": "19-scripts", + }, + } + + outputPage := templatenest.Hash{"TEMPLATE": "output/08-complex-page-fixed-indent"} + + render := nest.MustRender(page) + outputRender := nest.MustRender(outputPage) + + assert.Equal(t, outputRender, render, "Rendered output does not match expected output") +}