Attempt to port Perl5's Text::Xslate to Go
Go-Xslate Playground is a little toy that allows you to try out Xslate template rendering. Note that for obvious reasons you cannot use directives that use external templates, such as WRAPPEr and INCLUDE
If you find templates that you think should work but doesn't, please file an issue using this service. You need to first "share" the current template that you're using, and then copy that new generated URL to file the issue.
This is an attempt to port Text::Xslate from Perl5 to Go.
Xslate is an extremely powerful virtual machine based template engine.
I believe there are at least two reasons you would choose Xslate over the basic text/template or html/template packages:
Template flexibility
IMHO, the default TTerse syntax is much more expressive and flexible. With WRAPPERs and INCLUDEs, it is possible to write a very module set of templates. YMMV
Dynamic/Automatic Reloading
By default Xslate expects that your template live in the file system -- i.e. outside of your go code. While text/template expects that you manage loading of templates yourself. Xslate handles all this for you. It searches for templates in the specified path, does the compilation, and handles caching, both on memory and on file system.
Xslate is also designed to allow you to customize this behavior: It should be easy to create a template loader that loads from databases and cache into memcached and the like.
Currently:
- I'm aiming for port of most of TTerse syntax
- See VM Progress for what the this xslate virtual machine can handle
- VM TODO: cleanup, optimization
- Parser is about 90% finished.
- Compiler is about 90% finished.
- Pluggable syntax isn't implemented at all.
- Need to come up with ways to register functions.
For simple templates, you can already do:
package main
import (
"log"
"github.com/lestrrat/go-xslate"
)
func main() {
xt, err := xslate.New()
if err != nil { // xslate.New may barf because it by default tries to
// initialize stuff that may want to access the filesystem
log.Fatalf("Failed to create xslate: %s", err)
}
// This uses RenderString() -- which is fine in itself and as an example,
// but the real use case for Xslate is loading templates from other
// locations, such as the filesystem. For this case yo uprobably
// want to use RenderInto() or Render()
template := `Hello World, [% name %]!`
output, err := xt.RenderString(template, xslate.Vars { "name": "Bob" })
if err != nil {
log.Fatalf("Failed to render template: %s", err)
}
log.Printf(output)
}
See Supported Syntax (TTerse) for what's currently available
Currently the error reporting is a bit weak. What you can do when you debug or send me bug reports is to give me a stack trace, and also while you're at it, run your templates with XSLATE_DEBUG=1 environment variable. This will print out the AST and ByteCode structure that is being executed.
In Go, functions that are not part of current package namespace must be qualified with a package name, e.g.:
time.Now()
This works fine because you can specify this at compile time, but you can't resolve this at runtime... which is a problem for templates. The way to solve this is to register these functions as variables:
template = `
[% now() %]
`
tx.RenderString(template, xslate.Vars { "now": time.Now })
But this forces you to register these functions every time, as well as having to take the extra care to make names globally unique.
tx := xslate.New(
functions: map[string]FuncDepot {
// TODO: create pre-built "bundle" of these FuncDepot's
"time": FuncDepot { "Now": time.Now }
}
)
template := `
[% time.Now() %]
`
tx.RenderString(template, ...)
The original xslate, written for Perl5, has comparison operators for both numeric and string ("eq" vs "==", "ne" vs "!=", etc). In go-xslate, there's no distinction. Both are translated to the same opcode (XXX "we plan to", that is)
So these are the same:
[% IF x == 1 %]...[% END %]
[% IF x eq 1 %]...[% END %]
Only public struc fields are accessible from templates. This is a limitation of the Go language itself. However, in order to allow smooth(er) migration from p5-Text-Xslate to go-xslate, go-xslate automatically changes the field name's first character to uppercase.
So given a struct like this:
x struct { Value int }
You can access Value
via value
, which is common in p5-Text-Xslate
[% x.value # same as x.Value %]