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
type Option struct {
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
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
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
DefaultsNamespaceChar string
DefaultsNamespaceChar *string
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
}
@ -73,14 +73,17 @@ func New(opts Option) (*TemplateNest, error) {
if opts.CommentDelimiters == [2]string{} {
opts.CommentDelimiters = [2]string{"<!--", "-->"}
}
if opts.NameLabel == "" {
opts.NameLabel = "TEMPLATE"
if opts.NameLabel == nil {
nameLabel := "TEMPLATE"
opts.NameLabel = &nameLabel
}
if opts.TemplateExtension == "" {
opts.TemplateExtension = "html"
if opts.TemplateExtension == nil {
ext := "html"
opts.TemplateExtension = &ext
}
if opts.DefaultsNamespaceChar == "" {
opts.DefaultsNamespaceChar = "."
if opts.DefaultsNamespaceChar == nil {
namespaceChar := "."
opts.DefaultsNamespaceChar = &namespaceChar
}
if opts.Defaults == nil {
opts.Defaults = make(map[string]interface{})
@ -90,7 +93,7 @@ func New(opts Option) (*TemplateNest, error) {
nest := &TemplateNest{
option: opts,
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.
@ -99,8 +102,14 @@ func New(opts Option) (*TemplateNest, error) {
return err
}
// Skip directories
if info.IsDir() {
return nil
}
// 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
}
@ -117,7 +126,11 @@ func New(opts Option) (*TemplateNest, error) {
}
// 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
return nil
})
@ -128,20 +141,24 @@ func New(opts Option) (*TemplateNest, error) {
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.
func FlattenMap(input Hash, parentKey string, separator string) Hash {
func flattenMap(input Hash, parentKey string, separator *string) Hash {
result := make(map[string]interface{})
for key, value := range input {
fullKey := key
if parentKey != "" {
fullKey = parentKey + separator + key
fullKey = parentKey + *separator + key
}
// Check if the value is a nested map
if nestedMap, ok := value.(Hash); ok {
for k, v := range FlattenMap(nestedMap, fullKey, separator) {
result[k] = v
// If separator is nil then there is no point in the user passing us
// a nested map.
if separator != nil {
for k, v := range flattenMap(nestedMap, fullKey, separator) {
result[k] = v
}
}
} else {
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
// template.
func (nest *TemplateNest) getTemplateFilePath(templateName string) string {
if *nest.option.TemplateExtension == "" {
return filepath.Join(nest.option.TemplateDir, templateName)
}
return filepath.Join(
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) {
tLabel, ok := hash[nest.option.NameLabel]
tLabel, ok := hash[*nest.option.NameLabel]
if !ok {
return "", fmt.Errorf(
"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
// file and it's not the template label then it's a bad param.
_, exists := tIndex.VariableNames[k]
if !exists && k != nest.option.NameLabel {
if !exists && k != *nest.option.NameLabel {
return "", fmt.Errorf(
"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",
)
}