Add initial nest version, add tests

This commit is contained in:
Andinus 2024-11-16 09:43:48 +05:30
parent 0152ebeb91
commit 5b1368dca0
Signed by: andinus
GPG Key ID: B67D55D482A799FD
45 changed files with 1247 additions and 173 deletions

1
.gitattributes vendored Normal file

@ -0,0 +1 @@
templates/* linguist-vendored

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

@ -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

@ -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

@ -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
}

@ -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

@ -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

@ -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")
}

@ -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>

@ -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>

@ -0,0 +1 @@
<p><% variable %></p>

@ -0,0 +1 @@
<p>\<!--% variable %--></p>

@ -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>

@ -0,0 +1,7 @@
<p>
This is a simple component on multiple lines.
</p>
<p>
This is used for fixed-indent testing.
</p>

@ -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>

@ -0,0 +1 @@
<!--% variable %-->

@ -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>

@ -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>

@ -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>

@ -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>

@ -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>

@ -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>

@ -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>

@ -0,0 +1,3 @@
<div class="row brand-cards-wrapper">
<!--% cards %-->
</div>

@ -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>

@ -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>

@ -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>

@ -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>

@ -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>

@ -0,0 +1,3 @@
const tmp = () => {
/* <!--% var %--> */
};

@ -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>

@ -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>

@ -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>

@ -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 !-->

@ -0,0 +1,3 @@
const tmp = () => {
/* Simple Variable */
};

@ -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>

@ -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>

@ -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>

@ -0,0 +1 @@
Simple Variable

@ -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>

@ -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>