Add initial nest version, add tests
This commit is contained in:
parent
0152ebeb91
commit
5b1368dca0
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
templates/* linguist-vendored
|
22
README.org
Normal file
22
README.org
Normal file
@ -0,0 +1,22 @@
|
||||
#+title: Template Nest
|
||||
#+subtitle: manipulate a generic template structure
|
||||
|
||||
~Template Nest~ is a template engine module for Go, designed to process nested
|
||||
templates quickly and efficiently.
|
||||
|
||||
For more details on the idea behind ~Template::Nest~ read:
|
||||
https://metacpan.org/pod/Template::Nest#DESCRIPTION and
|
||||
https://pypi.org/project/template-nest/.
|
||||
|
||||
* News
|
||||
|
||||
** v0.1.0 - Upcoming
|
||||
|
||||
+ Initial Release.
|
||||
|
||||
* Other Implementations
|
||||
|
||||
- [[https://metacpan.org/pod/Template::Nest][Template::Nest (Perl 5)]]
|
||||
- [[https://pypi.org/project/template-nest/][template-nest (Python)]]
|
||||
- [[https://raku.land/zef:jaffa4/Template::Nest::XS][Template::Nest::XS (Raku)]]
|
||||
- [[https://raku.land/zef:andinus/Template::Nest::Fast][Template::Nest::Fast (Raku)]]
|
11
go.mod
Normal file
11
go.mod
Normal file
@ -0,0 +1,11 @@
|
||||
module git.virtual.blue/tomgracey/template-nest-go
|
||||
|
||||
go 1.22
|
||||
|
||||
require github.com/stretchr/testify v1.9.0
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
10
go.sum
Normal file
10
go.sum
Normal file
@ -0,0 +1,10 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
262
template_nest.go
Normal file
262
template_nest.go
Normal file
@ -0,0 +1,262 @@
|
||||
package templatenest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Hash map[string]interface{}
|
||||
|
||||
// Option holds configuration for TemplateNest
|
||||
type Option struct {
|
||||
Delimiters [2]string
|
||||
NameLabel string
|
||||
TemplateDir string
|
||||
TemplateExtension string
|
||||
DieOnBadParams bool
|
||||
}
|
||||
|
||||
type TemplateNest struct {
|
||||
option Option
|
||||
cache map[string]TemplateFileIndex
|
||||
}
|
||||
|
||||
// TemplateFileIndex represents an indexed template file.
|
||||
type TemplateFileIndex struct {
|
||||
Contents string
|
||||
LastModified time.Time // Last modified timestamp of the template file
|
||||
Variables []TemplateFileVariable // List of variables in the template file
|
||||
VariableNames map[string]struct{} // Set of variable names
|
||||
}
|
||||
|
||||
// TemplateFileVariable represents a variable in a template file.
|
||||
type TemplateFileVariable struct {
|
||||
Name string
|
||||
StartPosition uint // Start position of the complete variable string (including delimiters)
|
||||
EndPosition uint // End position of the complete variable string (including delimiters)
|
||||
IndentLevel uint // Indentation level of the variable
|
||||
EscapedToken bool // Indicates if the variable was escaped with token escape character
|
||||
}
|
||||
|
||||
func New(opts Option) (*TemplateNest, error) {
|
||||
// Check if the TemplateDir exists and is a directory.
|
||||
templateDirInfo, err := os.Stat(opts.TemplateDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("template dir `%s` does not exist", opts.TemplateDir)
|
||||
}
|
||||
return nil, fmt.Errorf("error checking template dir: %w", err)
|
||||
}
|
||||
if !templateDirInfo.IsDir() {
|
||||
return nil, fmt.Errorf("template dir `%s` is not a directory", opts.TemplateDir)
|
||||
}
|
||||
|
||||
// Set defaults for options that the user hasn't provided.
|
||||
if opts.Delimiters == [2]string{} {
|
||||
opts.Delimiters = [2]string{"<!--%", "%-->"}
|
||||
}
|
||||
if opts.NameLabel == "" {
|
||||
opts.NameLabel = "TEMPLATE"
|
||||
}
|
||||
if opts.TemplateExtension == "" {
|
||||
opts.TemplateExtension = "html"
|
||||
}
|
||||
|
||||
// Initialize TemplateNest with the final options.
|
||||
nest := &TemplateNest{
|
||||
option: opts,
|
||||
cache: make(map[string]TemplateFileIndex),
|
||||
}
|
||||
|
||||
// Walk through the template directory and index the templates.
|
||||
err = filepath.Walk(opts.TemplateDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if the file has the correct extension.
|
||||
if !strings.HasSuffix(info.Name(), "."+opts.TemplateExtension) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Index the template and store it in the cache.
|
||||
templateIndex, err := nest.index(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the relative path of the file.
|
||||
relPath, err := filepath.Rel(opts.TemplateDir, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove the extension from the relative path.
|
||||
templateName := strings.TrimSuffix(relPath, "."+opts.TemplateExtension)
|
||||
nest.cache[templateName] = templateIndex
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nest, nil
|
||||
}
|
||||
|
||||
func (nest *TemplateNest) index(filePath string) (TemplateFileIndex, error) {
|
||||
contents, err := ioutil.ReadFile(filePath)
|
||||
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 {
|
||||
return TemplateFileIndex{}, fmt.Errorf("Error getting file (`%s`) info: %w", filePath, err)
|
||||
}
|
||||
|
||||
variableNames := make(map[string]struct{})
|
||||
variables := []TemplateFileVariable{}
|
||||
|
||||
delimiterStart := nest.option.Delimiters[0]
|
||||
delimiterEnd := nest.option.Delimiters[1]
|
||||
|
||||
re := regexp.MustCompile(fmt.Sprintf(
|
||||
"%s\\s*(.+?)\\s*%s", regexp.QuoteMeta(delimiterStart), regexp.QuoteMeta(delimiterEnd),
|
||||
))
|
||||
|
||||
matches := re.FindAllStringSubmatchIndex(string(contents), -1)
|
||||
for _, match := range matches {
|
||||
if len(match) < 4 {
|
||||
continue
|
||||
}
|
||||
|
||||
startIdx := match[0]
|
||||
endIdx := match[1]
|
||||
nameStartIdx := match[2]
|
||||
nameEndIdx := match[3]
|
||||
|
||||
varName := string(contents[nameStartIdx:nameEndIdx])
|
||||
variableNames[varName] = struct{}{}
|
||||
|
||||
variables = append(variables, TemplateFileVariable{
|
||||
Name: varName,
|
||||
StartPosition: uint(startIdx),
|
||||
EndPosition: uint(endIdx),
|
||||
})
|
||||
}
|
||||
|
||||
fileIndex := TemplateFileIndex{
|
||||
Contents: string(contents),
|
||||
LastModified: fileInfo.ModTime(),
|
||||
VariableNames: variableNames,
|
||||
Variables: variables,
|
||||
}
|
||||
|
||||
return fileIndex, nil
|
||||
}
|
||||
|
||||
// getTemplateFilePath takes a template name and returns the file path for the
|
||||
// template.
|
||||
func (nest *TemplateNest) getTemplateFilePath(templateName string) string {
|
||||
return filepath.Join(
|
||||
nest.option.TemplateDir,
|
||||
fmt.Sprintf("%s.%s", templateName, nest.option.TemplateExtension),
|
||||
)
|
||||
}
|
||||
|
||||
func (nest *TemplateNest) MustRender(toRender interface{}) string {
|
||||
render, err := nest.Render(toRender)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return render
|
||||
}
|
||||
|
||||
func (nest *TemplateNest) Render(toRender interface{}) (string, error) {
|
||||
switch v := toRender.(type) {
|
||||
case nil:
|
||||
return "", nil
|
||||
|
||||
case bool:
|
||||
return fmt.Sprintf("%t", v), nil
|
||||
|
||||
case string:
|
||||
return v, nil
|
||||
|
||||
case float64, int, int64:
|
||||
return fmt.Sprintf("%v", v), nil
|
||||
|
||||
case []Hash:
|
||||
var rendered strings.Builder
|
||||
for _, item := range v {
|
||||
renderedItem, err := nest.Render(item)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
rendered.WriteString(renderedItem)
|
||||
}
|
||||
return rendered.String(), nil
|
||||
|
||||
case Hash:
|
||||
tLabel, ok := v[nest.option.NameLabel]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("missing template label: %s", nest.option.NameLabel)
|
||||
}
|
||||
|
||||
tName, ok := tLabel.(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("invalid template label: %+v", tLabel)
|
||||
}
|
||||
|
||||
tFile := nest.getTemplateFilePath(tName)
|
||||
fileInfo, err := os.Stat(tFile)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error getting file info: %w", err)
|
||||
}
|
||||
|
||||
tIndex, exists := nest.cache[tName]
|
||||
|
||||
// If cache doesn't exist or has expired, re-index the file.
|
||||
if !exists || fileInfo.ModTime().After(tIndex.LastModified) {
|
||||
newIndex, err := nest.index(tFile)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("File index failed: %w", err)
|
||||
}
|
||||
tIndex = newIndex
|
||||
}
|
||||
|
||||
rendered := tIndex.Contents
|
||||
for i := len(tIndex.Variables) - 1; i >= 0; i-- {
|
||||
variable := tIndex.Variables[i]
|
||||
|
||||
// If the variable doesn't exist in template hash then replace it
|
||||
// with an empty string.
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// Replace in rendered template
|
||||
rendered = rendered[:variable.StartPosition] + replacement + rendered[variable.EndPosition:]
|
||||
}
|
||||
|
||||
return strings.TrimSpace(rendered), nil
|
||||
|
||||
default:
|
||||
return "", fmt.Errorf("Unsupported template type: %+v", v)
|
||||
}
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
package template_nest
|
||||
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
|
||||
type Nest struct {
|
||||
|
||||
templateLabel string
|
||||
templateDir string
|
||||
templates map[string]string
|
||||
tokenDelims [2]string
|
||||
|
||||
}
|
||||
|
||||
|
||||
func New( templates map[string]string ) Nest {
|
||||
|
||||
nest := Nest{
|
||||
templateLabel: "TEMPLATE",
|
||||
tokenDelims: [2]string{"<!--%", "%-->"},
|
||||
templates: templates,
|
||||
}
|
||||
|
||||
return nest
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
func (obj *Nest) Render ( sjson []byte ) string {
|
||||
|
||||
var nesti interface{}
|
||||
err := json.Unmarshal(sjson, &nesti)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to parse JSON")
|
||||
}
|
||||
|
||||
nested := nesti.(map[string]interface{})
|
||||
|
||||
html := obj._Render( nested )
|
||||
|
||||
return html
|
||||
|
||||
}
|
||||
|
||||
|
||||
func (obj *Nest) _Render( nested interface{} ) string{
|
||||
|
||||
var html string
|
||||
|
||||
switch vtype := nested.(type) {
|
||||
|
||||
case string:
|
||||
html = vtype
|
||||
|
||||
case []interface{}:
|
||||
|
||||
html = obj._RenderArray( vtype )
|
||||
|
||||
case map[string]interface{}:
|
||||
html = obj._RenderMap( vtype )
|
||||
|
||||
}
|
||||
|
||||
return html
|
||||
|
||||
}
|
||||
|
||||
|
||||
func (obj *Nest) _RenderArray( nested []interface{} ) string {
|
||||
|
||||
html := ""
|
||||
for v := range nested {
|
||||
html += obj._Render( v )
|
||||
}
|
||||
|
||||
return html
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
func (obj *Nest) _RenderMap( nested map[string]interface{} ) string {
|
||||
|
||||
templateName := nested[ obj.templateLabel ].(string)
|
||||
if templateName == "" {
|
||||
panic("Encountered map with no TEMPLATE label")
|
||||
}
|
||||
|
||||
template := obj.templates[ templateName ]
|
||||
|
||||
params := make(map[string]string)
|
||||
|
||||
for k, v := range nested {
|
||||
|
||||
if k == obj.templateLabel {
|
||||
continue
|
||||
}
|
||||
|
||||
params[ k ] = obj._Render( v )
|
||||
|
||||
}
|
||||
|
||||
html := obj._FillIn( templateName, template, params )
|
||||
|
||||
return html
|
||||
|
||||
}
|
||||
|
||||
|
||||
func (obj *Nest) _FillIn( templateName string, template string, params map[string]string ) string {
|
||||
|
||||
html := template
|
||||
for param, val := range params {
|
||||
|
||||
regex, err := regexp.Compile( obj.tokenDelims[0] + `\s*` + param + `\s*` + obj.tokenDelims[1] )
|
||||
if err != nil {
|
||||
panic( err )
|
||||
}
|
||||
|
||||
html = regex.ReplaceAllString( html, val )
|
||||
|
||||
}
|
||||
|
||||
return html
|
||||
}
|
42
test_nest.go
42
test_nest.go
@ -1,42 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"tntester/template_nest"
|
||||
)
|
||||
|
||||
func main(){
|
||||
|
||||
templates := map[string]string{
|
||||
"00-simple-page": `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Simple Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>A fairly simple page to test the performance of Template::Nest.</p>
|
||||
<p><!--% variable %--></p>
|
||||
<!--% simple_component %-->
|
||||
</body>
|
||||
</html>`, "00-simple-component": `<p><!--% variable %--></p>`}
|
||||
|
||||
json := []byte(`{
|
||||
"TEMPLATE": "00-simple-page",
|
||||
"variable": "Simple Variable",
|
||||
"simple_component": {
|
||||
"TEMPLATE":"01-simple-component",
|
||||
"variable": "Simple Variable in Simple Component"
|
||||
}
|
||||
}`)
|
||||
|
||||
|
||||
nest := template_nest.New( templates )
|
||||
|
||||
html := nest.Render( json )
|
||||
|
||||
fmt.Println( html )
|
||||
|
||||
|
||||
}
|
15
tests/00_basic_test.go
Normal file
15
tests/00_basic_test.go
Normal file
@ -0,0 +1,15 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"git.virtual.blue/tomgracey/template-nest-go"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestInitialize(t *testing.T) {
|
||||
_, err := templatenest.New(templatenest.Option{
|
||||
TemplateDir: "templates",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to initialize TemplateNest: %+v", err)
|
||||
}
|
||||
}
|
145
tests/01_render_test.go
Normal file
145
tests/01_render_test.go
Normal file
@ -0,0 +1,145 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"git.virtual.blue/tomgracey/template-nest-go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRenderSimplePage(t *testing.T) {
|
||||
nest, err := templatenest.New(templatenest.Option{
|
||||
TemplateDir: "templates",
|
||||
})
|
||||
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",
|
||||
"variable": "Simple Variable in Simple Component",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
outputPage := templatenest.Hash{"TEMPLATE": "output/01-simple-page"}
|
||||
|
||||
render, err := nest.Render(page)
|
||||
if err != nil {
|
||||
t.Fatalf("Render failed for page: %+v", err)
|
||||
}
|
||||
|
||||
outputRender := nest.MustRender(outputPage)
|
||||
|
||||
assert.Equal(t, outputRender, render, "Rendered output does not match expected output")
|
||||
}
|
||||
|
||||
func TestRenderIncompletePage(t *testing.T) {
|
||||
nest, err := templatenest.New(templatenest.Option{TemplateDir: "templates"})
|
||||
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",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
outputPage := templatenest.Hash{"TEMPLATE": "output/03-incomplete-page"}
|
||||
|
||||
render := nest.MustRender(page)
|
||||
outputRender := nest.MustRender(outputPage)
|
||||
|
||||
assert.Equal(t, outputRender, render, "Rendered output does not match expected output")
|
||||
}
|
||||
|
||||
func TestRenderComplexPage(t *testing.T) {
|
||||
nest, err := templatenest.New(templatenest.Option{TemplateDir: "templates"})
|
||||
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/02-complex-page"}
|
||||
|
||||
render := nest.MustRender(page)
|
||||
outputRender := nest.MustRender(outputPage)
|
||||
|
||||
assert.Equal(t, outputRender, render, "Rendered output does not match expected output")
|
||||
}
|
||||
|
||||
func TestRenderArrayOfTemplateHash(t *testing.T) {
|
||||
nest, err := templatenest.New(templatenest.Option{TemplateDir: "templates"})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to initialize TemplateNest: %+v", err)
|
||||
}
|
||||
|
||||
page := []templatenest.Hash{
|
||||
templatenest.Hash{
|
||||
"TEMPLATE": "01-simple-component",
|
||||
"variable": "This is a variable",
|
||||
},
|
||||
templatenest.Hash{
|
||||
"TEMPLATE": "01-simple-component",
|
||||
"variable": "This is another variable",
|
||||
},
|
||||
}
|
||||
|
||||
outputPage := templatenest.Hash{"TEMPLATE": "output/13-render-with-array-of-template-hash"}
|
||||
|
||||
render := nest.MustRender(page)
|
||||
outputRender := nest.MustRender(outputPage)
|
||||
|
||||
assert.Equal(t, outputRender, render, "Rendered output does not match expected output")
|
||||
}
|
13
tests/templates/00-simple-page-alt-delim.html
Normal file
13
tests/templates/00-simple-page-alt-delim.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Simple Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>A fairly simple page to test the performance of Template::Nest.</p>
|
||||
<p><% variable %></p>
|
||||
<% simple_component %>
|
||||
</body>
|
||||
</html>
|
13
tests/templates/00-simple-page.html
Normal file
13
tests/templates/00-simple-page.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Simple Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>A fairly simple page to test the performance of Template::Nest.</p>
|
||||
<p><!--% variable %--></p>
|
||||
<!--% simple_component %-->
|
||||
</body>
|
||||
</html>
|
1
tests/templates/01-simple-component-alt-delim.html
Normal file
1
tests/templates/01-simple-component-alt-delim.html
Normal file
@ -0,0 +1 @@
|
||||
<p><% variable %></p>
|
1
tests/templates/01-simple-component-token-escape.html
Normal file
1
tests/templates/01-simple-component-token-escape.html
Normal file
@ -0,0 +1 @@
|
||||
<p>\<!--% variable %--></p>
|
1
tests/templates/01-simple-component.html
Normal file
1
tests/templates/01-simple-component.html
Normal file
@ -0,0 +1 @@
|
||||
<p><!--% variable %--></p>
|
@ -0,0 +1,7 @@
|
||||
<p>
|
||||
This is a simple component on multiple lines.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This is used for fixed-indent testing.
|
||||
</p>
|
7
tests/templates/02-simple-component-multi-line.html
Normal file
7
tests/templates/02-simple-component-multi-line.html
Normal file
@ -0,0 +1,7 @@
|
||||
<p>
|
||||
This is a simple component on multiple lines.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This is used for fixed-indent testing.
|
||||
</p>
|
12
tests/templates/03-namespace-page.html
Normal file
12
tests/templates/03-namespace-page.html
Normal file
@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Simple Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>A fairly simple page to test the performance of Template::Nest.</p>
|
||||
<p><!--% space.inside %--></p>
|
||||
</body>
|
||||
</html>
|
1
tests/templates/03-var-at-begin.html
Normal file
1
tests/templates/03-var-at-begin.html
Normal file
@ -0,0 +1 @@
|
||||
<!--% variable %-->
|
29
tests/templates/10-complex-page.html
Normal file
29
tests/templates/10-complex-page.html
Normal file
@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title><!--% title %--></title>
|
||||
<link rel="stylesheet" href="/03-resources/style.css" />
|
||||
<!--% pre_body %-->
|
||||
</head>
|
||||
<body>
|
||||
<header id="navigation" class="p-navigation is-dark">
|
||||
<!--% navigation %-->
|
||||
</header>
|
||||
|
||||
<section class="p-strip--light is-bordered hero-section">
|
||||
<!--% hero_section %-->
|
||||
</section>
|
||||
|
||||
<main class="row">
|
||||
<!--% main_content %-->
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<h2>virtual.blue</h2>
|
||||
</footer>
|
||||
|
||||
<!--% post_footer %-->
|
||||
</body>
|
||||
</html>
|
20
tests/templates/11-navigation.html
Normal file
20
tests/templates/11-navigation.html
Normal file
@ -0,0 +1,20 @@
|
||||
<div class="p-navigation__row">
|
||||
<!--% banner %-->
|
||||
<nav class="p-navigation__nav" aria-label="Example sub navigation">
|
||||
<ul class="p-navigation__items">
|
||||
<!--% items %-->
|
||||
</ul>
|
||||
<ul class="p-navigation__items">
|
||||
<li class="p-navigation__item--dropdown-toggle" id="link-4">
|
||||
<a class="p-navigation__link" aria-controls="account-menu">
|
||||
My account
|
||||
</a>
|
||||
<ul class="p-navigation__dropdown--right" id="account-menu" aria-hidden="true">
|
||||
<li>
|
||||
<a href="#" class="p-navigation__dropdown-item">Sign out</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
11
tests/templates/12-navigation-banner.html
Normal file
11
tests/templates/12-navigation-banner.html
Normal file
@ -0,0 +1,11 @@
|
||||
<div class="p-navigation__banner">
|
||||
<div class="p-navigation__tagged-logo">
|
||||
<a class="p-navigation__link" href="#">
|
||||
<span class="p-navigation__logo-title">
|
||||
<img class="nav-vb-logo" src="https://virtual.blue/resources/img/vb-small-dark-bg.png" alt="virtual.blue logo">
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<a href="#navigation" class="p-navigation__toggle--open" title="menu">Menu</a>
|
||||
<a href="#navigation-closed" class="p-navigation__toggle--close" title="close menu">Close menu</a>
|
||||
</div>
|
17
tests/templates/13-navigation-item-00-services.html
Normal file
17
tests/templates/13-navigation-item-00-services.html
Normal file
@ -0,0 +1,17 @@
|
||||
<li class="p-navigation__item--dropdown-toggle" id="link-2">
|
||||
<a href="#link-2-menu" aria-controls="link-2-menu" class="p-navigation__link">Services</a>
|
||||
<ul class="p-navigation__dropdown" id="link-2-menu" aria-hidden="true">
|
||||
<li>
|
||||
<a href="#" class="p-navigation__dropdown-item">Web & Mobile Apps</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="p-navigation__dropdown-item">Legacy Code</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="p-navigation__dropdown-item">Migrations</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="p-navigation__dropdown-item">Replacement Systems</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
14
tests/templates/13-navigation-item-01-resources.html
Normal file
14
tests/templates/13-navigation-item-01-resources.html
Normal file
@ -0,0 +1,14 @@
|
||||
<li class="p-navigation__item--dropdown-toggle" id="link-3">
|
||||
<a href="#link-3-menu" aria-controls="link-3-menu" class="p-navigation__link">Resources</a>
|
||||
<ul class="p-navigation__dropdown" id="link-3-menu" aria-hidden="true">
|
||||
<li>
|
||||
<a href="#" class="p-navigation__dropdown-item">Point Drag Controls</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="p-navigation__dropdown-item">Delay Proxy</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="p-navigation__dropdown-item">TaskPipe</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
4
tests/templates/14-hero-section.html
Normal file
4
tests/templates/14-hero-section.html
Normal file
@ -0,0 +1,4 @@
|
||||
<div class="row">
|
||||
<h1>Software Systems</h1>
|
||||
<p>Quality web and app specialists at low cost. New app development. Web-scrapers and crawlers. Full systems. Legacy repairs.</p>
|
||||
</div>
|
14
tests/templates/15-isdc-card.html
Normal file
14
tests/templates/15-isdc-card.html
Normal file
@ -0,0 +1,14 @@
|
||||
<div class="p-card">
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<img src="https://virtual.blue/resources/img/isdc-zoom.png">
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<h3>Raku Prodigy ISDC</h3>
|
||||
<p class="p-card__content">
|
||||
Are you a rising coding talent with a flair for innovative system development?
|
||||
Make a name for yourself by winning our <a href="https://virtual.blue/isdc">web development competition</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
3
tests/templates/16-vb-brand-cards.html
Normal file
3
tests/templates/16-vb-brand-cards.html
Normal file
@ -0,0 +1,3 @@
|
||||
<div class="row brand-cards-wrapper">
|
||||
<!--% cards %-->
|
||||
</div>
|
9
tests/templates/17-vb-brand-card-00.html
Normal file
9
tests/templates/17-vb-brand-card-00.html
Normal file
@ -0,0 +1,9 @@
|
||||
<div class="<!--% parent_classes %-->">
|
||||
<div class="p-card__content">
|
||||
<img class="p-card__image" alt="" src="https://virtual.blue/resources/img/home/Cost%20Minimising.svg">
|
||||
<h4>
|
||||
Cost Minimising
|
||||
</h4>
|
||||
<p class="u-no-padding--bottom">Through our efficient approach to project management we are able to offer some of the lowest service rates in the industry - without compromising on quality.</p>
|
||||
</div>
|
||||
</div>
|
9
tests/templates/17-vb-brand-card-01.html
Normal file
9
tests/templates/17-vb-brand-card-01.html
Normal file
@ -0,0 +1,9 @@
|
||||
<div class="<!--% parent_classes %-->">
|
||||
<div class="p-card__content">
|
||||
<img class="p-card__image" alt="" src="https://virtual.blue/resources/img/home/Code%20Warranty.svg">
|
||||
<h4>
|
||||
Code Warranty
|
||||
</h4>
|
||||
<p class="u-no-padding--bottom">As standard we continue to provide support for 6 months after you have accepted the code. This includes explaining usage, performing minor adjustments and ironing out any bugs. Subject to contract terms.</p>
|
||||
</div>
|
||||
</div>
|
9
tests/templates/17-vb-brand-card-02.html
Normal file
9
tests/templates/17-vb-brand-card-02.html
Normal file
@ -0,0 +1,9 @@
|
||||
<div class="<!--% parent_classes %-->">
|
||||
<div class="p-card__content">
|
||||
<img class="p-card__image" alt="" src="https://virtual.blue/resources/img/home/Tailor%20Made%20Solutions.svg">
|
||||
<h4>
|
||||
Tailor Made Solutions
|
||||
</h4>
|
||||
<p class="u-no-padding--bottom">When evaluating your case we'll consider your individual requirements, your specific business needs and the particular problem you are facing - so we'll always propose solutions which are uniquely tailored to your situation.</p>
|
||||
</div>
|
||||
</div>
|
20
tests/templates/18-styles.html
Normal file
20
tests/templates/18-styles.html
Normal file
@ -0,0 +1,20 @@
|
||||
<style>
|
||||
.nav-vb-logo {
|
||||
max-width: 200px;
|
||||
}
|
||||
.hero-section {
|
||||
background-image: url('/03-resources/bg-header.jpg');
|
||||
background-size: cover;
|
||||
}
|
||||
main {
|
||||
margin-top: 1em;
|
||||
}
|
||||
main .brand-card img {
|
||||
max-width: 50%;
|
||||
margin: auto;
|
||||
}
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 4em;
|
||||
}
|
||||
</style>
|
49
tests/templates/19-scripts.html
Normal file
49
tests/templates/19-scripts.html
Normal file
@ -0,0 +1,49 @@
|
||||
<script>
|
||||
function toggleDropdown(toggle, open) {
|
||||
let parentElement = toggle.parentNode;
|
||||
let dropdown = document.getElementById(toggle.getAttribute('aria-controls'));
|
||||
dropdown.setAttribute('aria-hidden', !open);
|
||||
|
||||
if (open) {
|
||||
parentElement.classList.add('is-active');
|
||||
} else {
|
||||
parentElement.classList.remove('is-active');
|
||||
}
|
||||
}
|
||||
|
||||
function closeAllDropdowns(toggles) {
|
||||
toggles.forEach(function (toggle) {
|
||||
toggleDropdown(toggle, false);
|
||||
});
|
||||
}
|
||||
|
||||
function handleClickOutside(toggles, containerClass) {
|
||||
document.addEventListener('click', function (event) {
|
||||
let target = event.target;
|
||||
|
||||
if (target.closest) {
|
||||
if (!target.closest(containerClass)) {
|
||||
closeAllDropdowns(toggles);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initNavDropdowns(containerClass) {
|
||||
let toggles = [].slice.call(document.querySelectorAll(containerClass + ' [aria-controls]'));
|
||||
|
||||
handleClickOutside(toggles, containerClass);
|
||||
|
||||
toggles.forEach(function (toggle) {
|
||||
toggle.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const shouldOpen = !toggle.parentNode.classList.contains('is-active');
|
||||
closeAllDropdowns(toggles);
|
||||
toggleDropdown(toggle, shouldOpen);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
initNavDropdowns('.p-navigation__item--dropdown-toggle');
|
||||
</script>
|
3
tests/templates/30-main.js
Normal file
3
tests/templates/30-main.js
Normal file
@ -0,0 +1,3 @@
|
||||
const tmp = () => {
|
||||
/* <!--% var %--> */
|
||||
};
|
13
tests/templates/output/01-simple-page.html
Normal file
13
tests/templates/output/01-simple-page.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Simple Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>A fairly simple page to test the performance of Template::Nest.</p>
|
||||
<p>Simple Variable</p>
|
||||
<p>Simple Variable in Simple Component</p>
|
||||
</body>
|
||||
</html>
|
196
tests/templates/output/02-complex-page.html
Normal file
196
tests/templates/output/02-complex-page.html
Normal file
@ -0,0 +1,196 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>Complex Page</title>
|
||||
<link rel="stylesheet" href="/03-resources/style.css" />
|
||||
<style>
|
||||
.nav-vb-logo {
|
||||
max-width: 200px;
|
||||
}
|
||||
.hero-section {
|
||||
background-image: url('/03-resources/bg-header.jpg');
|
||||
background-size: cover;
|
||||
}
|
||||
main {
|
||||
margin-top: 1em;
|
||||
}
|
||||
main .brand-card img {
|
||||
max-width: 50%;
|
||||
margin: auto;
|
||||
}
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 4em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header id="navigation" class="p-navigation is-dark">
|
||||
<div class="p-navigation__row">
|
||||
<div class="p-navigation__banner">
|
||||
<div class="p-navigation__tagged-logo">
|
||||
<a class="p-navigation__link" href="#">
|
||||
<span class="p-navigation__logo-title">
|
||||
<img class="nav-vb-logo" src="https://virtual.blue/resources/img/vb-small-dark-bg.png" alt="virtual.blue logo">
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<a href="#navigation" class="p-navigation__toggle--open" title="menu">Menu</a>
|
||||
<a href="#navigation-closed" class="p-navigation__toggle--close" title="close menu">Close menu</a>
|
||||
</div>
|
||||
<nav class="p-navigation__nav" aria-label="Example sub navigation">
|
||||
<ul class="p-navigation__items">
|
||||
<li class="p-navigation__item--dropdown-toggle" id="link-2">
|
||||
<a href="#link-2-menu" aria-controls="link-2-menu" class="p-navigation__link">Services</a>
|
||||
<ul class="p-navigation__dropdown" id="link-2-menu" aria-hidden="true">
|
||||
<li>
|
||||
<a href="#" class="p-navigation__dropdown-item">Web & Mobile Apps</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="p-navigation__dropdown-item">Legacy Code</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="p-navigation__dropdown-item">Migrations</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="p-navigation__dropdown-item">Replacement Systems</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li><li class="p-navigation__item--dropdown-toggle" id="link-3">
|
||||
<a href="#link-3-menu" aria-controls="link-3-menu" class="p-navigation__link">Resources</a>
|
||||
<ul class="p-navigation__dropdown" id="link-3-menu" aria-hidden="true">
|
||||
<li>
|
||||
<a href="#" class="p-navigation__dropdown-item">Point Drag Controls</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="p-navigation__dropdown-item">Delay Proxy</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="p-navigation__dropdown-item">TaskPipe</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="p-navigation__items">
|
||||
<li class="p-navigation__item--dropdown-toggle" id="link-4">
|
||||
<a class="p-navigation__link" aria-controls="account-menu">
|
||||
My account
|
||||
</a>
|
||||
<ul class="p-navigation__dropdown--right" id="account-menu" aria-hidden="true">
|
||||
<li>
|
||||
<a href="#" class="p-navigation__dropdown-item">Sign out</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="p-strip--light is-bordered hero-section">
|
||||
<div class="row">
|
||||
<h1>Software Systems</h1>
|
||||
<p>Quality web and app specialists at low cost. New app development. Web-scrapers and crawlers. Full systems. Legacy repairs.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<main class="row">
|
||||
<div class="p-card">
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<img src="https://virtual.blue/resources/img/isdc-zoom.png">
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<h3>Raku Prodigy ISDC</h3>
|
||||
<p class="p-card__content">
|
||||
Are you a rising coding talent with a flair for innovative system development?
|
||||
Make a name for yourself by winning our <a href="https://virtual.blue/isdc">web development competition</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div><div class="row brand-cards-wrapper">
|
||||
<div class="p-card brand-card col-4">
|
||||
<div class="p-card__content">
|
||||
<img class="p-card__image" alt="" src="https://virtual.blue/resources/img/home/Cost%20Minimising.svg">
|
||||
<h4>
|
||||
Cost Minimising
|
||||
</h4>
|
||||
<p class="u-no-padding--bottom">Through our efficient approach to project management we are able to offer some of the lowest service rates in the industry - without compromising on quality.</p>
|
||||
</div>
|
||||
</div><div class="p-card brand-card col-4">
|
||||
<div class="p-card__content">
|
||||
<img class="p-card__image" alt="" src="https://virtual.blue/resources/img/home/Code%20Warranty.svg">
|
||||
<h4>
|
||||
Code Warranty
|
||||
</h4>
|
||||
<p class="u-no-padding--bottom">As standard we continue to provide support for 6 months after you have accepted the code. This includes explaining usage, performing minor adjustments and ironing out any bugs. Subject to contract terms.</p>
|
||||
</div>
|
||||
</div><div class="p-card brand-card col-4">
|
||||
<div class="p-card__content">
|
||||
<img class="p-card__image" alt="" src="https://virtual.blue/resources/img/home/Tailor%20Made%20Solutions.svg">
|
||||
<h4>
|
||||
Tailor Made Solutions
|
||||
</h4>
|
||||
<p class="u-no-padding--bottom">When evaluating your case we'll consider your individual requirements, your specific business needs and the particular problem you are facing - so we'll always propose solutions which are uniquely tailored to your situation.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<h2>virtual.blue</h2>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
function toggleDropdown(toggle, open) {
|
||||
let parentElement = toggle.parentNode;
|
||||
let dropdown = document.getElementById(toggle.getAttribute('aria-controls'));
|
||||
dropdown.setAttribute('aria-hidden', !open);
|
||||
|
||||
if (open) {
|
||||
parentElement.classList.add('is-active');
|
||||
} else {
|
||||
parentElement.classList.remove('is-active');
|
||||
}
|
||||
}
|
||||
|
||||
function closeAllDropdowns(toggles) {
|
||||
toggles.forEach(function (toggle) {
|
||||
toggleDropdown(toggle, false);
|
||||
});
|
||||
}
|
||||
|
||||
function handleClickOutside(toggles, containerClass) {
|
||||
document.addEventListener('click', function (event) {
|
||||
let target = event.target;
|
||||
|
||||
if (target.closest) {
|
||||
if (!target.closest(containerClass)) {
|
||||
closeAllDropdowns(toggles);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initNavDropdowns(containerClass) {
|
||||
let toggles = [].slice.call(document.querySelectorAll(containerClass + ' [aria-controls]'));
|
||||
|
||||
handleClickOutside(toggles, containerClass);
|
||||
|
||||
toggles.forEach(function (toggle) {
|
||||
toggle.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const shouldOpen = !toggle.parentNode.classList.contains('is-active');
|
||||
closeAllDropdowns(toggles);
|
||||
toggleDropdown(toggle, shouldOpen);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
initNavDropdowns('.p-navigation__item--dropdown-toggle');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
13
tests/templates/output/03-incomplete-page.html
Normal file
13
tests/templates/output/03-incomplete-page.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Simple Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>A fairly simple page to test the performance of Template::Nest.</p>
|
||||
<p>Simple Variable</p>
|
||||
<p></p>
|
||||
</body>
|
||||
</html>
|
17
tests/templates/output/04-simple-page-with-labels.html
Normal file
17
tests/templates/output/04-simple-page-with-labels.html
Normal file
@ -0,0 +1,17 @@
|
||||
<!-- BEGIN 00-simple-page -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Simple Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>A fairly simple page to test the performance of Template::Nest.</p>
|
||||
<p>Simple Variable</p>
|
||||
<!-- BEGIN 01-simple-component -->
|
||||
<p>Simple Variable in Simple Component</p>
|
||||
<!-- END 01-simple-component -->
|
||||
</body>
|
||||
</html>
|
||||
<!-- END 00-simple-page -->
|
@ -0,0 +1,17 @@
|
||||
<!--! BEGIN 00-simple-page !-->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Simple Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>A fairly simple page to test the performance of Template::Nest.</p>
|
||||
<p>Simple Variable</p>
|
||||
<!--! BEGIN 01-simple-component !-->
|
||||
<p>Simple Variable in Simple Component</p>
|
||||
<!--! END 01-simple-component !-->
|
||||
</body>
|
||||
</html>
|
||||
<!--! END 00-simple-page !-->
|
3
tests/templates/output/06-main-template-extension.js
Normal file
3
tests/templates/output/06-main-template-extension.js
Normal file
@ -0,0 +1,3 @@
|
||||
const tmp = () => {
|
||||
/* Simple Variable */
|
||||
};
|
19
tests/templates/output/07-simple-page-fixed-indent.html
Normal file
19
tests/templates/output/07-simple-page-fixed-indent.html
Normal file
@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Simple Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>A fairly simple page to test the performance of Template::Nest.</p>
|
||||
<p>Simple Variable</p>
|
||||
<p>
|
||||
This is a simple component on multiple lines.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This is used for fixed-indent testing.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
196
tests/templates/output/08-complex-page-fixed-indent.html
Normal file
196
tests/templates/output/08-complex-page-fixed-indent.html
Normal file
@ -0,0 +1,196 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>Complex Page</title>
|
||||
<link rel="stylesheet" href="/03-resources/style.css" />
|
||||
<style>
|
||||
.nav-vb-logo {
|
||||
max-width: 200px;
|
||||
}
|
||||
.hero-section {
|
||||
background-image: url('/03-resources/bg-header.jpg');
|
||||
background-size: cover;
|
||||
}
|
||||
main {
|
||||
margin-top: 1em;
|
||||
}
|
||||
main .brand-card img {
|
||||
max-width: 50%;
|
||||
margin: auto;
|
||||
}
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 4em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header id="navigation" class="p-navigation is-dark">
|
||||
<div class="p-navigation__row">
|
||||
<div class="p-navigation__banner">
|
||||
<div class="p-navigation__tagged-logo">
|
||||
<a class="p-navigation__link" href="#">
|
||||
<span class="p-navigation__logo-title">
|
||||
<img class="nav-vb-logo" src="https://virtual.blue/resources/img/vb-small-dark-bg.png" alt="virtual.blue logo">
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<a href="#navigation" class="p-navigation__toggle--open" title="menu">Menu</a>
|
||||
<a href="#navigation-closed" class="p-navigation__toggle--close" title="close menu">Close menu</a>
|
||||
</div>
|
||||
<nav class="p-navigation__nav" aria-label="Example sub navigation">
|
||||
<ul class="p-navigation__items">
|
||||
<li class="p-navigation__item--dropdown-toggle" id="link-2">
|
||||
<a href="#link-2-menu" aria-controls="link-2-menu" class="p-navigation__link">Services</a>
|
||||
<ul class="p-navigation__dropdown" id="link-2-menu" aria-hidden="true">
|
||||
<li>
|
||||
<a href="#" class="p-navigation__dropdown-item">Web & Mobile Apps</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="p-navigation__dropdown-item">Legacy Code</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="p-navigation__dropdown-item">Migrations</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="p-navigation__dropdown-item">Replacement Systems</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li><li class="p-navigation__item--dropdown-toggle" id="link-3">
|
||||
<a href="#link-3-menu" aria-controls="link-3-menu" class="p-navigation__link">Resources</a>
|
||||
<ul class="p-navigation__dropdown" id="link-3-menu" aria-hidden="true">
|
||||
<li>
|
||||
<a href="#" class="p-navigation__dropdown-item">Point Drag Controls</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="p-navigation__dropdown-item">Delay Proxy</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="p-navigation__dropdown-item">TaskPipe</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="p-navigation__items">
|
||||
<li class="p-navigation__item--dropdown-toggle" id="link-4">
|
||||
<a class="p-navigation__link" aria-controls="account-menu">
|
||||
My account
|
||||
</a>
|
||||
<ul class="p-navigation__dropdown--right" id="account-menu" aria-hidden="true">
|
||||
<li>
|
||||
<a href="#" class="p-navigation__dropdown-item">Sign out</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="p-strip--light is-bordered hero-section">
|
||||
<div class="row">
|
||||
<h1>Software Systems</h1>
|
||||
<p>Quality web and app specialists at low cost. New app development. Web-scrapers and crawlers. Full systems. Legacy repairs.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<main class="row">
|
||||
<div class="p-card">
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<img src="https://virtual.blue/resources/img/isdc-zoom.png">
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<h3>Raku Prodigy ISDC</h3>
|
||||
<p class="p-card__content">
|
||||
Are you a rising coding talent with a flair for innovative system development?
|
||||
Make a name for yourself by winning our <a href="https://virtual.blue/isdc">web development competition</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div><div class="row brand-cards-wrapper">
|
||||
<div class="p-card brand-card col-4">
|
||||
<div class="p-card__content">
|
||||
<img class="p-card__image" alt="" src="https://virtual.blue/resources/img/home/Cost%20Minimising.svg">
|
||||
<h4>
|
||||
Cost Minimising
|
||||
</h4>
|
||||
<p class="u-no-padding--bottom">Through our efficient approach to project management we are able to offer some of the lowest service rates in the industry - without compromising on quality.</p>
|
||||
</div>
|
||||
</div><div class="p-card brand-card col-4">
|
||||
<div class="p-card__content">
|
||||
<img class="p-card__image" alt="" src="https://virtual.blue/resources/img/home/Code%20Warranty.svg">
|
||||
<h4>
|
||||
Code Warranty
|
||||
</h4>
|
||||
<p class="u-no-padding--bottom">As standard we continue to provide support for 6 months after you have accepted the code. This includes explaining usage, performing minor adjustments and ironing out any bugs. Subject to contract terms.</p>
|
||||
</div>
|
||||
</div><div class="p-card brand-card col-4">
|
||||
<div class="p-card__content">
|
||||
<img class="p-card__image" alt="" src="https://virtual.blue/resources/img/home/Tailor%20Made%20Solutions.svg">
|
||||
<h4>
|
||||
Tailor Made Solutions
|
||||
</h4>
|
||||
<p class="u-no-padding--bottom">When evaluating your case we'll consider your individual requirements, your specific business needs and the particular problem you are facing - so we'll always propose solutions which are uniquely tailored to your situation.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<h2>virtual.blue</h2>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
function toggleDropdown(toggle, open) {
|
||||
let parentElement = toggle.parentNode;
|
||||
let dropdown = document.getElementById(toggle.getAttribute('aria-controls'));
|
||||
dropdown.setAttribute('aria-hidden', !open);
|
||||
|
||||
if (open) {
|
||||
parentElement.classList.add('is-active');
|
||||
} else {
|
||||
parentElement.classList.remove('is-active');
|
||||
}
|
||||
}
|
||||
|
||||
function closeAllDropdowns(toggles) {
|
||||
toggles.forEach(function (toggle) {
|
||||
toggleDropdown(toggle, false);
|
||||
});
|
||||
}
|
||||
|
||||
function handleClickOutside(toggles, containerClass) {
|
||||
document.addEventListener('click', function (event) {
|
||||
let target = event.target;
|
||||
|
||||
if (target.closest) {
|
||||
if (!target.closest(containerClass)) {
|
||||
closeAllDropdowns(toggles);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initNavDropdowns(containerClass) {
|
||||
let toggles = [].slice.call(document.querySelectorAll(containerClass + ' [aria-controls]'));
|
||||
|
||||
handleClickOutside(toggles, containerClass);
|
||||
|
||||
toggles.forEach(function (toggle) {
|
||||
toggle.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const shouldOpen = !toggle.parentNode.classList.contains('is-active');
|
||||
closeAllDropdowns(toggles);
|
||||
toggleDropdown(toggle, shouldOpen);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
initNavDropdowns('.p-navigation__item--dropdown-toggle');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
13
tests/templates/output/09-simple-page-token-escape.html
Normal file
13
tests/templates/output/09-simple-page-token-escape.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Simple Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>A fairly simple page to test the performance of Template::Nest.</p>
|
||||
<p>Simple Variable</p>
|
||||
<p><!--% variable %--></p>
|
||||
</body>
|
||||
</html>
|
1
tests/templates/output/10-var-at-begin.html
Normal file
1
tests/templates/output/10-var-at-begin.html
Normal file
@ -0,0 +1 @@
|
||||
Simple Variable
|
12
tests/templates/output/11-namespace-page.html
Normal file
12
tests/templates/output/11-namespace-page.html
Normal file
@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Simple Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>A fairly simple page to test the performance of Template::Nest.</p>
|
||||
<p>A variable inside a space.</p>
|
||||
</body>
|
||||
</html>
|
13
tests/templates/output/12-simple-page-arrays.html
Normal file
13
tests/templates/output/12-simple-page-arrays.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Simple Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>A fairly simple page to test the performance of Template::Nest.</p>
|
||||
<p>Simple Variable</p>
|
||||
<p>Simple Variable in Simple Component</p><strong>Another test</strong><p>Simple Variable in Simple Component</p><strong>Another nested test 2</strong>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1 @@
|
||||
<p>This is a variable</p><p>This is another variable</p>
|
Loading…
Reference in New Issue
Block a user