Add extension, delimiter tests & various fixes

This commit is contained in:
Andinus 2024-11-18 21:43:12 +05:30
parent e09a3298c3
commit aec5482578
Signed by: andinus
SSH Key Fingerprint: SHA256:rop/w9c/gwtEJhUWS5curN3KJEwRr+Fm1ojM3liQTqo
3 changed files with 195 additions and 20 deletions

@ -17,15 +17,15 @@ type Hash map[string]interface{}
// Option holds configuration for TemplateNest // Option holds configuration for TemplateNest
type Option struct { type Option struct {
Delimiters [2]string Delimiters [2]string
NameLabel string // Identify the template to be used NameLabel *string // Identify the template to be used
TemplateDir string // Directory where templates are located TemplateDir string // Directory where templates are located
TemplateExtension string // Appended on the label to idenfity the template 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 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 ShowLabels bool // Prepend & Append a string to every template, helpful in debugging
CommentDelimiters [2]string // Used in conjunction with ShowLabels, if HTML then use '<!--', '-->' CommentDelimiters [2]string // Used in conjunction with ShowLabels, if HTML then use '<!--', '-->'
FixedIndent bool // Intended to improve readability when inspecting nested templates 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 TokenEscapeChar string // Escapes a token delimiter, i.e. if set to '\' then variables that have '\' prefix won't be replaced
DefaultsNamespaceChar string DefaultsNamespaceChar *string
Defaults Hash // Provide a hash of default values that are substituted if template hash does not provide a value Defaults Hash // Provide a hash of default values that are substituted if template hash does not provide a value
NoEscapeInput bool // By default all template values are html escaped NoEscapeInput bool // By default all template values are html escaped
} }
@ -73,14 +73,17 @@ func New(opts Option) (*TemplateNest, error) {
if opts.CommentDelimiters == [2]string{} { if opts.CommentDelimiters == [2]string{} {
opts.CommentDelimiters = [2]string{"<!--", "-->"} opts.CommentDelimiters = [2]string{"<!--", "-->"}
} }
if opts.NameLabel == "" { if opts.NameLabel == nil {
opts.NameLabel = "TEMPLATE" nameLabel := "TEMPLATE"
opts.NameLabel = &nameLabel
} }
if opts.TemplateExtension == "" { if opts.TemplateExtension == nil {
opts.TemplateExtension = "html" ext := "html"
opts.TemplateExtension = &ext
} }
if opts.DefaultsNamespaceChar == "" { if opts.DefaultsNamespaceChar == nil {
opts.DefaultsNamespaceChar = "." namespaceChar := "."
opts.DefaultsNamespaceChar = &namespaceChar
} }
if opts.Defaults == nil { if opts.Defaults == nil {
opts.Defaults = make(map[string]interface{}) opts.Defaults = make(map[string]interface{})
@ -90,7 +93,7 @@ func New(opts Option) (*TemplateNest, error) {
nest := &TemplateNest{ nest := &TemplateNest{
option: opts, option: opts,
cache: make(map[string]TemplateFileIndex), cache: make(map[string]TemplateFileIndex),
defaultsFlat: FlattenMap(opts.Defaults, "", opts.DefaultsNamespaceChar), defaultsFlat: flattenMap(opts.Defaults, "", opts.DefaultsNamespaceChar),
} }
// Walk through the template directory and index the templates. // Walk through the template directory and index the templates.
@ -99,8 +102,14 @@ func New(opts Option) (*TemplateNest, error) {
return err return err
} }
// Skip directories
if info.IsDir() {
return nil
}
// Check if the file has the correct extension. // Check if the file has the correct extension.
if !strings.HasSuffix(info.Name(), "."+opts.TemplateExtension) { if *opts.TemplateExtension != "" &&
!strings.HasSuffix(info.Name(), "."+*opts.TemplateExtension) {
return nil return nil
} }
@ -117,7 +126,11 @@ func New(opts Option) (*TemplateNest, error) {
} }
// Remove the extension from the relative path. // Remove the extension from the relative path.
templateName := strings.TrimSuffix(relPath, "."+opts.TemplateExtension) templateName := relPath
if *opts.TemplateExtension != "" {
templateName = strings.TrimSuffix(relPath, "."+*opts.TemplateExtension)
}
nest.cache[templateName] = templateIndex nest.cache[templateName] = templateIndex
return nil return nil
}) })
@ -128,20 +141,24 @@ func New(opts Option) (*TemplateNest, error) {
return nest, nil return nest, nil
} }
// FlattenMap flattens a nested map[string]interface{} into a flat map with // flattenMap flattens a nested map[string]interface{} into a flat map with
// dot-separated keys. // dot-separated keys.
func FlattenMap(input Hash, parentKey string, separator string) Hash { func flattenMap(input Hash, parentKey string, separator *string) Hash {
result := make(map[string]interface{}) result := make(map[string]interface{})
for key, value := range input { for key, value := range input {
fullKey := key fullKey := key
if parentKey != "" { if parentKey != "" {
fullKey = parentKey + separator + key fullKey = parentKey + *separator + key
} }
// Check if the value is a nested map // Check if the value is a nested map
if nestedMap, ok := value.(Hash); ok { if nestedMap, ok := value.(Hash); ok {
for k, v := range FlattenMap(nestedMap, fullKey, separator) { // If separator is nil then there is no point in the user passing us
result[k] = v // a nested map.
if separator != nil {
for k, v := range flattenMap(nestedMap, fullKey, separator) {
result[k] = v
}
} }
} else { } else {
result[fullKey] = value result[fullKey] = value
@ -242,9 +259,13 @@ func (nest *TemplateNest) index(filePath string) (TemplateFileIndex, error) {
// getTemplateFilePath takes a template name and returns the file path for the // getTemplateFilePath takes a template name and returns the file path for the
// template. // template.
func (nest *TemplateNest) getTemplateFilePath(templateName string) string { func (nest *TemplateNest) getTemplateFilePath(templateName string) string {
if *nest.option.TemplateExtension == "" {
return filepath.Join(nest.option.TemplateDir, templateName)
}
return filepath.Join( return filepath.Join(
nest.option.TemplateDir, nest.option.TemplateDir,
fmt.Sprintf("%s.%s", templateName, nest.option.TemplateExtension), fmt.Sprintf("%s.%s", templateName, *nest.option.TemplateExtension),
) )
} }
@ -307,7 +328,7 @@ func (nest *TemplateNest) renderSlice(toRender interface{}) (string, error) {
} }
func (nest *TemplateNest) renderHash(hash map[string]interface{}) (string, error) { func (nest *TemplateNest) renderHash(hash map[string]interface{}) (string, error) {
tLabel, ok := hash[nest.option.NameLabel] tLabel, ok := hash[*nest.option.NameLabel]
if !ok { if !ok {
return "", fmt.Errorf( return "", fmt.Errorf(
"encountered hash with no name label (name label: `%s`)", nest.option.NameLabel, "encountered hash with no name label (name label: `%s`)", nest.option.NameLabel,
@ -343,7 +364,7 @@ func (nest *TemplateNest) renderHash(hash map[string]interface{}) (string, error
// If a variable in template hash is not present in template // 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. // file and it's not the template label then it's a bad param.
_, exists := tIndex.VariableNames[k] _, exists := tIndex.VariableNames[k]
if !exists && k != nest.option.NameLabel { if !exists && k != *nest.option.NameLabel {
return "", fmt.Errorf( return "", fmt.Errorf(
"bad params in template hash, variable not present in template file: `%s`", k, "bad params in template hash, variable not present in template file: `%s`", k,
) )

@ -0,0 +1,77 @@
package tests
import (
"git.virtual.blue/tomgracey/template-nest-go"
"github.com/stretchr/testify/assert"
"io/ioutil"
"strings"
"testing"
)
func TestRenderWithJsExtension(t *testing.T) {
ext := "js"
nest, err := templatenest.New(templatenest.Option{
TemplateDir: "templates",
TemplateExtension: &ext,
})
if err != nil {
t.Fatalf("Failed to initialize TemplateNest: %+v", err)
}
page := map[string]interface{}{
"TEMPLATE": "30-main",
"var": "Simple Variable",
}
render := nest.MustRender(page)
outputPath := "templates/output/06-main-template-extension.js"
outputContents, err := ioutil.ReadFile(outputPath)
if err != nil {
t.Fatalf("error reading file (`%s`): %+v", outputPath, err)
}
assert.Equal(
t,
strings.TrimSpace(string(outputContents)),
render,
"Rendered output does not match expected output",
)
}
func TestRenderWithNoExtension(t *testing.T) {
ext := ""
nest, err := templatenest.New(templatenest.Option{
TemplateDir: "templates",
TemplateExtension: &ext,
})
if err != nil {
t.Fatalf("Failed to initialize TemplateNest: %+v", err)
}
page := map[string]interface{}{
"TEMPLATE": "00-simple-page.html",
"variable": "Simple Variable",
"simple_component": []interface{}{
map[string]interface{}{
"TEMPLATE": "01-simple-component.html",
"variable": "Simple Variable in Simple Component",
},
},
}
render := nest.MustRender(page)
outputPath := "templates/output/01-simple-page.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)),
render,
"Rendered output does not match expected output",
)
}

@ -0,0 +1,77 @@
package tests
import (
"git.virtual.blue/tomgracey/template-nest-go"
"github.com/stretchr/testify/assert"
"io/ioutil"
"strings"
"testing"
)
func TestRenderWithAltDelim(t *testing.T) {
nest, err := templatenest.New(templatenest.Option{
TemplateDir: "templates",
Delimiters: [2]string{"<%", "%>"},
})
if err != nil {
t.Fatalf("Failed to initialize TemplateNest: %+v", err)
}
page := map[string]interface{}{
"TEMPLATE": "00-simple-page-alt-delim",
"variable": "Simple Variable",
"simple_component": map[string]interface{}{
"TEMPLATE": "01-simple-component-alt-delim",
"variable": "Simple Variable in Simple Component",
},
}
render := nest.MustRender(page)
outputPath := "templates/output/01-simple-page.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)),
render,
"Rendered output does not match expected output",
)
}
func TestRenderWithAltDelimAndFixedIndent(t *testing.T) {
nest, err := templatenest.New(templatenest.Option{
TemplateDir: "templates",
Delimiters: [2]string{"<%", "%>"},
FixedIndent: true,
})
if err != nil {
t.Fatalf("Failed to initialize TemplateNest: %+v", err)
}
page := map[string]interface{}{
"TEMPLATE": "00-simple-page-alt-delim",
"variable": "Simple Variable",
"simple_component": map[string]interface{}{
"TEMPLATE": "02-simple-component-multi-line-alt-delim",
},
}
render := nest.MustRender(page)
outputPath := "templates/output/07-simple-page-fixed-indent.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)),
render,
"Rendered output does not match expected output",
)
}