stick: github.com/tyler-sommer/stick Index | Examples | Files | Directories

package stick

import "github.com/tyler-sommer/stick"

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/github.com/tyler-sommer/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

Examples

Package Files

doc.go exec.go loader.go stick.go value.go

func CoerceBool Uses

func CoerceBool(v Value) bool

CoerceBool coerces the given value into a boolean. Boolean false is returned if the value cannot be coerced.

This example demonstrates how various values are coerced to boolean.

Code:

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 Uses

func CoerceNumber(v Value) float64

CoerceNumber coerces the given value into a number. Zero (0) is returned if the value cannot be coerced.

This example demonstrates how various values are coerced to number.

Code:

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 Uses

func CoerceString(v Value) string

CoerceString coerces the given value into a string. An empty string is returned if the value cannot be coerced.

This demonstrates how various values are coerced to string.

Code:

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 Contains Uses

func Contains(haystack Value, needle Value) (bool, error)

Contains returns true if the haystack Value contains needle.

func Equal Uses

func Equal(left Value, right Value) bool

Equal returns true if the two Values are considered equal.

func IsArray Uses

func IsArray(val Value) bool

IsArray returns true if the given Value is a slice or array.

func IsIterable Uses

func IsIterable(val Value) bool

IsIterable returns true if the given Value is a slice, array, or map.

func IsMap Uses

func IsMap(val Value) bool

IsMap returns true if the given Value is a map.

func Iterate Uses

func Iterate(val Value, it Iteratee) (int, error)

Iterate calls the Iteratee func for every item in the Value.

func Len Uses

func Len(val Value) (int, error)

Len returns the length of Value.

type Boolean Uses

type Boolean interface {
    // Boolean returns a boolean representation of the type.
    Boolean() bool
}

Boolean is implemented by any value that has a Boolean method.

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
}

Code:

v := exampleType{}
fmt.Printf("%t", stick.CoerceBool(v))

Output:

true

type Context Uses

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 Uses

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 Uses

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 Uses

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 New Uses

func New(loader Loader) *Env

New creates an empty Env. If nil is passed as loader, a StringLoader is used.

func (*Env) Execute Uses

func (env *Env) Execute(tpl string, out io.Writer, ctx map[string]Value) error

Execute parses and executes the given template.

An example of executing a template in the simplest possible manner.

Code:

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!

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).

Code:

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.

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.

Code:

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)

func (*Env) Parse Uses

func (env *Env) Parse(name string) (*parse.Tree, error)

Parse loads and parses the given template.

func (*Env) Register Uses

func (env *Env) Register(e Extension) error

Register adds the given Extension to the Env.

type Extension Uses

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 Uses

type FilesystemLoader struct {
    // contains filtered or unexported fields
}

A FilesystemLoader loads templates from a filesystem.

func NewFilesystemLoader Uses

func NewFilesystemLoader(rootDir string) *FilesystemLoader

NewFilesystemLoader creates a new FilesystemLoader with the specified root directory.

func (*FilesystemLoader) Load Uses

func (l *FilesystemLoader) Load(name string) (Template, error)

Load on a FileSystemLoader attempts to load the given file, relative to the configured root directory.

type Filter Uses

type Filter func(ctx Context, val Value, args ...Value) Value

A Filter is a user-defined filter. Filters receive a value and modify it in some way. Filters also accept parameters.

A simple user-defined filter.

Code:

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>

A simple user-defined filter that accepts a parameter.

Code:

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 Uses

type Func func(ctx Context, args ...Value) Value

A Func represents a user-defined function. Functions can be called anywhere expressions are allowed and take any number of arguments.

A contrived example of a user-defined function.

Code:

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)

Code:

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 Iteratee Uses

type Iteratee func(k, v Value, l Loop) (brk bool, err error)

An Iteratee is called for each step in a loop.

type Loader Uses

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 Loop Uses

type Loop struct {
    Last   bool
    Index  int
    Index0 int
}

Loop contains metadata about the current state of a loop.

type MemoryLoader Uses

type MemoryLoader struct {
    Templates map[string]string
}

MemoryLoader loads templates from an in-memory map.

func (*MemoryLoader) Load Uses

func (l *MemoryLoader) Load(name string) (Template, error)

Load tries to load the template from the in-memory map.

type Number Uses

type Number interface {
    // Number returns a float64 representation of the type.
    Number() float64
}

Number is implemented by any value that has a Number method.

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
}

Code:

v := exampleType{}
fmt.Printf("%.2f", stick.CoerceNumber(v))

Output:

3.14

type SafeValue Uses

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 Uses

func NewSafeValue(val Value, types ...string) SafeValue

NewSafeValue wraps the given value and returns a SafeValue.

type StringLoader Uses

type StringLoader struct{}

StringLoader is intended to be used to load Stick templates directly from a string.

func (*StringLoader) Load Uses

func (l *StringLoader) Load(name string) (Template, error)

Load on a StringLoader simply returns the name that is passed in.

type Stringer Uses

type Stringer interface {
    fmt.Stringer
}

Stringer is implemented by any value that has a String method.

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

Code:

v := exampleType{}
fmt.Printf("%s", v)

Output:

some kinda string

type Template Uses

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 Uses

type Test func(ctx Context, val Value, args ...Value) bool

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.

A simple test to check if a value is empty

Code:

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

A test made up of two words that takes an argument.

Code:

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

type Value Uses

type Value interface{}

A Value represents some value, scalar or otherwise, able to be passed into and used by a Stick template.

func GetAttr Uses

func GetAttr(v Value, attr Value, args ...Value) (Value, error)

GetAttr attempts to access the given value and return the specified attribute.

Directories

PathSynopsis
parsePackage parse handles transforming Stick source code into AST for further processing.
twigPackage twig provides Twig 1.x compatible template parsing and executing.
twig/escapePackage escape provides Twig-compatible escape functions.
twig/filterPackage filter provides built-in filters for Twig-compatibility.

Package stick imports 13 packages (graph) and is imported by 37 packages. Updated 2018-11-14. Refresh now. Tools for package owners.