raymond

package module
v2.0.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 8, 2022 License: MIT Imports: 12 Imported by: 5

README

raymond Build Status GoDoc

Handlebars for golang with the same features as handlebars.js 3.0.

The full API documentation is available here: http://godoc.org/github.com/infinytum/raymond.

Raymond Logo

Table of Contents

Quick Start

$ go get github.com/infinytum/raymond

The quick and dirty way of rendering a handlebars template:

package main

import (
    "fmt"

    "github.com/infinytum/raymond/v2"
)

func main() {
    tpl := `<div class="entry">
  <h1>{{title}}</h1>
  <div class="body">
    {{body}}
  </div>
</div>
`

    ctx := map[string]string{
        "title": "My New Post",
        "body":  "This is my first post!",
    }

    result, err := raymond.Render(tpl, ctx)
    if err != nil {
        panic("Please report a bug :)")
    }

    fmt.Print(result)
}

Displays:

<div class="entry">
  <h1>My New Post</h1>
  <div class="body">
    This is my first post!
  </div>
</div>

Please note that the template will be parsed everytime you call Render() function. So you probably want to read the next section.

Correct Usage

To avoid parsing a template several times, use the Parse() and Exec() functions:

package main

import (
    "fmt"

    "github.com/infinytum/raymond/v2"
)

func main() {
    source := `<div class="entry">
  <h1>{{title}}</h1>
  <div class="body">
    {{body}}
  </div>
</div>
`

    ctxList := []map[string]string{
        {
            "title": "My New Post",
            "body":  "This is my first post!",
        },
        {
            "title": "Here is another post",
            "body":  "This is my second post!",
        },
    }

    // parse template
    tpl, err := raymond.Parse(source)
    if err != nil {
        panic(err)
    }

    for _, ctx := range ctxList {
        // render template
        result, err := tpl.Exec(ctx)
        if err != nil {
            panic(err)
        }

        fmt.Print(result)
    }
}

Displays:

<div class="entry">
  <h1>My New Post</h1>
  <div class="body">
    This is my first post!
  </div>
</div>
<div class="entry">
  <h1>Here is another post</h1>
  <div class="body">
    This is my second post!
  </div>
</div>

You can use MustParse() and MustExec() functions if you don't want to deal with errors:

// parse template
tpl := raymond.MustParse(source)

// render template
result := tpl.MustExec(ctx)

Context

The rendering context can contain any type of values, including array, slice, map, struct and func.

When using structs, be warned that only exported fields are accessible. However you can access exported fields in template with their lowercase names. For example, both {{author.firstName}} and {{Author.FirstName}} references give the same result, as long as Author and FirstName are exported struct fields.

More, you can use the handlebars struct tag to specify a template variable name different from the struct field name.

package main

import (
  "fmt"

  "github.com/infinytum/raymond/v2"
)

func main() {
    source := `<div class="post">
  <h1>By {{author.firstName}} {{author.lastName}}</h1>
  <div class="body">{{body}}</div>

  <h1>Comments</h1>

  {{#each comments}}
  <h2>By {{author.firstName}} {{author.lastName}}</h2>
  <div class="body">{{content}}</div>
  {{/each}}
</div>`

    type Person struct {
        FirstName string
        LastName  string
    }

    type Comment struct {
        Author Person
        Body   string `handlebars:"content"`
    }

    type Post struct {
        Author   Person
        Body     string
        Comments []Comment
    }

    ctx := Post{
        Person{"Jean", "Valjean"},
        "Life is difficult",
        []Comment{
            Comment{
                Person{"Marcel", "Beliveau"},
                "LOL!",
            },
        },
    }

    output := raymond.MustRender(source, ctx)

    fmt.Print(output)
}

Output:

<div class="post">
  <h1>By Jean Valjean</h1>
  <div class="body">Life is difficult</div>

  <h1>Comments</h1>

  <h2>By Marcel Beliveau</h2>
  <div class="body">LOL!</div>
</div>

HTML Escaping

