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

handlebars

Go Reference

Handlebars for golang with the same features as handlebars.js 3.0. Hard fork of Raymond to modularize and keep up with handlebars development.

Handlebars Logo

Table of Contents

Quick Start

$ go get github.com/flowchartsman/handlebars/v3

The quick and dirty way of rendering a handlebars template:

{{title}}

{{body}}
` ctx := map[string]string{ "title": "My New Post", "body": "This is my first post!", } result, err := handlebars.Render(tpl, ctx) if err != nil { panic("Please report a bug :)") } fmt.Print(result) } ">
package main

import (
    "fmt"

    "github.com/flowchartsman/handlebars/v3"
)

func main() {
    tpl := `

{{title}}

{{body}}
` ctx := map[string]string{ "title": "My New Post", "body": "This is my first post!", } result, err := handlebars.Render(tpl, ctx) if err != nil { panic("Please report a bug :)") } fmt.Print(result) }

Displays:

My New Post

This is my first post!
">
<div class="entry">
  <h1>My New Posth1>
  <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:

{{title}}

{{body}}
` 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 := handlebars.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) } } ">
package main

import (
    "fmt"

    "github.com/flowchartsman/handlebars/v3
)

func main() {
    source := `

{{title}}

{{body}}
` 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 := handlebars.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:

My New Post

This is my first post!

Here is another post

This is my second post!
">
<div class="entry">
  <h1>My New Posth1>
  <div class="body">
    This is my first post!
  div>
div>
<div class="entry">
  <h1>Here is another posth1>
  <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 := handlebars.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.

By {{author.firstName}} {{author.lastName}}

{{body}}

Comments

{{#each comments}}

By {{author.firstName}} {{author.lastName}}

{{content}}
{{/each}}
` 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 := handlebars.MustRender(source, ctx) fmt.Print(output) } ">
package main

import (
  "fmt"

  "github.com/flowchartsman/handlebars/v3
)

func main() {
    source := `

By {{author.firstName}} {{author.lastName}}

{{body}}

Comments

{{#each comments}}

By {{author.firstName}} {{author.lastName}}

{{content}}
{{/each}}
`
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 := handlebars.MustRender(source, ctx) fmt.Print(output) }

Output:

By Jean Valjean

Life is difficult

Comments

By Marcel Beliveau

LOL!
">
<div class="post">
  <h1>By Jean Valjeanh1>
  <div class="body">Life is difficultdiv>

  <h1>Commentsh1>

  <h2>By Marcel Beliveauh2>
  <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.

{{title}}

{{{body}}}
` ctx := map[string]string{ "title": "All about

Tags", "body": "

This is a post about <p> tags

", } tpl := handlebars.MustParse(source) result := tpl.MustExec(ctx) fmt.Print(result) ">
source := `

{{title}}

{{{body}}}
` ctx := map[string]string{ "title": "All about

Tags", "body": "

This is a post about <p> tags

"
, } tpl := handlebars.MustParse(source) result := tpl.MustExec(ctx) fmt.Print(result)

Output:

All about <p> Tags

This is a post about <p> tags

">
<div class="entry">
  <h1>All about <p> Tagsh1>
  <div class="body">
    <p>This is a post about <p> tagsp>
  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.

" + handlebars.Escape(text) + "") }) tpl := handlebars.MustParse("{{link url text}}") ctx := map[string]string{ "url": "http://www.aymerick.com/", "text": "This is a cool website", } result := tpl.MustExec(ctx) fmt.Print(result) ">
handlebars.RegisterHelper("link", func(url, text string) handlebars.SafeString {
    return handlebars.SafeString("" + handlebars.Escape(text) + "")
})

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

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

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

Output:

<a href='http://www.aymerick.com/'>This is a <em>cool</em> websitea>

Helpers

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

For example:

By {{fullName author}}

{{body}}

Comments

{{#each comments}}

By {{fullName author}}

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

  <h1>Commentsh1>

  {{#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!",
    }},
}

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

Outputs:

By Jean Valjean

Life is difficult

Comments

By Marcel Beliveau

LOL!
">
<div class="post">
  <h1>By Jean Valjeanh1>
  <div class="body">Life is difficultdiv>

  <h1>Commentsh1>

  <h2>By Marcel Beliveauh2>
  <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:

By {{fullName author}}

{{body}}

Comments

{{#each comments}}

By {{fullName author}}

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

  <h1>Commentsh1>

  {{#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!",
        },
    },
}

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

You can unregister global helpers with RemoveHelper and RemoveAllHelpers functions:

handlebars.RemoveHelper("fullname")
handlebars.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 := handlebars.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 handlebars will not render the block.

{{#if author}}

{{firstName}} {{lastName}}

{{/if}}
">
<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".

{{#if author}}

{{firstName}} {{lastName}}

{{else}}

Unknown Author

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

You can chain several blocks. For example that template:

{{else if isInactive}} Inactive {{else}} Unknown {{/if}} ">
{{#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.

{{#unless license}}

WARNING: This entry does not have a license!

{{/unless}}
">
<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:

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

    With this context:

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

    Outputs:

  • Marcel
  • Jean-Claude
  • Yvette
  • ">
    <ul class="people">
      <li>Marcelli>
      <li>Jean-Claudeli>
      <li>Yvetteli>
    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).

    No content

    {{/each}} ">
    {{#each paragraphs}}
      <p>{{this}}p>
    {{else}}
      <p class="empty">No contentp>
    {{/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.

    {{title}}

    {{#with author}}

    By {{firstName}} {{lastName}}

    {{/with}}
    ">
    <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:

    My first post!

    By Jean Valjean

    ">
    <div class="entry">
      <h1>My first post!h1>
    
      <h2>By Jean Valjeanh2>
    div>

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

    No content

    {{/with}} ">
    {{#with author}}
      <p>{{name}}p>
    {{else}}
      <p class="empty">No contentp>
    {{/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.

    {{title}}

    {{#bold}}{{body}}{{/bold}}
    ">
    <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.

    ` + options.Fn() + "
    ") }) ">
    handlebars.RegisterHelper("bold", func(options *handlebars.Options) handlebars.SafeString {
        return handlebars.SafeString(`
    ` + options.Fn() + "
    "
    ) })

    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:

    handlebars.RegisterHelper("avec", func(context interface{}, options *handlebars.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}
    
    handlebars.RegisterHelper("si", func(conditional bool, options *handlebars.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}
    
    handlebars.RegisterHelper("si", func(conditional bool, options *handlebars.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, handlebars expects the same number of arguments as the number of helper function parameters.

    So this template:

    {{add a}}

    With this helper:

    handlebars.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",
    }
    
    handlebars.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, handlebars 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:

    handlebars.RegisterHelper("add", func(val1, val2 int, options *handlebars.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 !",
    }
    
    handlebars.RegisterHelper("concat", func(val1, val2 string, options *handlebars.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 !",
    }
    
    handlebars.RegisterHelper("concat", func(suffix string, options *handlebars.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",
    }
    
    handlebars.RegisterHelper("voodoo", func(options *handlebars.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(), handlebars provides utility functions that can be usefull for helpers.

    Str()

    Str() converts its parameter to a string.

    Booleans:

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

    Numbers:

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

    Maps:

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

    Arrays and Slices:

    handlebars.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:

    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:

    foo}} baz") tpl.RegisterPartial("foo", "bar") result := tpl.MustExec(nil) fmt.Print(result) ">
    tpl := handlebars.MustParse("{{> foo}} baz")
    tpl.RegisterPartial("foo", "bar")
    
    result := tpl.MustExec(nil)
    fmt.Print(result)

    Output:

    <span>barspan> baz

    You can register several partials at once:

    foo}} and {{> baz}}") tpl.RegisterPartials(map[string]string{ "foo": "bar", "baz": "bat", }) result := tpl.MustExec(nil) fmt.Print(result) ">
    tpl := handlebars.MustParse("{{> foo}} and {{> baz}}")
    tpl.RegisterPartials(map[string]string{
        "foo": "bar",
        "baz": "bat",
    })
    
    result := tpl.MustExec(nil)
    fmt.Print(result)

    Output:

    <span>barspan> and <span>batspan>

    Global Partials

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

    bar") tpl := handlebars.MustParse("{{> foo}} baz") result := tpl.MustExec(nil) fmt.Print(result) ">
    handlebars.RegisterPartial("foo", "bar")
    
    tpl := handlebars.MustParse("{{> foo}} baz")
    result := tpl.MustExec(nil)
    fmt.Print(result)

    Or:

    bar", "baz": "bat", }) tpl := handlebars.MustParse("{{> foo}} and {{> baz}}") result := tpl.MustExec(nil) fmt.Print(result) ">
    handlebars.RegisterPartials(map[string]string{
        "foo": "bar",
        "baz": "bat",
    })
    
    tpl := handlebars.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:

    (whichPartial) }}") tpl.RegisterPartials(map[string]string{ "foo": "bar", "baz": "bat", }) 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) ">
    tpl := handlebars.MustParse("{{> (whichPartial) }}")
    tpl.RegisterPartials(map[string]string{
        "foo": "bar",
        "baz": "bat",
    })
    
    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:

    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) ">
    tpl := handlebars.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:

    myPartial name=hero }}") tpl.RegisterPartial("myPartial", "My hero is {{name}}") ctx := map[string]interface{}{ "hero": "Goldorak", } result := tpl.MustExec(ctx) fmt.Print(result) ">
    tpl := handlebars.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:

    Mustache

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

    Limitations

    These handlebars options are currently NOT implemented:

    These handlebars features are currently NOT implemented:

    Handlebars Lexer

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

    package main
    
    import (
        "fmt"
    
        "github.com/flowchartsman/handlebars/v3/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/flowchartsman/handlebars/v3/ast"
        "github.com/flowchartsman/handlebars/v3/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

    Owner
    Andy Walker
    Andy Walker
    Similar Resources

    Pagser is a simple, extensible, configurable parse and deserialize html page to struct based on goquery and struct tags for golang crawler

    Pagser is a simple, extensible, configurable parse and deserialize html page to struct based on goquery and struct tags for golang crawler

    Pagser Pagser inspired by page parser。 Pagser is a simple, extensible, configurable parse and deserialize html page to struct based on goquery and str

    Dec 13, 2022

    iTunes and RSS 2.0 Podcast Generator in Golang

    podcast Package podcast generates a fully compliant iTunes and RSS 2.0 podcast feed for GoLang using a simple API. Full documentation with detailed ex

    Dec 23, 2022

    TOML parser for Golang with reflection.

    THIS PROJECT IS UNMAINTAINED The last commit to this repo before writing this message occurred over two years ago. While it was never my intention to

    Dec 30, 2022

    Your CSV pocket-knife (golang)

    csvutil - Your CSV pocket-knife (golang) #WARNING I would advise against using this package. It was a language learning exercise from a time before "e

    Oct 24, 2022

    Golang (Go) bindings for GNU's gettext (http://www.gnu.org/software/gettext/)

    gosexy/gettext Go bindings for GNU gettext, an internationalization and localization library for writing multilingual systems. Requeriments GNU gettex

    Nov 16, 2022

    agrep-like fuzzy matching, but made faster using Golang and precomputation.

    goagrep There are situations where you want to take the user's input and match a primary key in a database. But, immediately a problem is introduced:

    Oct 8, 2022

    Ngram index for golang

    go-ngram N-gram index for Go. Key features Unicode support. Append only. Data can't be deleted from index. GC friendly (all strings are pooled and com

    Dec 29, 2022

    String-matching in Golang using the Knuth–Morris–Pratt algorithm (KMP)

    gokmp String-matching in Golang using the Knuth–Morris–Pratt algorithm (KMP). Disclaimer This library was written as part of my Master's Thesis and sh

    Dec 8, 2022

    Golang HTML to plaintext conversion library

    html2text Converts HTML into text of the markdown-flavored variety Introduction Ensure your emails are readable by all! Turns HTML into raw text, usef

    Dec 28, 2022
    Comments
    • Q: How to use external hbs file

      Q: How to use external hbs file

      I have an external handlebar file to use on an email. I am new to go so sorry.

      All i did was:

      tpl, err := handlebars.Parse("../emails/order_email.hbs")
      	if err != nil {
      		log.Fatal("Html parser error", err)
      	}
      	orderHTML, err := tpl.Exec(data) // the data to use to hbs
      	if err != nil {
      			panic(err)
      	}
      

      Thanks, also i am using helpers, hoping it is correct.

      On the email i see: ../emails/order_email.hbs

    • Bracket in identifier name causing panic in #if helper

      Bracket in identifier name causing panic in #if helper

      As mentioned in https://github.com/aymerick/raymond/issues/34, the following will panic:

      {{#if wat[0].fnord}}wat{{/if}}

      But so will this:

      {{#if wat[0]}}wat{{/if}}

      I believe the original issue is something of a red herring, since you're not supposed to access array elements like this in handlebars. The correct notation would be:

      {{#if wat.[0]}}wat{{/if}}

      Which does NOT panic if wat does not exist.

      So the question isn't so much why a missing reference is panicking, it's why the bracket is being allowed (or not) when it should be (or not), thus causing a panic.

    [Crawler/Scraper for Golang]🕷A lightweight distributed friendly Golang crawler framework.一个轻量的分布式友好的 Golang 爬虫框架。

    Goribot 一个分布式友好的轻量的 Golang 爬虫框架。 完整文档 | Document !! Warning !! Goribot 已经被迁移到 Gospider|github.com/zhshch2002/gospider。修复了一些调度问题并分离了网络请求部分到另一个仓库。此仓库会继续

    Oct 29, 2022
    golang 在线预览word,excel,pdf,MarkDown(Online Preview Word,Excel,PPT,PDF,Image by Golang)
    golang 在线预览word,excel,pdf,MarkDown(Online Preview Word,Excel,PPT,PDF,Image by Golang)

    Go View File 在线体验地址 http://39.97.98.75:8082/view/upload (不会经常更新,保留最基本的预览功能。服务器配置较低,如果出现链接超时请等待几秒刷新重试,或者换Chrome) 目前已经完成 docker部署 (不用为运行环境烦恼) Wor

    Dec 26, 2022
    bluemonday: a fast golang HTML sanitizer (inspired by the OWASP Java HTML Sanitizer) to scrub user generated content of XSS

    bluemonday bluemonday is a HTML sanitizer implemented in Go. It is fast and highly configurable. bluemonday takes untrusted user generated content as

    Jan 4, 2023
    Elegant Scraper and Crawler Framework for Golang

    Colly Lightning Fast and Elegant Scraping Framework for Gophers Colly provides a clean interface to write any kind of crawler/scraper/spider. With Col

    Dec 30, 2022
    A golang package to work with Decentralized Identifiers (DIDs)

    did did is a Go package that provides tools to work with Decentralized Identifiers (DIDs). Install go get github.com/ockam-network/did Example packag

    Nov 25, 2022
    wcwidth for golang

    go-runewidth Provides functions to get fixed width of the character or string. Usage runewidth.StringWidth("つのだ☆HIRO") == 12 Author Yasuhiro Matsumoto

    Dec 11, 2022
    Parses the Graphviz DOT language in golang

    Parses the Graphviz DOT language and creates an interface, in golang, with which to easily create new and manipulate existing graphs which can be writ

    Dec 25, 2022
    Go (Golang) GNU gettext utilities package

    Gotext GNU gettext utilities for Go. Features Implements GNU gettext support in native Go. Complete support for PO files including: Support for multil

    Dec 18, 2022
    htmlquery is golang XPath package for HTML query.

    htmlquery Overview htmlquery is an XPath query package for HTML, lets you extract data or evaluate from HTML documents by an XPath expression. htmlque

    Jan 4, 2023
    omniparser: a native Golang ETL streaming parser and transform library for CSV, JSON, XML, EDI, text, etc.
    omniparser: a native Golang ETL streaming parser and transform library for CSV, JSON, XML, EDI, text, etc.

    omniparser Omniparser is a native Golang ETL parser that ingests input data of various formats (CSV, txt, fixed length/width, XML, EDI/X12/EDIFACT, JS

    Jan 4, 2023