Documentation ¶
Overview ¶
Package stick is a Go language port of the Twig templating engine.
Stick executes Twig templates and allows users to define custom Functions, Filters, and Tests. The parser allows parse-time node inspection with NodeVisitors, and a template Loader to load named templates from any source.
Twig compatibility ¶
Stick itself is a parser and template executor. If you're looking for Twig compatibility, check out package https://godoc.org/bitbucket.org/ticketshopdev/stick/twig
For additional information on Twig, check http://twig.sensiolabs.org/
Basic usage ¶
Obligatory "Hello, World!" example:
env := stick.New(nil); // A nil loader means stick will simply execute // the string passed into env.Execute. // Templates receive a map of string to any value. p := map[string]stick.Value{"name": "World"} // Substitute os.Stdout with any io.Writer. env.Execute("Hello, {{ name }}!", os.Stdout, p)
Another example, using a FilesystemLoader and responding to an HTTP request:
import "net/http" // ... fsRoot := os.Getwd() // Templates are loaded relative to this directory. env := stick.New(stick.NewFilesystemLoader(fsRoot)) http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) { env.Execute("bar.html.twig", w, nil) // Loads "bar.html.twig" relative to fsRoot. }) http.ListenAndServe(":80", nil)
Types and values ¶
Any user value in Stick is represented by a stick.Value. There are three main types in Stick when it comes to built-in operations: strings, numbers, and booleans. Of note, numbers are represented by float64 as this matches regular Twig behavior most closely.
Stick makes no restriction on what is stored in a stick.Value, but some built-in operators will try to coerce a value into a boolean, string, or number depending on the operation.
Additionally, custom types that implement specific interfaces can be coerced. Stick defines three interfaces: Stringer, Number, and Boolean. Each interface defines a single method that should convert a custom type into the specified type.
type myType struct { // ... } func (t *myType) String() string { return fmt.Sprintf("%v", t.someField) } func (t *myType) Number() float64 { return t.someFloatField } func (t *myType) Boolean() bool { return t.someValue != nil }
On a final note, there exists three functions to coerce any type into a string, number, or boolean, respectively.
// Coerce any value to a string v := stick.CoerceString(anything) // Coerce any value to a float64 f := stick.CoerceNumber(anything) // Coerce any vale to a boolean b := stick.CoerceBool(anything)
User defined helpers ¶
It is possible to define custom Filters, Functions, and boolean Tests available to your Stick templates. Each user-defined type is simply a function with a specific signature.
A Func represents a user-defined function.
type Func func(e *Env, args ...Value) Value
Functions can be called anywhere expressions are allowed. Functions may take any number of arguments.
A Filter is a user-defined filter.
type Filter func(e *Env, val Value, args ...Value) Value
Filters receive a value and modify it in some way. Filters also accept zero or more arguments beyond the value to be filtered.
A Test represents a user-defined boolean test.
type Test func(e *Env, val Value, args ...Value) bool
Tests are used to make some comparisons more expressive. Tests also accept zero to any number of arguments, and Test names can contain up to one space.
User-defined types are added to an Env after it is created. For example:
env := stick.New(nil) env.Functions["form_valid"] = func(e *stick.Env, args ...stick.Value) stick.Value { // Do something useful.. return true } env.Filters["number_format"] = func(e *stick.Env, val stick.Value, args ...stick.Value) stick.Value { v := stick.CoerceNumber(val) // Do some formatting. return fmt.Sprintf("%.2d", v) } env.Tests["empty"] = func(e *stick.Env, val stick.Value, args ...stick.Value) bool { // Probably not that useful. return stick.CoerceBool(val) == false }
Index ¶
- func CoerceBool(v Value) bool
- func CoerceNumber(v Value) float64
- func CoerceString(v Value) string
- func Contains(haystack Value, needle Value) (bool, error)
- func Equal(left Value, right Value) bool
- func IsArray(val Value) bool
- func IsIterable(val Value) bool
- func IsMap(val Value) bool
- func Iterate(val Value, it Iteratee) (int, error)
- func Len(val Value) (int, error)
- type Boolean
- type Context
- type ContextMetadata
- type ContextScope
- type Env
- type Extension
- type FilesystemLoader
- type Filter
- type Func
- type Iteratee
- type Loader
- type Loop
- type MemoryLoader
- type Number
- type SafeValue
- type StringLoader
- type Stringer
- type Template
- type Test
- type Value
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CoerceBool ¶
CoerceBool coerces the given value into a boolean. Boolean false is returned if the value cannot be coerced.
Example ¶
This example demonstrates how various values are coerced to boolean.
package main import ( "fmt" "bitbucket.org/ticketshopdev/stick" ) func main() { v0 := "" v1 := "some string" v2 := 0 v3 := 3.14 fmt.Printf("%t %t %t %t", stick.CoerceBool(v0), stick.CoerceBool(v1), stick.CoerceBool(v2), stick.CoerceBool(v3)) }
Output: false true false true
func CoerceNumber ¶
CoerceNumber coerces the given value into a number. Zero (0) is returned if the value cannot be coerced.
Example ¶
This example demonstrates how various values are coerced to number.
package main import ( "fmt" "bitbucket.org/ticketshopdev/stick" ) func main() { v0 := true v1 := "" v2 := "54" v3 := "1.33" fmt.Printf("%.f %.f %.f %.2f", stick.CoerceNumber(v0), stick.CoerceNumber(v1), stick.CoerceNumber(v2), stick.CoerceNumber(v3)) }
Output: 1 0 54 1.33
func CoerceString ¶
CoerceString coerces the given value into a string. An empty string is returned if the value cannot be coerced.
Example ¶
This demonstrates how various values are coerced to string.
package main import ( "fmt" "bitbucket.org/ticketshopdev/stick" ) func main() { v0 := true v1 := false // Coerces into "" v2 := 54 v3 := 1.33 v4 := 0 fmt.Printf("%s '%s' %s %s %s", stick.CoerceString(v0), stick.CoerceString(v1), stick.CoerceString(v2), stick.CoerceString(v3), stick.CoerceString(v4)) }
Output: 1 '' 54 1.33 0
func IsIterable ¶
IsIterable returns true if the given Value is a slice, array, or map.
Types ¶
type Boolean ¶
type Boolean interface { // Boolean returns a boolean representation of the type. Boolean() bool }
Boolean is implemented by any value that has a Boolean method.
Example ¶
This demonstrates how a type can be coerced to a boolean. The struct in this example has the Boolean method implemented.
func (e exampleType) Boolean() bool { return true }
package main import ( "fmt" "bitbucket.org/ticketshopdev/stick" ) type exampleType struct{} func (e exampleType) Boolean() bool { return true } func (e exampleType) Number() float64 { return 3.14 } func (e exampleType) String() string { return "some kinda string" } func main() { v := exampleType{} fmt.Printf("%t", stick.CoerceBool(v)) }
Output: true
type Context ¶
type Context interface { Name() string // The name of the template being executed. Meta() ContextMetadata // Runtime metadata about the template. Scope() ContextScope // All defined root-level names. Env() *Env // contains filtered or unexported methods }
A Context represents the execution context of a template.
The Context is passed to all user-defined functions, filters, tests, and node visitors. It can be used to affect and inspect the local environment while a template is executing.
type ContextMetadata ¶
type ContextMetadata interface { All() map[string]string // Returns a map of all attributes and values. Set(name, val string) // Set a metadata attribute on the context. Get(name string) (string, bool) // Get a metadata attribute on the context. // contains filtered or unexported methods }
ContextMetadata contains additional, unstructured runtime attributes about the template being executed.
type ContextScope ¶
type ContextScope interface { All() map[string]Value // Returns a map of all values defined in the scope. Get(string) (Value, bool) // Get a value defined in the scope. Set(string, Value) // Set a value in the scope. // contains filtered or unexported methods }
ContextScope provides an interface with the currently executing template's scope.
type Env ¶
type Env struct { Loader Loader // Template loader. Functions map[string]Func // User-defined functions. Filters map[string]Filter // User-defined filters. Tests map[string]Test // User-defined tests. Visitors []parse.NodeVisitor // User-defined node visitors. }
Env represents a configured Stick environment.
func (*Env) Execute ¶
Execute parses and executes the given template.
Example ¶
An example of executing a template in the simplest possible manner.
package main import ( "fmt" "os" "bitbucket.org/ticketshopdev/stick" ) func main() { env := stick.New(nil) params := map[string]stick.Value{"name": "World"} err := env.Execute(`Hello, {{ name }}!`, os.Stdout, params) if err != nil { fmt.Println(err) } }
Output: Hello, World!
Example (FilesystemLoader) ¶
An example showing the use of the provided FilesystemLoader.
This example makes use of templates in the testdata folder. In particular, this example shows vertical (via extends) and horizontal reuse (via use).
package main import ( "fmt" "os" "path/filepath" "bitbucket.org/ticketshopdev/stick" ) func main() { d, _ := os.Getwd() env := stick.New(stick.NewFilesystemLoader(filepath.Join(d, "testdata"))) params := map[string]stick.Value{"name": "World"} err := env.Execute("main.txt.twig", os.Stdout, params) if err != nil { fmt.Println(err) } }
Output: This is a document. Hello An introduction to the topic. The body of this topic. Another section Some extra information. Still nobody knows. Some kind of footer.
Example (Macro) ¶
An example of macro definition and usage.
This example uses a macro to list the values, also showing two ways to import macros. Check the templates in the testdata folder for more information.
package main import ( "fmt" "os" "path/filepath" "bitbucket.org/ticketshopdev/stick" ) func main() { d, _ := os.Getwd() env := stick.New(stick.NewFilesystemLoader(filepath.Join(d, "testdata"))) params := map[string]stick.Value{ "title_first": "Hello", "value_first": []struct{ Key, Value string }{ {"item1", "something about item1"}, {"item2", "something about item2"}, }, "title_second": "Responses", "value_second": []struct{ Key, Value string }{ {"please", "no, thank you"}, {"why not", "cause"}, }, } err := env.Execute("other.txt.twig", os.Stdout, params) if err != nil { fmt.Println(err) } }
Output: Hello * item1: something about item1 (0) * item2: something about item2 (1) Responses * please: no, thank you (0) * why not: cause (1)
Example (Parent) ¶
An example of macro definition and usage.
This example uses a macro to list the values, also showing two ways to import macros. Check the templates in the testdata folder for more information.
package main import ( "fmt" "os" "path/filepath" "bitbucket.org/ticketshopdev/stick" ) func main() { d, _ := os.Getwd() env := stick.New(stick.NewFilesystemLoader(filepath.Join(d, "testdata"))) err := env.Execute("parent.txt.twig", os.Stdout, nil) if err != nil { fmt.Println(err) } }
Output: This is a document. Not A title Testing parent() This is a test Another section Some extra information.
type Extension ¶
type Extension interface { // Init is the entry-point for an extension to modify the Env. Init(*Env) error }
An Extension is used to group related functions, filters, visitors, etc.
type FilesystemLoader ¶
type FilesystemLoader struct {
// contains filtered or unexported fields
}
A FilesystemLoader loads templates from a filesystem.
func NewFilesystemLoader ¶
func NewFilesystemLoader(rootDir string) *FilesystemLoader
NewFilesystemLoader creates a new FilesystemLoader with the specified root directory.
type Filter ¶
A Filter is a user-defined filter. Filters receive a value and modify it in some way. Filters also accept parameters.
Example ¶
A simple user-defined filter.
package main import ( "fmt" "os" "bitbucket.org/ticketshopdev/stick" ) func main() { env := stick.New(nil) env.Filters["raw"] = func(ctx stick.Context, val stick.Value, args ...stick.Value) stick.Value { return stick.NewSafeValue(val) } err := env.Execute( `{{ name|raw }}`, os.Stdout, map[string]stick.Value{"name": "<name>"}, ) if err != nil { fmt.Println(err) } }
Output: <name>
Example (WithParam) ¶
A simple user-defined filter that accepts a parameter.
package main import ( "fmt" "os" "strconv" "bitbucket.org/ticketshopdev/stick" ) func main() { env := stick.New(nil) env.Filters["number_format"] = func(ctx stick.Context, val stick.Value, args ...stick.Value) stick.Value { var d float64 if len(args) > 0 { d = stick.CoerceNumber(args[0]) } return strconv.FormatFloat(stick.CoerceNumber(val), 'f', int(d), 64) } err := env.Execute( `${{ price|number_format(2) }}`, os.Stdout, map[string]stick.Value{"price": 4.99}, ) if err != nil { fmt.Println(err) } }
Output: $4.99
type Func ¶
A Func represents a user-defined function. Functions can be called anywhere expressions are allowed and take any number of arguments.
Example ¶
A contrived example of a user-defined function.
package main import ( "fmt" "os" "bitbucket.org/ticketshopdev/stick" ) func main() { env := stick.New(nil) env.Functions["get_post"] = func(ctx stick.Context, args ...stick.Value) stick.Value { if len(args) == 0 { return nil } return struct { Title string ID float64 }{"A post", stick.CoerceNumber(args[0])} } err := env.Execute( `{% set post = get_post(123) %}{{ post.Title }} (# {{ post.ID }})`, os.Stdout, nil, ) if err != nil { fmt.Println(err) } }
Output: A post (# 123)
Example (UsingContext) ¶
package main import ( "fmt" "bytes" "io/ioutil" "bitbucket.org/ticketshopdev/stick" ) func main() { env := stick.New(&stick.MemoryLoader{ Templates: map[string]string{ "base.html.twig": `<!doctype html> <html> <head> <title>{% block title %}{% endblock %}</title> </head> <body> {% block nav %}{% endblock %} #3 base: {{ current_template() }} {% block content %}{% endblock %} </body> </html> `, "side.html.twig": `{% block nav %}#2 side: {{ current_template() }}{% endblock %}`, "child.html.twig": `{% extends 'base.html.twig' %} {% use 'side.html.twig' %} {% block title %}#1 child: {{ current_template() }}{% endblock %} {% block content %}#4 child: {{ current_template() }}{% endblock %}`, }, }) buf := &bytes.Buffer{} env.Functions["current_template"] = func(ctx stick.Context, args ...stick.Value) stick.Value { // Reading persistent metadata v, _ := ctx.Meta().Get("current_template_calls") nc := stick.CoerceNumber(v) nc++ fmt.Fprintf(buf, "#%.0f Current Template: %s\n", nc, ctx.Name()) // Writing persistent metadata ctx.Meta().Set("current_template_calls", stick.CoerceString(nc)) return nil } // Notice that we discard the actual output. We only care about what the // current_template function writes to buf. err := env.Execute( `child.html.twig`, ioutil.Discard, nil, ) if err != nil { fmt.Println(err) } fmt.Println(buf.String()) }
Output: #1 Current Template: child.html.twig #2 Current Template: side.html.twig #3 Current Template: base.html.twig #4 Current Template: child.html.twig
type Loader ¶
type Loader interface { // Load attempts to load the specified template, returning a Template or an error. Load(name string) (Template, error) }
Loader defines a type that can load Stick templates using the given name.
type MemoryLoader ¶
MemoryLoader loads templates from an in-memory map.
type Number ¶
type Number interface { // Number returns a float64 representation of the type. Number() float64 }
Number is implemented by any value that has a Number method.
Example ¶
This demonstrates how a type can be coerced to a number. The struct in this example has the Number method implemented.
func (e exampleType) Number() float64 { return 3.14 }
package main import ( "fmt" "bitbucket.org/ticketshopdev/stick" ) type exampleType struct{} func (e exampleType) Boolean() bool { return true } func (e exampleType) Number() float64 { return 3.14 } func (e exampleType) String() string { return "some kinda string" } func main() { v := exampleType{} fmt.Printf("%.2f", stick.CoerceNumber(v)) }
Output: 3.14
type SafeValue ¶
type SafeValue interface { // Value returns the value stored in the SafeValue. Value() Value // IsSafe returns true if the value is safely escaped for content of type typ. IsSafe(typ string) bool // SafeFor returns the content types this value is safe for. SafeFor() []string }
A SafeValue represents a value that has already been sanitized and escaped.
func NewSafeValue ¶
NewSafeValue wraps the given value and returns a SafeValue.
type StringLoader ¶
type StringLoader struct{}
StringLoader is intended to be used to load Stick templates directly from a string.
type Stringer ¶
Stringer is implemented by any value that has a String method.
Example ¶
This example demonstrates how a type can be coerced to a string. The struct in this example has the String method implemented.
func (e exampleType) String() string { return "some kinda string" }
package main import ( "fmt" ) type exampleType struct{} func (e exampleType) Boolean() bool { return true } func (e exampleType) Number() float64 { return 3.14 } func (e exampleType) String() string { return "some kinda string" } func main() { v := exampleType{} fmt.Printf("%s", v) }
Output: some kinda string
type Template ¶
type Template interface { // Name returns the name of this Template. Name() string // Contents returns an io.Reader for reading the Template contents. Contents() io.Reader }
A Template represents a named template and its contents.
type Test ¶
A Test represents a user-defined test. Tests are used to make some comparisons more expressive. Tests also accept arguments and can consist of two words.
Example ¶
A simple test to check if a value is empty
package main import ( "fmt" "os" "bitbucket.org/ticketshopdev/stick" ) func main() { env := stick.New(nil) env.Tests["empty"] = func(ctx stick.Context, val stick.Value, args ...stick.Value) bool { return stick.CoerceBool(val) == false } err := env.Execute( `{{ (false is empty) ? 'empty' : 'not empty' }} - {{ ("a string" is empty) ? 'empty' : 'not empty' }}`, os.Stdout, nil, ) if err != nil { fmt.Println(err) } }
Output: empty - not empty
Example (TwoWordsWithArgs) ¶
A test made up of two words that takes an argument.
package main import ( "fmt" "os" "bitbucket.org/ticketshopdev/stick" ) func main() { env := stick.New(nil) env.Tests["divisible by"] = func(ctx stick.Context, val stick.Value, args ...stick.Value) bool { if len(args) != 1 { return false } i := stick.CoerceNumber(args[0]) if i == 0 { return false } v := stick.CoerceNumber(val) return int(v)%int(i) == 0 } err := env.Execute( `{{ ('something' is divisible by(3)) ? "yep, 'something' evals to 0" : 'nope' }} - {{ (9 is divisible by(3)) ? 'sure' : 'nope' }} - {{ (4 is divisible by(3)) ? 'sure' : 'nope' }}`, os.Stdout, nil, ) if err != nil { fmt.Println(err) } }
Output: yep, 'something' evals to 0 - sure - nope
Directories ¶
Path | Synopsis |
---|---|
Package parse handles transforming Stick source code into AST for further processing.
|
Package parse handles transforming Stick source code into AST for further processing. |
Package twig provides Twig 1.x compatible template parsing and executing.
|
Package twig provides Twig 1.x compatible template parsing and executing. |
escape
Package escape provides Twig-compatible escape functions.
|
Package escape provides Twig-compatible escape functions. |
filter
Package filter provides built-in filters for Twig-compatibility.
|
Package filter provides built-in filters for Twig-compatibility. |