diff --git a/template_nest.go b/template_nest.go index d3920f1..588f457 100644 --- a/template_nest.go +++ b/template_nest.go @@ -22,6 +22,7 @@ type Option struct { 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 + TokenEscapeChar string // Escapes a token delimiter, i.e. if set to '\' then variables that have '\' prefix won't be replaced } type TemplateNest struct { @@ -131,6 +132,7 @@ func (nest *TemplateNest) index(filePath string) (TemplateFileIndex, error) { delimiterStart := nest.option.Delimiters[0] delimiterEnd := nest.option.Delimiters[1] + escapeChar := nest.option.TokenEscapeChar re := regexp.MustCompile(fmt.Sprintf( "%s\\s*(.+?)\\s*%s", regexp.QuoteMeta(delimiterStart), regexp.QuoteMeta(delimiterEnd), @@ -151,6 +153,22 @@ func (nest *TemplateNest) index(filePath string) (TemplateFileIndex, error) { varName := string(contents[nameStartIdx:nameEndIdx]) variableNames[varName] = struct{}{} + // If token escape char is set then look behind for it and if we + // find the escape char then we're only going to remove the escape + // char and not remove this variable. + if escapeChar != "" && startIdx >= len(escapeChar) { + escapeStartIdx := startIdx - len(escapeChar) + if contentsStr[escapeStartIdx:startIdx] == escapeChar { + variables = append(variables, TemplateFileVariable{ + Name: "", + StartPosition: uint(escapeStartIdx), + EndPosition: uint(escapeStartIdx + len(escapeChar)), + EscapedToken: true, + }) + continue + } + } + // 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 @@ -276,6 +294,10 @@ func (nest *TemplateNest) Render(toRender interface{}) (string, error) { 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. diff --git a/tests/07_token_escape_char_test.go b/tests/07_token_escape_char_test.go new file mode 100644 index 0000000..5a6807f --- /dev/null +++ b/tests/07_token_escape_char_test.go @@ -0,0 +1,97 @@ +package tests + +import ( + "git.virtual.blue/tomgracey/template-nest-go" + "github.com/stretchr/testify/assert" + "io/ioutil" + "strings" + "testing" +) + +func TestRenderWithEscapedVariable(t *testing.T) { + nest, err := templatenest.New(templatenest.Option{ + TemplateDir: "templates", + TokenEscapeChar: "\\", + }) + 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{ + templatenest.Hash{ + "TEMPLATE": "01-simple-component-token-escape", + }, + }, + } + + outputPath := "templates/output/09-simple-page-token-escape.html" + outputContents, err := ioutil.ReadFile(outputPath) + if err != nil { + t.Fatalf("error reading file (`%s`): %+v", outputPath, err) + } + + assert.Equal( + t, + strings.TrimSpace(string(outputContents)), + nest.MustRender(page), + "Rendered output does not match expected output", + ) +} + +func TestRenderWithEscapedVariableAtStart(t *testing.T) { + nest, err := templatenest.New(templatenest.Option{ + TemplateDir: "templates", + TokenEscapeChar: "\\", + }) + if err != nil { + t.Fatalf("Failed to initialize TemplateNest: %+v", err) + } + + page := templatenest.Hash{ + "TEMPLATE": "03-var-at-begin", + "variable": "Simple Variable", + } + + outputPath := "templates/output/10-var-at-begin.html" + outputContents, err := ioutil.ReadFile(outputPath) + if err != nil { + t.Fatalf("error reading file (`%s`): %+v", outputPath, err) + } + + assert.Equal( + t, + strings.TrimSpace(string(outputContents)), + nest.MustRender(page), + "Rendered output does not match expected output", + ) +} + +func TestRenderWithEscapedVariableAtStart01(t *testing.T) { + nest, err := templatenest.New(templatenest.Option{ + TemplateDir: "templates", + TokenEscapeChar: "\\", + }) + if err != nil { + t.Fatalf("Failed to initialize TemplateNest: %+v", err) + } + + page := templatenest.Hash{ + "TEMPLATE": "03-var-at-begin-with-space", + } + + outputPath := "templates/output/10-var-at-begin-with-space.html" + outputContents, err := ioutil.ReadFile(outputPath) + if err != nil { + t.Fatalf("error reading file (`%s`): %+v", outputPath, err) + } + + assert.Equal( + t, + strings.TrimSpace(string(outputContents)), + nest.MustRender(page), + "Rendered output does not match expected output", + ) +} diff --git a/tests/templates/03-var-at-begin-with-space.html b/tests/templates/03-var-at-begin-with-space.html new file mode 100644 index 0000000..b2d355b --- /dev/null +++ b/tests/templates/03-var-at-begin-with-space.html @@ -0,0 +1 @@ +\ diff --git a/tests/templates/output/10-var-at-begin-with-space.html b/tests/templates/output/10-var-at-begin-with-space.html new file mode 100644 index 0000000..c31bc6c --- /dev/null +++ b/tests/templates/output/10-var-at-begin-with-space.html @@ -0,0 +1 @@ +