By default, the result of a mustache expression is HTML escaped. Use the triple mustache {{{ to output unescaped values.

source := `<div class="entry">
  <h1>{{title}}</h1>
  <div class="body">
    {{{body}}}
  </div>
</div>
`

ctx := map[string]string{
    "title": "All about <p> Tags",
    "body":  "<p>This is a post about &lt;p&gt; tags</p>",
}

tpl := raymond.MustParse(source)
result := tpl.MustExec(ctx)

fmt.Print(result)

Output:

<div class="entry">
  <h1>All about &lt;p&gt; Tags</h1>
  <div class="body">
    <p>This is a post about &lt;p&gt; tags</p>
  </div>
</div>

When returning HTML from a helper, you should return a SafeString if you don't want it to be escaped by default. When using SafeString all unknown or unsafe data should be manually escaped with the Escape method.

raymond.RegisterHelper("link", func(url, text string) raymond.SafeString {
    return raymond.SafeString("<a href='" + raymond.Escape(url) + "'>" + raymond.Escape(text) + "</a>")
})

tpl := raymond.MustParse("{{link url text}}")

ctx := map[string]string{
    "url":  "http://www.aymerick.com/",
    "text": "This is a <em>cool</em> website",
}

result := tpl.MustExec(ctx)
fmt.Print(result)

Output:

<a href='http://www.aymerick.com/'>This is a &lt;em&gt;cool&lt;/em&gt; website</a>

Helpers

Helpers can be accessed from any context in a template. You can register a helper with the RegisterHelper function.

For example:

<div class="post">
  <h1>By {{fullName author}}</h1>
  <div class="body">{{body}}</div>

  <h1>Comments</h1>

  {{#each comments}}
  <h2>By {{fullName author}}</h2>
  <div class="body">{{body}}</div>
  {{/each}}
</div>

With this context and helper:

ctx := map[string]interface{}{
    "author": map[string]string{"firstName": "Jean", "lastName": "Valjean"},
    "body":   "Life is difficult",
    "comments": []map[string]interface{}{{
        "author": map[string]string{"firstName": "Marcel", "lastName": "Beliveau"},
        "body":   "LOL!",
    }},
}

raymond.RegisterHelper("fullName", func(person map[string]string) string {
    return person["firstName"] + " " + person["lastName"]
})

Outputs:

<div class="post">
  <h1>By Jean Valjean</h1>
  <div class="body">Life is difficult</div>

  <h1>Comments</h1>

  <h2>By Marcel Beliveau</h2>
  <div class="body">LOL!</div>
</div>

Helper arguments can be any type.

The following example uses structs instead of maps and produces the same output as the previous one:

<div class="post">
  <h1>By {{fullName author}}</h1>
  <div class="body">{{body}}</div>

  <h1>Comments</h1>

  {{#each comments}}
  <h2>By {{fullName author}}</h2>
  <div class="body">{{body}}</div>
  {{/each}}
</div>

With this context and helper:

type Post struct {
    Author   Person
    Body     string
    Comments []Comment
}

type Person struct {
    FirstName string
    LastName  string
}

type Comment struct {
    Author Person
    Body   string
}

ctx := Post{
    Person{"Jean", "Valjean"},
    "Life is difficult",
    []Comment{
        Comment{
            Person{"Marcel", "Beliveau"},
            "LOL!",
        },
    },
}

raymond.RegisterHelper("fullName", func(person Person) string {
    return person.FirstName + " " + person.LastName
})

You can unregister global helpers with RemoveHelper and RemoveAllHelpers functions:

raymond.RemoveHelper("fullname")
raymond.RemoveAllHelpers()
Template Helpers

You can register a helper on a specific template, and in that case that helper will be available to that template only:

tpl := raymond.MustParse("User: {{fullName user.firstName user.lastName}}")

tpl.RegisterHelper("fullName", func(firstName, lastName string) string {
  return firstName + " " + lastName
})
Built-In Helpers

Those built-in helpers are available to all templates.

The if block helper

You can use the if helper to conditionally render a block. If its argument returns false, nil, 0, "", an empty array, an empty slice or an empty map, then raymond will not render the block.

<div class="entry">
  {{#if author}}
    <h1>{{firstName}} {{lastName}}</h1>
  {{/if}}
</div>

When using a block expression, you can specify a template section to run if the expression returns a falsy value. That section, marked by {{else}} is called an "else section".

<div class="entry">
  {{#if author}}
    <h1>{{firstName}} {{lastName}}</h1>
  {{else}}
    <h1>Unknown Author</h1>
  {{/if}}
</div>

You can chain several blocks. For example that template:

{{#if isActive}}
  <img src="star.gif" alt="Active">
{{else if isInactive}}
  <img src="cry.gif" alt="Inactive">
{{else}}
  <img src="wat.gif" alt="Unknown">
{{/if}}

With that context:

ctx := map[string]interface{}{
    "isActive":   false,
    "isInactive": false,
}

Outputs:

 <img src="wat.gif" alt="Unknown">
The unless block helper

You can use the unless helper as the inverse of the if helper. Its block will be rendered if the expression returns a falsy value.

<div class="entry">
  {{#unless license}}
  <h3 class="warning">WARNING: This entry does not have a license!</h3>
  {{/unless}}
</div>
The each block helper

You can iterate over an array, a slice, a map or a struct instance using this built-in each helper. Inside the block, you can use this to reference the element being iterated over.

For example:

<ul class="people">
  {{#each people}}
    <li>{{this}}</li>
  {{/each}}
</ul>

With this context:

map[string]interface{}{
    "people": []string{
        "Marcel", "Jean-Claude", "Yvette",
    },
}

Outputs:

<ul class="people">
  <li>Marcel</li>
  <li>Jean-Claude</li>
  <li>Yvette</li>
</ul>

You can optionally provide an {{else}} section which will display only when the passed argument is an empty array, an empty slice or an empty map (a struct instance is never considered empty).

{{#each paragraphs}}
  <p>{{this}}</p>
{{else}}
  <p class="empty">No content</p>
{{/each}}

When looping through items in each, you can optionally reference the current loop index via {{@index}}.

{{#each array}}
  {{@index}}: {{this}}
{{/each}}

Additionally for map and struct instance iteration, {{@key}} references the current map key or struct field name:

{{#each map}}
  {{@key}}: {{this}}
{{/each}}

The first and last steps of iteration are noted via the @first and @last variables.

The with block helper

You can shift the context for a section of a template by using the built-in with block helper.

<div class="entry">
  <h1>{{title}}</h1>

  {{#with author}}
  <h2>By {{firstName}} {{lastName}}</h2>
  {{/with}}
</div>

With this context:

map[string]interface{}{
    "title": "My first post!",
    "author": map[string]string{
        "firstName": "Jean",
        "lastName":  "Valjean",
    },
}

Outputs:

<div class="entry">
  <h1>My first post!</h1>

  <h2>By Jean Valjean</h2>
</div>

You can optionally provide an {{else}} section which will display only when the passed argument is falsy.

{{#with author}}
  <p>{{name}}</p>
{{else}}
  <p class="empty">No content</p>
{{/with}}
The lookup helper

The lookup helper allows for dynamic parameter resolution using handlebars variables.

{{#each bar}}
  {{lookup ../foo @index}}
{{/each}}
The log helper

The log helper allows for logging while rendering a template.

{{log "Look at me!"}}

Note that the handlebars.js @level variable is not supported.

The equal helper

The equal helper renders a block if the string version of both arguments are equals.

For example that template:

{{#equal foo "bar"}}foo is bar{{/equal}}
{{#equal foo baz}}foo is the same as baz{{/equal}}
{{#equal nb 0}}nothing{{/equal}}
{{#equal nb 1}}there is one{{/equal}}
{{#equal nb "1"}}everything is stringified before comparison{{/equal}}

With that context:

ctx := map[string]interface{}{
    "foo": "bar",
    "baz": "bar",
    "nb":  1,
}

Outputs:

foo is bar
foo is the same as baz

there is one
everything is stringified before comparison
Block Helpers

Block helpers make it possible to define custom iterators and other functionality that can invoke the passed block with a new context.

Block Evaluation

As an example, let's define a block helper that adds some markup to the wrapped text.

<div class="entry">
  <h1>{{title}}</h1>
  <div class="body">
    {{#bold}}{{body}}{{/bold}}
  </div>
</div>

The bold helper will add markup to make its text bold.

raymond.RegisterHelper("bold", func(options *raymond.Options) raymond.SafeString {
    return raymond.SafeString(`<div class="mybold">` + options.Fn() + "</div>")
})

A helper evaluates the block content with current context by calling options.Fn().

If you want to evaluate the block with another context, then use options.FnWith(ctx), like this french version of built-in with block helper:

raymond.RegisterHelper("avec", func(context interface{}, options *raymond.Options) string {
    return options.FnWith(context)
})

With that template:

{{#avec obj.text}}{{this}}{{/avec}}
Conditional

Let's write a french version of if block helper:

source := `{{#si yep}}YEP !{{/si}}`

ctx := map[string]interface{}{"yep": true}

raymond.RegisterHelper("si", func(conditional bool, options *raymond.Options) string {
    if conditional {
        return options.Fn()
    }
    return ""
})

Note that as the first parameter of the helper is typed as bool an automatic conversion is made if corresponding context value is not a boolean. So this helper works with that context too:

ctx := map[string]interface{}{"yep": "message"}

Here, "message" is converted to true because it is an non-empty string. See IsTrue() function for more informations on boolean conversion.

Else Block Evaluation

We can enhance the si block helper to evaluate the else block by calling options.Inverse() if conditional is false:

source := `{{#si yep}}YEP !{{else}}NOP !{{/si}}`

ctx := map[string]interface{}{"yep": false}

raymond.RegisterHelper("si", func(conditional bool, options *raymond.Options) string {
    if conditional {
        return options.Fn()
    }
    return options.Inverse()
})

Outputs:

NOP !
Block Parameters

It's possible to receive named parameters from supporting helpers.

{{#each users as |user userId|}}
  Id: {{userId}} Name: {{user.name}}
{{/each}}

In this particular example, user will have the same value as the current context and userId will have the index/key value for the iteration.

This allows for nested helpers to avoid name conflicts.

For example:

{{#each users as |user userId|}}
  {{#each user.books as |book bookId|}}
    User: {{userId}} Book: {{bookId}}
  {{/each}}
{{/each}}

With this context:

ctx := map[string]interface{}{
    "users": map[string]interface{}{
        "marcel": map[string]interface{}{
            "books": map[string]interface{}{
                "book1": "My first book",
                "book2": "My second book",
            },
        },
        "didier": map[string]interface{}{
            "books": map[string]interface{}{
                "bookA": "Good book",
                "bookB": "Bad book",
            },
        },
    },
}

Outputs:

  User: marcel Book: book1
  User: marcel Book: book2
  User: didier Book: bookA
  User: didier Book: bookB

As you can see, the second block parameter is the map key. When using structs, it is the struct field name.

When using arrays and slices, the second parameter is element index:

ctx := map[string]interface{}{
    "users": []map[string]interface{}{
        {
            "id": "marcel",
            "books": []map[string]interface{}{
                {"id": "book1", "title": "My first book"},
                {"id": "book2", "title": "My second book"},
            },
        },
        {
            "id": "didier",
            "books": []map[string]interface{}{
                {"id": "bookA", "title": "Good book"},
                {"id": "bookB", "title": "Bad book"},
            },
        },
    },
}

Outputs:

    User: 0 Book: 0
    User: 0 Book: 1
    User: 1 Book: 0
    User: 1 Book: 1
Helper Parameters

When calling a helper in a template, raymond expects the same number of arguments as the number of helper function parameters.

So this template:

{{add a}}

With this helper:

raymond.RegisterHelper("add", func(val1, val2 int) string {
    return strconv.Itoa(val1 + val2)
})

Will simply panics, because we call the helper with one argument whereas it expects two.

Automatic conversion

Let's create a concat helper that expects two strings and concat them:

source := `{{concat a b}}`

ctx := map[string]interface{}{
    "a": "Jean",
    "b": "Valjean",
}

raymond.RegisterHelper("concat", func(val1, val2 string) string {
    return val1 + " " + val2
})

Everything goes well, two strings are passed as arguments to the helper that outputs:

Jean VALJEAN

But what happens if there is another type than string in the context ? For example:

ctx := map[string]interface{}{
    "a": 10,
    "b": "Valjean",
}

Actually, raymond perfoms automatic string conversion. So because the first parameter of the helper is typed as string, the first argument will be converted from the 10 integer to "10", and the helper outputs:

10 VALJEAN

Note that this kind of automatic conversion is done with bool type too, thanks to the IsTrue() function.

Options Argument

If a helper needs the Options argument, just add it at the end of helper parameters:

raymond.RegisterHelper("add", func(val1, val2 int, options *raymond.Options) string {
    return strconv.Itoa(val1 + val2) + " " + options.ValueStr("bananas")
})

Thanks to the options argument, helpers have access to the current evaluation context, to the Hash arguments, and they can manipulate the private data variables.

The Options argument is even necessary for Block Helpers to evaluate block and "else block".

Context Values

Helpers fetch current context values with options.Value() and options.ValuesStr().

Value() returns an interface{} and lets the helper do the type assertions whereas ValueStr() automatically converts the value to a string.

For example:

source := `{{concat a b}}`

ctx := map[string]interface{}{
    "a":      "Marcel",
    "b":      "Beliveau",
    "suffix": "FOREVER !",
}

raymond.RegisterHelper("concat", func(val1, val2 string, options *raymond.Options) string {
    return val1 + " " + val2 + " " + options.ValueStr("suffix")
})

Outputs:

Marcel Beliveau FOREVER !

Helpers can get the entire current context with options.Ctx() that returns an interface{}.

Helper Hash Arguments

Helpers access hash arguments with options.HashProp() and options.HashStr().

HashProp() returns an interface{} and lets the helper do the type assertions whereas HashStr() automatically converts the value to a string.

For example:

source := `{{concat suffix first=a second=b}}`

ctx := map[string]interface{}{
    "a":      "Marcel",
    "b":      "Beliveau",
    "suffix": "FOREVER !",
}

raymond.RegisterHelper("concat", func(suffix string, options *raymond.Options) string {
    return options.HashStr("first") + " " + options.HashStr("second") + " " + suffix
})

Outputs:

Marcel Beliveau FOREVER !

Helpers can get the full hash with options.Hash() that returns a map[string]interface{}.

Private Data

Helpers access private data variables with options.Data() and options.DataStr().

Data() returns an interface{} and lets the helper do the type assertions whereas DataStr() automatically converts the value to a string.

Helpers can get the entire current data frame with options.DataFrame() that returns a *DataFrame.

For helpers that need to inject their own private data frame, use options.NewDataFrame() to create the frame and options.FnData() to evaluate the block with that frame.

For example:

source := `{{#voodoo kind=a}}Voodoo is {{@magix}}{{/voodoo}}`

ctx := map[string]interface{}{
    "a": "awesome",
}

raymond.RegisterHelper("voodoo", func(options *raymond.Options) string {
    // create data frame with @magix data
    frame := options.NewDataFrame()
    frame.Set("magix", options.HashProp("kind"))

    // evaluates block with new data frame
    return options.FnData(frame)
})

Helpers that need to evaluate the block with a private data frame and a new context can call options.FnCtxData().

Utilites

In addition to Escape(), raymond provides utility functions that can be usefull for helpers.

Str()

Str() converts its parameter to a string.

Booleans:

raymond.Str(3) + " foos and " + raymond.Str(-1.25) + " bars"
// Outputs: "3 foos and -1.25 bars"

Numbers:

"everything is " + raymond.Str(true) + " and nothing is " + raymond.Str(false)
// Outputs: "everything is true and nothing is false"

Maps:

raymond.Str(map[string]string{"foo": "bar"})
// Outputs: "map[foo:bar]"

Arrays and Slices:

raymond.Str([]interface{}{true, 10, "foo", 5, "bar"})
// Outputs: "true10foo5bar"
IsTrue()

IsTrue() returns the truthy version of its parameter.

It returns false when parameter is either:

  • an empty array
  • an empty slice
  • an empty map
  • ""
  • nil
  • 0
  • false

For all others values, IsTrue() returns true.

Context Functions

In addition to helpers, lambdas found in context are evaluated.

For example, that template and context:

source := "I {{feeling}} you"

ctx := map[string]interface{}{
    "feeling": func() string {
        rand.Seed(time.Now().UTC().UnixNano())

        feelings := []string{"hate", "love"}
        return feelings[rand.Intn(len(feelings))]
    },
}

Randomly renders I hate you or I love you.

Those context functions behave like helper functions: they can be called with parameters and they can have an Options argument.

Partials

Template Partials

You can register template partials before execution:

tpl := raymond.MustParse("{{> foo}} baz")
tpl.RegisterPartial("foo", "<span>bar</span>")

result := tpl.MustExec(nil)
fmt.Print(result)

Output:

<span>bar</span> baz

You can register several partials at once:

tpl := raymond.MustParse("{{> foo}} and {{> baz}}")
tpl.RegisterPartials(map[string]string{
    "foo": "<span>bar</span>",
    "baz": "<span>bat</span>",
})

result := tpl.MustExec(nil)
fmt.Print(result)

Output:

<span>bar</span> and <span>bat</span>
Global Partials

You can registers global partials that will be accessible by all templates:

raymond.RegisterPartial("foo", "<span>bar</span>")

tpl := raymond.MustParse("{{> foo}} baz")
result := tpl.MustExec(nil)
fmt.Print(result)

Or:

raymond.RegisterPartials(map[string]string{
    "foo": "<span>bar</span>",
    "baz": "<span>bat</span>",
})

tpl := raymond.MustParse("{{> foo}} and {{> baz}}")
result := tpl.MustExec(nil)
fmt.Print(result)
Dynamic Partials

It's possible to dynamically select the partial to be executed by using sub expression syntax.

For example, that template randomly evaluates the foo or baz partial:

tpl := raymond.MustParse("{{> (whichPartial) }}")
tpl.RegisterPartials(map[string]string{
    "foo": "<span>bar</span>",
    "baz": "<span>bat</span>",
})

ctx := map[string]interface{}{
    "whichPartial": func() string {
        rand.Seed(time.Now().UTC().UnixNano())

        names := []string{"foo", "baz"}
        return names[rand.Intn(len(names))]
    },
}

result := tpl.MustExec(ctx)
fmt.Print(result)
Partial Contexts

It's possible to execute partials on a custom context by passing in the context to the partial call.

For example:

tpl := raymond.MustParse("User: {{> userDetails user }}")
tpl.RegisterPartial("userDetails", "{{firstname}} {{lastname}}")

ctx := map[string]interface{}{
    "user": map[string]string{
        "firstname": "Jean",
        "lastname":  "Valjean",
    },
}

result := tpl.MustExec(ctx)
fmt.Print(result)

Displays:

User: Jean Valjean
Partial Parameters

Custom data can be passed to partials through hash parameters.

For example:

tpl := raymond.MustParse("{{> myPartial name=hero }}")
tpl.RegisterPartial("myPartial", "My hero is {{name}}")

ctx := map[string]interface{}{
    "hero": "Goldorak",
}

result := tpl.MustExec(ctx)
fmt.Print(result)

Displays:

My hero is Goldorak

Utility Functions

You can use following utility fuctions to parse and register partials from files:

  • ParseFile() - reads a file and return parsed template
  • Template.RegisterPartialFile() - reads a file and registers its content as a partial with given name
  • Template.RegisterPartialFiles() - reads several files and registers them as partials, the filename base is used as the partial name

Mustache

Handlebars is a superset of mustache but it differs on those points:

  • Alternative delimiters are not supported
  • There is no recursive lookup

Limitations

These handlebars options are currently NOT implemented:

  • compat - enables recursive field lookup
  • knownHelpers - list of helpers that are known to exist (truthy) at template execution time
  • knownHelpersOnly - allows further optimizations based on the known helpers list
  • trackIds - include the id names used to resolve parameters for helpers
  • noEscape - disables HTML escaping globally
  • strict - templates will throw rather than silently ignore missing fields
  • assumeObjects - removes object existence checks when traversing paths
  • preventIndent - disables the auto-indententation of nested partials
  • stringParams - resolves a parameter to it's name if the value isn't present in the context stack

These handlebars features are currently NOT implemented:

  • raw block content is not passed as a parameter to helper
  • blockHelperMissing - helper called when a helper can not be directly resolved
  • helperMissing - helper called when a potential helper expression was not found
  • @contextPath - value set in trackIds mode that records the lookup path for the current context
  • @level - log level

Handlebars Lexer

You should not use the lexer directly, but for your information here is an example:

package main

import (
    "fmt"

    "github.com/infinytum/raymond/v2/lexer"
)

func main() {
    source := "You know {{nothing}} John Snow"

    output := ""

    lex := lexer.Scan(source)
    for {
        // consume next token
        token := lex.NextToken()

        output += fmt.Sprintf(" %s", token)

        // stops when all tokens have been consumed, or on error
        if token.Kind == lexer.TokenEOF || token.Kind == lexer.TokenError {
            break
        }
    }

    fmt.Print(output)
}

Outputs:

Content{"You know "} Open{"{{"} ID{"nothing"} Close{"}}"} Content{" John Snow"} EOF

Handlebars Parser

You should not use the parser directly, but for your information here is an example:

package main

import (
    "fmt"

    "github.com/infinytum/raymond/v2/ast"
    "github.com/infinytum/raymond/v2/parser"
)

fu  nc main() {
    source := "You know {{nothing}} John Snow"

    // parse template
    program, err := parser.Parse(source)
    if err != nil {
        panic(err)
    }

    // print AST
    output := ast.Print(program)

    fmt.Print(output)
}

Outputs:

CONTENT[ 'You know ' ]
{{ PATH:nothing [] }}
CONTENT[ ' John Snow' ]

Test

First, fetch mustache tests:

$ git submodule update --init

To run all tests:

$ go test ./...

To filter tests:

$ go test -run="Partials"

To run all test and all benchmarks:

$ go test -bench . ./...

To test with race detection:

$ go test -race ./...

References

Others Implementations

Documentation

Overview

Package raymond provides handlebars evaluation

Example
source := "<h1>{{title}}</h1><p>{{body.content}}</p>"

ctx := map[string]interface{}{
	"title": "foo",
	"body":  map[string]string{"content": "bar"},
}

// parse template
tpl := MustParse(source)

// evaluate template with context
output := tpl.MustExec(ctx)

// alternatively, for one shots:
// output :=  MustRender(source, ctx)

fmt.Print(output)
Output:

<h1>foo</h1><p>bar</p>
Example (Struct)
source := `<div class="post">
  <h1>By {{fullName author}}</h1>
  <div class="body">{{body}}</div>

  <h1>Comments</h1>

  {{#each comments}}
  <h2>By {{fullName author}}</h2>
  <div class="body">{{content}}</div>
  {{/each}}
</div>`

type Person struct {
	FirstName string
	LastName  string
}

type Comment struct {
	Author Person
	Body   string `handlebars:"content"`
}

type Post struct {
	Author   Person
	Body     string
	Comments []Comment
}

ctx := Post{
	Person{"Jean", "Valjean"},
	"Life is difficult",
	[]Comment{
		Comment{
			Person{"Marcel", "Beliveau"},
			"LOL!",
		},
	},
}

RegisterHelper("fullName", func(person Person) string {
	return person.FirstName + " " + person.LastName
})

output := MustRender(source, ctx)

fmt.Print(output)
Output:

<div class="post">
  <h1>By Jean Valjean</h1>
  <div class="body">Life is difficult</div>

  <h1>Comments</h1>

  <h2>By Marcel Beliveau</h2>
  <div class="body">LOL!</div>
</div>

Index

Examples

Constants

This section is empty.

Variables

View Source
var ResolvePartial func(view string) *Partial = func(view string) *Partial { return nil }

Functions

func Escape

func Escape(s string) string

Escape escapes special HTML characters.

It can be used by helpers that return a SafeString and that need to escape some content by themselves.

Example
tpl := MustParse("{{link url text}}")

tpl.RegisterHelper("link", func(url string, text string) SafeString {
	return SafeString("<a href='" + Escape(url) + "'>" + Escape(text) + "</a>")
})

ctx := map[string]string{
	"url":  "http://www.aymerick.com/",
	"text": "This is a <em>cool</em> website",
}

result := tpl.MustExec(ctx)
fmt.Print(result)
Output:

<a href='http://www.aymerick.com/'>This is a &lt;em&gt;cool&lt;/em&gt; website</a>

func IsTrue

func IsTrue(obj interface{}) bool

IsTrue returns true if obj is a truthy value.

Example
output := "Empty array: " + Str(IsTrue([0]string{})) + "\n"
output += "Non empty array: " + Str(IsTrue([1]string{"foo"})) + "\n"

output += "Empty slice: " + Str(IsTrue([]string{})) + "\n"
output += "Non empty slice: " + Str(IsTrue([]string{"foo"})) + "\n"

output += "Empty map: " + Str(IsTrue(map[string]string{})) + "\n"
output += "Non empty map: " + Str(IsTrue(map[string]string{"foo": "bar"})) + "\n"

output += "Empty string: " + Str(IsTrue("")) + "\n"
output += "Non empty string: " + Str(IsTrue("foo")) + "\n"

output += "true bool: " + Str(IsTrue(true)) + "\n"
output += "false bool: " + Str(IsTrue(false)) + "\n"

output += "0 integer: " + Str(IsTrue(0)) + "\n"
output += "positive integer: " + Str(IsTrue(10)) + "\n"
output += "negative integer: " + Str(IsTrue(-10)) + "\n"

output += "0 float: " + Str(IsTrue(0.0)) + "\n"
output += "positive float: " + Str(IsTrue(10.0)) + "\n"
output += "negative integer: " + Str(IsTrue(-10.0)) + "\n"

output += "struct: " + Str(IsTrue(struct{}{})) + "\n"
output += "nil: " + Str(IsTrue(nil)) + "\n"

fmt.Println(output)
Output:

Empty array: false
Non empty array: true
Empty slice: false
Non empty slice: true
Empty map: false
Non empty map: true
Empty string: false
Non empty string: true
true bool: true
false bool: false
0 integer: false
positive integer: true
negative integer: true
0 float: false
positive float: true
negative integer: true
struct: true
nil: false

func MustRender

func MustRender(source string, ctx interface{}) string

MustRender parses a template and evaluates it with given context. It panics on error.

Note that this function call is not optimal as your template is parsed everytime you call it. You should use Parse() function instead.

Example
tpl := "<h1>{{title}}</h1><p>{{body.content}}</p>"

ctx := map[string]interface{}{
	"title": "foo",
	"body":  map[string]string{"content": "bar"},
}

// render template with context
output := MustRender(tpl, ctx)

fmt.Print(output)
Output:

<h1>foo</h1><p>bar</p>

func RegisterHelper

func RegisterHelper(name string, helper interface{})

RegisterHelper registers a global helper. That helper will be available to all templates.

func RegisterHelpers

func RegisterHelpers(helpers map[string]interface{})

RegisterHelpers registers several global helpers. Those helpers will be available to all templates.

func RegisterPartial

func RegisterPartial(name string, source string)

RegisterPartial registers a global partial. That partial will be available to all templates.

func RegisterPartialTemplate

func RegisterPartialTemplate(name string, tpl *Template)

RegisterPartialTemplate registers a global partial with given parsed template. That partial will be available to all templates.

func RegisterPartials

func RegisterPartials(partials map[string]string)

RegisterPartials registers several global partials. Those partials will be available to all templates.

func RemoveAllHelpers

func RemoveAllHelpers()

RemoveAllHelpers unregisters all global helpers

func RemoveAllPartials

func RemoveAllPartials()

RemoveAllPartials removes all globally registered partials. This does not affect partials registered on a specific template.

func RemoveHelper

func RemoveHelper(name string)

RemoveHelper unregisters a global helper

func RemovePartial

func RemovePartial(name string)

RemovePartial removes the partial registered under the given name. The partial will not be available globally anymore. This does not affect partials registered on a specific template.

func Render

func Render(source string, ctx interface{}) (string, error)

Render parses a template and evaluates it with given context

Note that this function call is not optimal as your template is parsed everytime you call it. You should use Parse() function instead.

Example
tpl := "<h1>{{title}}</h1><p>{{body.content}}</p>"

ctx := map[string]interface{}{
	"title": "foo",
	"body":  map[string]string{"content": "bar"},
}

// render template with context
output, err := Render(tpl, ctx)
if err != nil {
	panic(err)
}

fmt.Print(output)
Output:

<h1>foo</h1><p>bar</p>

func Str

func Str(value interface{}) string

Str returns string representation of any basic type value.

Example
output := Str(3) + " foos are " + Str(true) + " and " + Str(-1.25) + " bars are " + Str(false) + "\n"
output += "But you know '" + Str(nil) + "' John Snow\n"
output += "map: " + Str(map[string]string{"foo": "bar"}) + "\n"
output += "array: " + Str([]interface{}{true, 10, "foo", 5, "bar"})

fmt.Println(output)
Output:

3 foos are true and -1.25 bars are false
But you know '' John Snow
map: map[foo:bar]
array: true10foo5bar

Types

type DataFrame

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

DataFrame represents a private data frame.

Cf. private variables documentation at: http://handlebarsjs.com/block_helpers.html

func NewDataFrame

func NewDataFrame() *DataFrame

NewDataFrame instanciates a new private data frame.

func (*DataFrame) Copy

func (p *DataFrame) Copy() *DataFrame

Copy instanciates a new private data frame with receiver as parent.

func (*DataFrame) Get

func (p *DataFrame) Get(key string) interface{}

Get gets a data value.

func (*DataFrame) Set

func (p *DataFrame) Set(key string, val interface{})

Set sets a data value.

type Options

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

Options represents the options argument provided to helpers and context functions.

func (*Options) Ctx

func (options *Options) Ctx() interface{}

Ctx returns current evaluation context.

func (*Options) Data

func (options *Options) Data(name string) interface{}

Data returns private data value.

func (*Options) DataFrame

func (options *Options) DataFrame() *DataFrame

DataFrame returns current private data frame.

func (*Options) DataStr

func (options *Options) DataStr(name string) string

DataStr returns string representation of private data value.

func (*Options) Eval

func (options *Options) Eval(ctx interface{}, field string) interface{}

Eval evaluates field for given context.

func (*Options) Fn

func (options *Options) Fn() string

Fn evaluates block with current evaluation context.

func (*Options) FnCtxData

func (options *Options) FnCtxData(ctx interface{}, data *DataFrame) string

FnCtxData evaluates block with given context and private data frame.

func (*Options) FnData

func (options *Options) FnData(data *DataFrame) string

FnData evaluates block with given private data frame.

func (*Options) FnWith

func (options *Options) FnWith(ctx interface{}) string

FnWith evaluates block with given context.

func (*Options) Hash

func (options *Options) Hash() map[string]interface{}

Hash returns entire hash.

func (*Options) HashProp

func (options *Options) HashProp(name string) interface{}

HashProp returns hash property.

func (*Options) HashStr

func (options *Options) HashStr(name string) string

HashStr returns string representation of hash property.

func (*Options) Inverse

func (options *Options) Inverse() string

Inverse evaluates "else block".

func (*Options) NewDataFrame

func (options *Options) NewDataFrame() *DataFrame

NewDataFrame instanciates a new data frame that is a copy of current evaluation data frame.

Parent of returned data frame is set to current evaluation data frame.

func (*Options) Param

func (options *Options) Param(pos int) interface{}

Param returns parameter at given position.

func (*Options) ParamStr

func (options *Options) ParamStr(pos int) string

ParamStr returns string representation of parameter at given position.

func (*Options) Params

func (options *Options) Params() []interface{}

Params returns all parameters.

func (*Options) Value

func (options *Options) Value(name string) interface{}

Value returns field value from current context.

func (*Options) ValueStr

func (options *Options) ValueStr(name string) string

ValueStr returns string representation of field value from current context.

type Partial

type Partial struct {
	Name   string
	Source string
	Tpl    *Template
}

Partial represents a Partial template

func NewPartial

func NewPartial(name string, source string, tpl *Template) *Partial

NewPartial instanciates a new partial

type SafeString

type SafeString string

SafeString represents a string that must not be escaped.

A SafeString can be returned by helpers to disable escaping.

Example
RegisterHelper("em", func() SafeString {
	return SafeString("<em>FOO BAR</em>")
})

tpl := MustParse("{{em}}")

result := tpl.MustExec(nil)
fmt.Print(result)
Output:

<em>FOO BAR</em>

type Template

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

Template represents a handlebars template.

func MustParse

func MustParse(source string) *Template

MustParse instanciates a template by parsing given source. It panics on error.

func Parse

func Parse(source string) (*Template, error)

Parse instanciates a template by parsing given source.

func ParseFile

func ParseFile(filePath string) (*Template, error)

ParseFile reads given file and returns parsed template.

func (*Template) Clone

func (tpl *Template) Clone() *Template

Clone returns a copy of that template.

func (*Template) Exec

func (tpl *Template) Exec(ctx interface{}) (result string, err error)

Exec evaluates template with given context.

Example
source := "<h1>{{title}}</h1><p>{{body.content}}</p>"

ctx := map[string]interface{}{
	"title": "foo",
	"body":  map[string]string{"content": "bar"},
}

// parse template
tpl := MustParse(source)

// evaluate template with context
output, err := tpl.Exec(ctx)
if err != nil {
	panic(err)
}

fmt.Print(output)
Output:

<h1>foo</h1><p>bar</p>

func (*Template) ExecWith

func (tpl *Template) ExecWith(ctx interface{}, privData *DataFrame) (result string, err error)

ExecWith evaluates template with given context and private data frame.

Example
source := "<h1>{{title}}</h1><p>{{#body}}{{content}} and {{@baz.bat}}{{/body}}</p>"

ctx := map[string]interface{}{
	"title": "foo",
	"body":  map[string]string{"content": "bar"},
}

// parse template
tpl := MustParse(source)

// computes private data frame
frame := NewDataFrame()
frame.Set("baz", map[string]string{"bat": "unicorns"})

// evaluate template
output, err := tpl.ExecWith(ctx, frame)
if err != nil {
	panic(err)
}

fmt.Print(output)
Output:

<h1>foo</h1><p>bar and unicorns</p>

func (*Template) MustExec

func (tpl *Template) MustExec(ctx interface{}) string

MustExec evaluates template with given context. It panics on error.

Example
source := "<h1>{{title}}</h1><p>{{body.content}}</p>"

ctx := map[string]interface{}{
	"title": "foo",
	"body":  map[string]string{"content": "bar"},
}

// parse template
tpl := MustParse(source)

// evaluate template with context
output := tpl.MustExec(ctx)

fmt.Print(output)
Output:

<h1>foo</h1><p>bar</p>

func (*Template) PrintAST

func (tpl *Template) PrintAST() string

PrintAST returns string representation of parsed template.

Example
source := "<h1>{{title}}</h1><p>{{#body}}{{content}} and {{@baz.bat}}{{/body}}</p>"

// parse template
tpl := MustParse(source)

// print AST
output := tpl.PrintAST()

fmt.Print(output)
Output:

CONTENT[ '<h1>' ]
{{ PATH:title [] }}
CONTENT[ '</h1><p>' ]
BLOCK:
  PATH:body []
  PROGRAM:
    {{     PATH:content []
 }}
    CONTENT[ ' and ' ]
    {{     @PATH:baz/bat []
 }}
  CONTENT[ '</p>' ]

func (*Template) RegisterHelper

func (tpl *Template) RegisterHelper(name string, helper interface{})

RegisterHelper registers a helper for that template.

func (*Template) RegisterHelpers

func (tpl *Template) RegisterHelpers(helpers map[string]interface{})

RegisterHelpers registers several helpers for that template.

func (*Template) RegisterPartial

func (tpl *Template) RegisterPartial(name string, source string)

RegisterPartial registers a partial for that template.

func (*Template) RegisterPartialFile

func (tpl *Template) RegisterPartialFile(filePath string, name string) error

RegisterPartialFile reads given file and registers its content as a partial with given name.

func (*Template) RegisterPartialFiles

func (tpl *Template) RegisterPartialFiles(filePaths ...string) error

RegisterPartialFiles reads several files and registers them as partials, the filename base is used as the partial name.

func (*Template) RegisterPartialTemplate

func (tpl *Template) RegisterPartialTemplate(name string, template *Template)

RegisterPartialTemplate registers an already parsed partial for that template.

func (*Template) RegisterPartials

func (tpl *Template) RegisterPartials(partials map[string]string)

RegisterPartials registers several partials for that template.

Directories

Path Synopsis
Package ast provides structures to represent a handlebars Abstract Syntax Tree, and a Visitor interface to visit that tree.
Package ast provides structures to represent a handlebars Abstract Syntax Tree, and a Visitor interface to visit that tree.
Package handlebars contains all the tests that come from handlebars.js project.
Package handlebars contains all the tests that come from handlebars.js project.
Package lexer provides a handlebars tokenizer.
Package lexer provides a handlebars tokenizer.
Package parser provides a handlebars syntax analyser.
Package parser provides a handlebars syntax analyser.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL