# raymond [![Build Status](https://secure.travis-ci.org/aymerick/raymond.svg?branch=master)](http://travis-ci.org/aymerick/raymond) [![GoDoc](https://godoc.org/github.com/aymerick/raymond?status.svg)](http://godoc.org/github.com/aymerick/raymond) Handlebars for [golang](https://golang.org) with the same features as [handlebars.js](http://handlebarsjs.com) `3.0`. The full API documentation is available here: . ![Raymond Logo](https://github.com/aymerick/raymond/blob/master/raymond.png?raw=true "Raymond") # Table of Contents - [Quick Start](#quick-start) - [Correct Usage](#correct-usage) - [Context](#context) - [HTML Escaping](#html-escaping) - [Helpers](#helpers) - [Template Helpers](#template-helpers) - [Built-In Helpers](#built-in-helpers) - [The `if` block helper](#the-if-block-helper) - [The `unless` block helper](#the-unless-block-helper) - [The `each` block helper](#the-each-block-helper) - [The `with` block helper](#the-with-block-helper) - [The `lookup` helper](#the-lookup-helper) - [The `log` helper](#the-log-helper) - [Block Helpers](#block-helpers) - [Block Evaluation](#block-evaluation) - [Conditional](#conditional) - [Else Block Evaluation](#else-block-evaluation) - [Block Parameters](#block-parameters) - [Helper Parameters](#helper-parameters) - [Automatic conversion](#automatic-conversion) - [Options Argument](#options-argument) - [Context Values](#context-values) - [Helper Hash Arguments](#helper-hash-arguments) - [Private Data](#private-data) - [Utilites](#utilites) - [`Str()`](#str) - [`IsTrue()`](#istrue) - [Context Functions](#context-functions) - [Partials](#partials) - [Template Partials](#template-partials) - [Global Partials](#global-partials) - [Dynamic Partials](#dynamic-partials) - [Partial Contexts](#partial-contexts) - [Partial Parameters](#partial-parameters) - [Utility Functions](#utility-functions) - [Mustache](#mustache) - [Limitations](#limitations) - [Handlebars Lexer](#handlebars-lexer) - [Handlebars Parser](#handlebars-parser) - [Test](#test) - [References](#references) - [Others Implementations](#others-implementations) ## Quick Start $ go get github.com/aymerick/raymond The quick and dirty way of rendering a handlebars template: ```go package main import ( "fmt" "github.com/aymerick/raymond" ) func main() { tpl := `

{{title}}

{{body}}
` 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 fill a bug :)") } fmt.Print(result) } ``` Displays: ```html

My New Post

This is my first post!
``` 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: ```go package main import ( "fmt" "github.com/aymerick/raymond" ) 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 := 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: ```html

My New Post

This is my first post!

Here is another post

This is my second post!
``` You can use `MustParse()` and `MustExec()` functions if you don't want to deal with errors: ```go // 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. ```go package main import ( "fmt" "github.com/aymerick/raymond" ) func main() { source := `

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

{{body}}

Comments

{{#each comments}}

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

{{body}}
{{/each}}
` 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!", }, }, } output := raymond.MustRender(source, ctx) fmt.Print(output) } ``` Output: ```html

By Jean Valjean

Life is difficult

Comments

By Marcel Beliveau

LOL!
``` ## HTML Escaping By default, the result of a mustache expression is HTML escaped. Use the triple mustache `{{{` to output unescaped values. ```go source := `

{{title}}

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

Tags", "body": "

This is a post about <p> tags

", } tpl := raymond.MustParse(source) result := tpl.MustExec(ctx) fmt.Print(result) ``` Output: ```html

All about <p> Tags

This is a post about <p> tags

``` 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. ```go raymond.RegisterHelper("link", func(url, text string) raymond.SafeString { return raymond.SafeString("" + raymond.Escape(text) + "") }) tpl := raymond.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: ```html This is a <em>cool</em> website ``` ## Helpers Helpers can be accessed from any context in a template. You can register a helper with the `RegisterHelper` function. For example: ```html

By {{fullName author}}

{{body}}

Comments

{{#each comments}}

By {{fullName author}}

{{body}}
{{/each}}
``` With this context and helper: ```go 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: ```html

By Jean Valjean

Life is difficult

Comments

By Marcel Beliveau

LOL!
``` Helper arguments can be any type. The following example uses structs instead of maps and produces the same output as the previous one: ```html

By {{fullName author}}

{{body}}

Comments

{{#each comments}}

By {{fullName author}}

{{body}}
{{/each}}
``` With this context and helper: ```go 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!", }, }, } RegisterHelper("fullName", func(person Person) string { return person.FirstName + " " + person.LastName }) ``` ### Template Helpers You can register a helper on a specific template, and in that case that helper will be available to that template only: ```go 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. ```html
{{#if author}}

{{firstName}} {{lastName}}

{{/if}}
``` 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". ```html
{{#if author}}

{{firstName}} {{lastName}}

{{else}}

Unknown Author

{{/if}}
``` You can chain several blocks. For example that template: ```html {{#if isActive}} Active {{else if isInactive}} Inactive {{else}} Unknown {{/if}} ``` With that context: ```go ctx := map[string]interface{}{ "isActive": false, "isInactive": false, } ``` Outputs: ```html 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. ```html
{{#unless license}}

WARNING: This entry does not have a license!

{{/unless}}
``` #### 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: ```html ``` With this context: ```go map[string]interface{}{ "people": []string{ "Marcel", "Jean-Claude", "Yvette", }, } ``` Outputs: ```html ``` 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). ```html {{#each paragraphs}}

{{this}}

{{else}}

No content

{{/each}} ``` When looping through items in `each`, you can optionally reference the current loop index via `{{@index}}`. ```html {{#each array}} {{@index}}: {{this}} {{/each}} ``` Additionally for map and struct instance iteration, `{{@key}}` references the current map key or struct field name: ```html {{#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. ```html

{{title}}

{{#with author}}

By {{firstName}} {{lastName}}

{{/with}}
``` With this context: ```go map[string]interface{}{ "title": "My first post!", "author": map[string]string{ "firstName": "Jean", "lastName": "Valjean", }, } ``` Outputs: ```html

My first post!

By Jean Valjean

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

{{name}}

{{else}}

No content

{{/with}} ``` #### The `lookup` helper The `lookup` helper allows for dynamic parameter resolution using handlebars variables. ```html {{#each bar}} {{lookup ../foo @index}} {{/each}} ``` #### The `log` helper The `log` helper allows for logging while rendering a template. ```html {{log "Look at me!"}} ``` Note that the handlebars.js `@level` variable is not supported. ### 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. ```html

{{title}}

{{#bold}}{{body}}{{/bold}}
``` The `bold` helper will add markup to make its text bold. ```go raymond.RegisterHelper("bold", func(options *raymond.Options) raymond.SafeString { return raymond.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: ```go raymond.RegisterHelper("avec", func(context interface{}, options *raymond.Options) string { return options.FnWith(context) }) ``` With that template: ```html {{#avec obj.text}}{{this}}{{/avec}} ``` #### Conditional Let's write a french version of `if` block helper: ```go 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: ```go 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: ```go 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. ```html {{#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: ```html {{#each users as |user userId|}} {{#each user.books as |book bookId|}} User: {{userId}} Book: {{bookId}} {{/each}} {{/each}} ``` With this context: ```go 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: ```html 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: ```go 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: ```html 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: ```html {{add a}} ``` With this helper: ```go 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: ```go 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: ```html Jean VALJEAN ``` But what happens if there is another type than `string` in the context ? For example: ```go 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: ```html 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: ```go 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: ```go 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: ```html 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: ```go 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: ```html 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: ```go 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: ```go raymond.Str(3) + " foos and " + raymond.Str(-1.25) + " bars" // Outputs: "3 foos and -1.25 bars" ``` Numbers: ``` go "everything is " + raymond.Str(true) + " and nothing is " + raymond.Str(false) // Outputs: "everything is true and nothing is false" ``` Maps: ```go raymond.Str(map[string]string{"foo": "bar"}) // Outputs: "map[foo:bar]" ``` Arrays and Slices: ```go 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: ```go 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: ```go tpl := raymond.MustParse("{{> foo}} baz") tpl.RegisterPartial("foo", "bar") result := tpl.MustExec(nil) fmt.Print(result) ``` Output: ```html bar baz ``` You can register several partials at once: ```go tpl := raymond.MustParse("{{> foo}} and {{> baz}}") tpl.RegisterPartials(map[string]string{ "foo": "bar", "baz": "bat", }) result := tpl.MustExec(nil) fmt.Print(result) ``` Output: ```html bar and bat ``` ### Global Partials You can registers global partials that will be accessible by all templates: ```go raymond.RegisterPartial("foo", "bar") tpl := raymond.MustParse("{{> foo}} baz") result := tpl.MustExec(nil) fmt.Print(result) ``` Or: ```go raymond.RegisterPartials(map[string]string{ "foo": "bar", "baz": "bat", }) 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: ```go tpl := raymond.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: ```go 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: ```html User: Jean Valjean ``` ### Partial Parameters Custom data can be passed to partials through hash parameters. For example: ```go 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: ```html 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](https://mustache.github.io) 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: ```go package main import ( "fmt" "github.com/aymerick/raymond/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: ```go package main import ( "fmt" "github.com/aymerick/raymond/ast" "github.com/aymerick/raymond/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 - [handlebars.js](http://handlebarsjs.com) - javascript - [handlebars.java](https://github.com/jknack/handlebars.java) - java - [handlebars.rb](https://github.com/cowboyd/handlebars.rb) - ruby - [handlebars.php](https://github.com/XaminProject/handlebars.php) - php - [handlebars-objc](https://github.com/Bertrand/handlebars-objc) - Objective C - [rumblebars](https://github.com/nicolas-cherel/rumblebars) - rust