Inspection
A compiled template can tell you, without rendering it, what it will need. Use this when a template is saved or published, so mistakes are reported to its author instead of failing a live page.
All inspection methods look at every branch, including ones that may never run, and none of them needs data.
#The names a template reads
ExternalNames lists every root name the template reads without defining it itself, with where it is first used. Variables it assigns or captures, loop variables inside their loops, forloop inside loops and settings are not external. Pass the names of your globals to exclude them too.
package main
import (
"fmt"
"vascula.dev/vascula"
)
func main() {
tmpl, err := vascula.Compile(`{% assign n = cart.items.size %}
{{ site.name }}: {{ n }} items{% if customer %} for {{ customer.name }}{% endif %}
{% for item in cart.items %}{{ item.title }}{{ settings.separator }}{% endfor %}`)
if err != nil {
panic(err)
}
for _, use := range tmpl.ExternalNames("site") {
fmt.Printf("%s (line %d, col %d)\n", use.Name, use.Line, use.Col)
}
}cart (line 1, col 15)
customer (line 2, col 37)Compare the result with what the template will be given. Anything left over will fail the first render that reaches it:
allowed := map[string]bool{"cart": true} // the roots this template declared
for _, use := range tmpl.ExternalNames("site") {
if !allowed[use.Name] {
return fmt.Errorf("line %d: %q is not available here", use.Line, use.Name)
}
}One case it cannot catch: reading a variable before the assign that creates it has run. That is still found when rendering.
#The composition tree
RenderTargets lists every child name used with {% render %}, in order, without duplicates. Because names must be literal strings, this is the complete list.
RenderArguments lists, for each child, the argument names passed to it. A child's ExternalNames must be covered by its own allowance plus the arguments its callers pass.
package main
import (
"fmt"
"vascula.dev/vascula"
)
func main() {
page, _ := vascula.Compile(`{% render "header" %}{% for p in catalog.products %}{% render "card", product: p, compact: true %}{% endfor %}`)
card, _ := vascula.Compile(`<h2>{{ product.title }}</h2>{% unless compact %}{{ product.text }}{% endunless %}{{ shop.currency }}`)
fmt.Println(page.RenderTargets())
args := page.RenderArguments()
fmt.Println(args["card"])
passed := map[string]bool{}
for _, a := range args["card"] {
passed[a] = true
}
for _, use := range card.ExternalNames() {
if !passed[use.Name] {
fmt.Println("card needs an allowance for", use.Name)
}
}
}[header card]
[compact product]
card needs an allowance for shopWalk the tree from a page through RenderTargets of each child to find missing children and cycles before anything renders.
#Features used
UsesForm reports whether a template contains {% form %}, and UsesTag(name) whether it uses a host tag. Use them to check a template only uses what its context supports, or to prepare work only when needed (for example, issue a form token only for pages with forms).