diff --git a/template_nest.go b/template_nest.go index eb3f453..466e5c1 100644 --- a/template_nest.go +++ b/template_nest.go @@ -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, ) diff --git a/tests/04_template_extension_test.go b/tests/04_template_extension_test.go new file mode 100644 index 0000000..52bddf4 --- /dev/null +++ b/tests/04_template_extension_test.go @@ -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", + ) +} diff --git a/tests/06_token_delims_test.go b/tests/06_token_delims_test.go new file mode 100644 index 0000000..d5d437a --- /dev/null +++ b/tests/06_token_delims_test.go @@ -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", + ) +}