{"id":13413852,"url":"https://github.com/aymerick/raymond","last_synced_at":"2025-04-09T01:23:03.561Z","repository":{"id":30833452,"uuid":"34390866","full_name":"aymerick/raymond","owner":"aymerick","description":"Handlebars for golang","archived":false,"fork":false,"pushed_at":"2024-08-10T05:37:57.000Z","size":445,"stargazers_count":621,"open_issues_count":21,"forks_count":108,"subscribers_count":12,"default_branch":"master","last_synced_at":"2025-04-02T00:18:31.444Z","etag":null,"topics":["go","handlebars"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aymerick.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-04-22T13:07:59.000Z","updated_at":"2025-04-01T03:25:40.000Z","dependencies_parsed_at":"2024-09-25T10:42:16.091Z","dependency_job_id":"b5411283-6379-4df8-8c24-2f6206066e71","html_url":"https://github.com/aymerick/raymond","commit_stats":{"total_commits":319,"total_committers":6,"mean_commits":"53.166666666666664","dds":"0.018808777429467072","last_synced_commit":"b565731e1464263de0bda75f2e45d97b54b60110"},"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aymerick%2Fraymond","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aymerick%2Fraymond/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aymerick%2Fraymond/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aymerick%2Fraymond/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aymerick","download_url":"https://codeload.github.com/aymerick/raymond/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247953903,"owners_count":21024112,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","handlebars"],"created_at":"2024-07-30T20:01:51.194Z","updated_at":"2025-04-09T01:23:03.531Z","avatar_url":"https://github.com/aymerick.png","language":"Go","funding_links":[],"categories":["Template Engines","模板引擎","模板引擎`模版渲染和模版生成处理库`","Relational Databases","\u003cspan id=\"模板引擎-template-engines\"\u003e模板引擎 Template Engines\u003c/span\u003e"],"sub_categories":["HTTP Clients","Advanced Console UIs","HTTP客户端","查询语","高级控制台界面","\u003cspan id=\"高级控制台用户界面-advanced-console-uis\"\u003e高级控制台用户界面 Advanced Console UIs\u003c/span\u003e","高級控制台界面","交流"],"readme":"# 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)\n\nHandlebars for [golang](https://golang.org) with the same features as [handlebars.js](http://handlebarsjs.com) `3.0`.\n\nThe full API documentation is available here: \u003chttp://godoc.org/github.com/aymerick/raymond\u003e.\n\n![Raymond Logo](https://github.com/aymerick/raymond/blob/master/raymond.png?raw=true \"Raymond\")\n\n\n# Table of Contents\n\n- [Quick Start](#quick-start)\n- [Correct Usage](#correct-usage)\n- [Context](#context)\n- [HTML Escaping](#html-escaping)\n- [Helpers](#helpers)\n  - [Template Helpers](#template-helpers)\n  - [Built-In Helpers](#built-in-helpers)\n    - [The `if` block helper](#the-if-block-helper)\n    - [The `unless` block helper](#the-unless-block-helper)\n    - [The `each` block helper](#the-each-block-helper)\n    - [The `with` block helper](#the-with-block-helper)\n    - [The `lookup` helper](#the-lookup-helper)\n    - [The `log` helper](#the-log-helper)\n    - [The `equal` helper](#the-equal-helper)\n  - [Block Helpers](#block-helpers)\n    - [Block Evaluation](#block-evaluation)\n    - [Conditional](#conditional)\n    - [Else Block Evaluation](#else-block-evaluation)\n    - [Block Parameters](#block-parameters)\n  - [Helper Parameters](#helper-parameters)\n    - [Automatic conversion](#automatic-conversion)\n  - [Options Argument](#options-argument)\n    - [Context Values](#context-values)\n    - [Helper Hash Arguments](#helper-hash-arguments)\n    - [Private Data](#private-data)\n  - [Utilites](#utilites)\n    - [`Str()`](#str)\n    - [`IsTrue()`](#istrue)\n- [Context Functions](#context-functions)\n- [Partials](#partials)\n  - [Template Partials](#template-partials)\n  - [Global Partials](#global-partials)\n  - [Dynamic Partials](#dynamic-partials)\n  - [Partial Contexts](#partial-contexts)\n  - [Partial Parameters](#partial-parameters)\n- [Utility Functions](#utility-functions)\n- [Mustache](#mustache)\n- [Limitations](#limitations)\n- [Handlebars Lexer](#handlebars-lexer)\n- [Handlebars Parser](#handlebars-parser)\n- [Test](#test)\n- [References](#references)\n- [Others Implementations](#others-implementations)\n\n\n## Quick Start\n\n    $ go get github.com/aymerick/raymond\n\nThe quick and dirty way of rendering a handlebars template:\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n\n    \"github.com/aymerick/raymond\"\n)\n\nfunc main() {\n    tpl := `\u003cdiv class=\"entry\"\u003e\n  \u003ch1\u003e{{title}}\u003c/h1\u003e\n  \u003cdiv class=\"body\"\u003e\n    {{body}}\n  \u003c/div\u003e\n\u003c/div\u003e\n`\n\n    ctx := map[string]string{\n        \"title\": \"My New Post\",\n        \"body\":  \"This is my first post!\",\n    }\n\n    result, err := raymond.Render(tpl, ctx)\n    if err != nil {\n        panic(\"Please report a bug :)\")\n    }\n\n    fmt.Print(result)\n}\n```\n\nDisplays:\n\n```html\n\u003cdiv class=\"entry\"\u003e\n  \u003ch1\u003eMy New Post\u003c/h1\u003e\n  \u003cdiv class=\"body\"\u003e\n    This is my first post!\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\nPlease note that the template will be parsed everytime you call `Render()` function. So you probably want to read the next section.\n\n\n## Correct Usage\n\nTo avoid parsing a template several times, use the `Parse()` and `Exec()` functions:\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n\n    \"github.com/aymerick/raymond\"\n)\n\nfunc main() {\n    source := `\u003cdiv class=\"entry\"\u003e\n  \u003ch1\u003e{{title}}\u003c/h1\u003e\n  \u003cdiv class=\"body\"\u003e\n    {{body}}\n  \u003c/div\u003e\n\u003c/div\u003e\n`\n\n    ctxList := []map[string]string{\n        {\n            \"title\": \"My New Post\",\n            \"body\":  \"This is my first post!\",\n        },\n        {\n            \"title\": \"Here is another post\",\n            \"body\":  \"This is my second post!\",\n        },\n    }\n\n    // parse template\n    tpl, err := raymond.Parse(source)\n    if err != nil {\n        panic(err)\n    }\n\n    for _, ctx := range ctxList {\n        // render template\n        result, err := tpl.Exec(ctx)\n        if err != nil {\n            panic(err)\n        }\n\n        fmt.Print(result)\n    }\n}\n\n```\n\nDisplays:\n\n```html\n\u003cdiv class=\"entry\"\u003e\n  \u003ch1\u003eMy New Post\u003c/h1\u003e\n  \u003cdiv class=\"body\"\u003e\n    This is my first post!\n  \u003c/div\u003e\n\u003c/div\u003e\n\u003cdiv class=\"entry\"\u003e\n  \u003ch1\u003eHere is another post\u003c/h1\u003e\n  \u003cdiv class=\"body\"\u003e\n    This is my second post!\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\nYou can use `MustParse()` and `MustExec()` functions if you don't want to deal with errors:\n\n```go\n// parse template\ntpl := raymond.MustParse(source)\n\n// render template\nresult := tpl.MustExec(ctx)\n```\n\n\n## Context\n\nThe rendering context can contain any type of values, including `array`, `slice`, `map`, `struct` and `func`.\n\nWhen 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.\n\nMore, you can use the `handlebars` struct tag to specify a template variable name different from the struct field name.\n\n```go\npackage main\n\nimport (\n  \"fmt\"\n\n  \"github.com/aymerick/raymond\"\n)\n\nfunc main() {\n    source := `\u003cdiv class=\"post\"\u003e\n  \u003ch1\u003eBy {{author.firstName}} {{author.lastName}}\u003c/h1\u003e\n  \u003cdiv class=\"body\"\u003e{{body}}\u003c/div\u003e\n\n  \u003ch1\u003eComments\u003c/h1\u003e\n\n  {{#each comments}}\n  \u003ch2\u003eBy {{author.firstName}} {{author.lastName}}\u003c/h2\u003e\n  \u003cdiv class=\"body\"\u003e{{content}}\u003c/div\u003e\n  {{/each}}\n\u003c/div\u003e`\n\n    type Person struct {\n        FirstName string\n        LastName  string\n    }\n\n    type Comment struct {\n        Author Person\n        Body   string `handlebars:\"content\"`\n    }\n\n    type Post struct {\n        Author   Person\n        Body     string\n        Comments []Comment\n    }\n\n    ctx := Post{\n        Person{\"Jean\", \"Valjean\"},\n        \"Life is difficult\",\n        []Comment{\n            Comment{\n                Person{\"Marcel\", \"Beliveau\"},\n                \"LOL!\",\n            },\n        },\n    }\n\n    output := raymond.MustRender(source, ctx)\n\n    fmt.Print(output)\n}\n```\n\nOutput:\n\n```html\n\u003cdiv class=\"post\"\u003e\n  \u003ch1\u003eBy Jean Valjean\u003c/h1\u003e\n  \u003cdiv class=\"body\"\u003eLife is difficult\u003c/div\u003e\n\n  \u003ch1\u003eComments\u003c/h1\u003e\n\n  \u003ch2\u003eBy Marcel Beliveau\u003c/h2\u003e\n  \u003cdiv class=\"body\"\u003eLOL!\u003c/div\u003e\n\u003c/div\u003e\n```\n\n## HTML Escaping\n\nBy default, the result of a mustache expression is HTML escaped. Use the triple mustache `{{{` to output unescaped values.\n\n```go\nsource := `\u003cdiv class=\"entry\"\u003e\n  \u003ch1\u003e{{title}}\u003c/h1\u003e\n  \u003cdiv class=\"body\"\u003e\n    {{{body}}}\n  \u003c/div\u003e\n\u003c/div\u003e\n`\n\nctx := map[string]string{\n    \"title\": \"All about \u003cp\u003e Tags\",\n    \"body\":  \"\u003cp\u003eThis is a post about \u0026lt;p\u0026gt; tags\u003c/p\u003e\",\n}\n\ntpl := raymond.MustParse(source)\nresult := tpl.MustExec(ctx)\n\nfmt.Print(result)\n```\n\nOutput:\n\n```html\n\u003cdiv class=\"entry\"\u003e\n  \u003ch1\u003eAll about \u0026lt;p\u0026gt; Tags\u003c/h1\u003e\n  \u003cdiv class=\"body\"\u003e\n    \u003cp\u003eThis is a post about \u0026lt;p\u0026gt; tags\u003c/p\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\nWhen 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.\n\n```go\nraymond.RegisterHelper(\"link\", func(url, text string) raymond.SafeString {\n    return raymond.SafeString(\"\u003ca href='\" + raymond.Escape(url) + \"'\u003e\" + raymond.Escape(text) + \"\u003c/a\u003e\")\n})\n\ntpl := raymond.MustParse(\"{{link url text}}\")\n\nctx := map[string]string{\n    \"url\":  \"http://www.aymerick.com/\",\n    \"text\": \"This is a \u003cem\u003ecool\u003c/em\u003e website\",\n}\n\nresult := tpl.MustExec(ctx)\nfmt.Print(result)\n```\n\nOutput:\n\n```html\n\u003ca href='http://www.aymerick.com/'\u003eThis is a \u0026lt;em\u0026gt;cool\u0026lt;/em\u0026gt; website\u003c/a\u003e\n```\n\n\n## Helpers\n\nHelpers can be accessed from any context in a template. You can register a helper with the `RegisterHelper` function.\n\nFor example:\n\n```html\n\u003cdiv class=\"post\"\u003e\n  \u003ch1\u003eBy {{fullName author}}\u003c/h1\u003e\n  \u003cdiv class=\"body\"\u003e{{body}}\u003c/div\u003e\n\n  \u003ch1\u003eComments\u003c/h1\u003e\n\n  {{#each comments}}\n  \u003ch2\u003eBy {{fullName author}}\u003c/h2\u003e\n  \u003cdiv class=\"body\"\u003e{{body}}\u003c/div\u003e\n  {{/each}}\n\u003c/div\u003e\n```\n\nWith this context and helper:\n\n```go\nctx := map[string]interface{}{\n    \"author\": map[string]string{\"firstName\": \"Jean\", \"lastName\": \"Valjean\"},\n    \"body\":   \"Life is difficult\",\n    \"comments\": []map[string]interface{}{{\n        \"author\": map[string]string{\"firstName\": \"Marcel\", \"lastName\": \"Beliveau\"},\n        \"body\":   \"LOL!\",\n    }},\n}\n\nraymond.RegisterHelper(\"fullName\", func(person map[string]string) string {\n    return person[\"firstName\"] + \" \" + person[\"lastName\"]\n})\n```\n\nOutputs:\n\n```html\n\u003cdiv class=\"post\"\u003e\n  \u003ch1\u003eBy Jean Valjean\u003c/h1\u003e\n  \u003cdiv class=\"body\"\u003eLife is difficult\u003c/div\u003e\n\n  \u003ch1\u003eComments\u003c/h1\u003e\n\n  \u003ch2\u003eBy Marcel Beliveau\u003c/h2\u003e\n  \u003cdiv class=\"body\"\u003eLOL!\u003c/div\u003e\n\u003c/div\u003e\n```\n\nHelper arguments can be any type.\n\nThe following example uses structs instead of maps and produces the same output as the previous one:\n\n```html\n\u003cdiv class=\"post\"\u003e\n  \u003ch1\u003eBy {{fullName author}}\u003c/h1\u003e\n  \u003cdiv class=\"body\"\u003e{{body}}\u003c/div\u003e\n\n  \u003ch1\u003eComments\u003c/h1\u003e\n\n  {{#each comments}}\n  \u003ch2\u003eBy {{fullName author}}\u003c/h2\u003e\n  \u003cdiv class=\"body\"\u003e{{body}}\u003c/div\u003e\n  {{/each}}\n\u003c/div\u003e\n```\n\nWith this context and helper:\n\n```go\ntype Post struct {\n    Author   Person\n    Body     string\n    Comments []Comment\n}\n\ntype Person struct {\n    FirstName string\n    LastName  string\n}\n\ntype Comment struct {\n    Author Person\n    Body   string\n}\n\nctx := Post{\n    Person{\"Jean\", \"Valjean\"},\n    \"Life is difficult\",\n    []Comment{\n        Comment{\n            Person{\"Marcel\", \"Beliveau\"},\n            \"LOL!\",\n        },\n    },\n}\n\nraymond.RegisterHelper(\"fullName\", func(person Person) string {\n    return person.FirstName + \" \" + person.LastName\n})\n```\n\nYou can unregister global helpers with `RemoveHelper` and `RemoveAllHelpers` functions:\n\n```go\nraymond.RemoveHelper(\"fullname\")\n```\n\n```go\nraymond.RemoveAllHelpers()\n```\n\n\n### Template Helpers\n\nYou can register a helper on a specific template, and in that case that helper will be available to that template only:\n\n```go\ntpl := raymond.MustParse(\"User: {{fullName user.firstName user.lastName}}\")\n\ntpl.RegisterHelper(\"fullName\", func(firstName, lastName string) string {\n  return firstName + \" \" + lastName\n})\n```\n\n\n### Built-In Helpers\n\nThose built-in helpers are available to all templates.\n\n\n#### The `if` block helper\n\nYou 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.\n\n```html\n\u003cdiv class=\"entry\"\u003e\n  {{#if author}}\n    \u003ch1\u003e{{firstName}} {{lastName}}\u003c/h1\u003e\n  {{/if}}\n\u003c/div\u003e\n```\n\nWhen 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\".\n\n```html\n\u003cdiv class=\"entry\"\u003e\n  {{#if author}}\n    \u003ch1\u003e{{firstName}} {{lastName}}\u003c/h1\u003e\n  {{else}}\n    \u003ch1\u003eUnknown Author\u003c/h1\u003e\n  {{/if}}\n\u003c/div\u003e\n```\n\nYou can chain several blocks. For example that template:\n\n```html\n{{#if isActive}}\n  \u003cimg src=\"star.gif\" alt=\"Active\"\u003e\n{{else if isInactive}}\n  \u003cimg src=\"cry.gif\" alt=\"Inactive\"\u003e\n{{else}}\n  \u003cimg src=\"wat.gif\" alt=\"Unknown\"\u003e\n{{/if}}\n```\n\nWith that context:\n\n```go\nctx := map[string]interface{}{\n    \"isActive\":   false,\n    \"isInactive\": false,\n}\n```\n\nOutputs:\n\n```html\n \u003cimg src=\"wat.gif\" alt=\"Unknown\"\u003e\n```\n\n\n#### The `unless` block helper\n\nYou can use the `unless` helper as the inverse of the `if` helper. Its block will be rendered if the expression returns a falsy value.\n\n```html\n\u003cdiv class=\"entry\"\u003e\n  {{#unless license}}\n  \u003ch3 class=\"warning\"\u003eWARNING: This entry does not have a license!\u003c/h3\u003e\n  {{/unless}}\n\u003c/div\u003e\n```\n\n\n#### The `each` block helper\n\nYou 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.\n\nFor example:\n\n```html\n\u003cul class=\"people\"\u003e\n  {{#each people}}\n    \u003cli\u003e{{this}}\u003c/li\u003e\n  {{/each}}\n\u003c/ul\u003e\n```\n\nWith this context:\n\n```go\nmap[string]interface{}{\n    \"people\": []string{\n        \"Marcel\", \"Jean-Claude\", \"Yvette\",\n    },\n}\n```\n\nOutputs:\n\n```html\n\u003cul class=\"people\"\u003e\n  \u003cli\u003eMarcel\u003c/li\u003e\n  \u003cli\u003eJean-Claude\u003c/li\u003e\n  \u003cli\u003eYvette\u003c/li\u003e\n\u003c/ul\u003e\n```\n\nYou 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).\n\n```html\n{{#each paragraphs}}\n  \u003cp\u003e{{this}}\u003c/p\u003e\n{{else}}\n  \u003cp class=\"empty\"\u003eNo content\u003c/p\u003e\n{{/each}}\n```\n\nWhen looping through items in `each`, you can optionally reference the current loop index via `{{@index}}`.\n\n```html\n{{#each array}}\n  {{@index}}: {{this}}\n{{/each}}\n```\n\nAdditionally for map and struct instance iteration, `{{@key}}` references the current map key or struct field name:\n\n```html\n{{#each map}}\n  {{@key}}: {{this}}\n{{/each}}\n```\n\nThe first and last steps of iteration are noted via the `@first` and `@last` variables.\n\n\n#### The `with` block helper\n\nYou can shift the context for a section of a template by using the built-in `with` block helper.\n\n```html\n\u003cdiv class=\"entry\"\u003e\n  \u003ch1\u003e{{title}}\u003c/h1\u003e\n\n  {{#with author}}\n  \u003ch2\u003eBy {{firstName}} {{lastName}}\u003c/h2\u003e\n  {{/with}}\n\u003c/div\u003e\n```\n\nWith this context:\n\n```go\nmap[string]interface{}{\n    \"title\": \"My first post!\",\n    \"author\": map[string]string{\n        \"firstName\": \"Jean\",\n        \"lastName\":  \"Valjean\",\n    },\n}\n```\n\nOutputs:\n\n```html\n\u003cdiv class=\"entry\"\u003e\n  \u003ch1\u003eMy first post!\u003c/h1\u003e\n\n  \u003ch2\u003eBy Jean Valjean\u003c/h2\u003e\n\u003c/div\u003e\n```\n\nYou can optionally provide an `{{else}}` section which will display only when the passed argument is falsy.\n\n```html\n{{#with author}}\n  \u003cp\u003e{{name}}\u003c/p\u003e\n{{else}}\n  \u003cp class=\"empty\"\u003eNo content\u003c/p\u003e\n{{/with}}\n```\n\n\n#### The `lookup` helper\n\nThe `lookup` helper allows for dynamic parameter resolution using handlebars variables.\n\n```html\n{{#each bar}}\n  {{lookup ../foo @index}}\n{{/each}}\n```\n\n\n#### The `log` helper\n\nThe `log` helper allows for logging while rendering a template.\n\n```html\n{{log \"Look at me!\"}}\n```\n\nNote that the handlebars.js `@level` variable is not supported.\n\n\n#### The `equal` helper\n\nThe `equal` helper renders a block if the string version of both arguments are equals.\n\nFor example that template:\n\n```html\n{{#equal foo \"bar\"}}foo is bar{{/equal}}\n{{#equal foo baz}}foo is the same as baz{{/equal}}\n{{#equal nb 0}}nothing{{/equal}}\n{{#equal nb 1}}there is one{{/equal}}\n{{#equal nb \"1\"}}everything is stringified before comparison{{/equal}}\n```\n\nWith that context:\n\n```go\nctx := map[string]interface{}{\n    \"foo\": \"bar\",\n    \"baz\": \"bar\",\n    \"nb\":  1,\n}\n```\n\nOutputs:\n\n```html\nfoo is bar\nfoo is the same as baz\n\nthere is one\neverything is stringified before comparison\n```\n\n\n### Block Helpers\n\nBlock helpers make it possible to define custom iterators and other functionality that can invoke the passed block with a new context.\n\n\n#### Block Evaluation\n\nAs an example, let's define a block helper that adds some markup to the wrapped text.\n\n```html\n\u003cdiv class=\"entry\"\u003e\n  \u003ch1\u003e{{title}}\u003c/h1\u003e\n  \u003cdiv class=\"body\"\u003e\n    {{#bold}}{{body}}{{/bold}}\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\nThe `bold` helper will add markup to make its text bold.\n\n```go\nraymond.RegisterHelper(\"bold\", func(options *raymond.Options) raymond.SafeString {\n    return raymond.SafeString(`\u003cdiv class=\"mybold\"\u003e` + options.Fn() + \"\u003c/div\u003e\")\n})\n```\n\nA helper evaluates the block content with current context by calling `options.Fn()`.\n\nIf you want to evaluate the block with another context, then use `options.FnWith(ctx)`, like this french version of built-in `with` block helper:\n\n```go\nraymond.RegisterHelper(\"avec\", func(context interface{}, options *raymond.Options) string {\n    return options.FnWith(context)\n})\n```\n\nWith that template:\n\n```html\n{{#avec obj.text}}{{this}}{{/avec}}\n```\n\n\n#### Conditional\n\nLet's write a french version of `if` block helper:\n\n```go\nsource := `{{#si yep}}YEP !{{/si}}`\n\nctx := map[string]interface{}{\"yep\": true}\n\nraymond.RegisterHelper(\"si\", func(conditional bool, options *raymond.Options) string {\n    if conditional {\n        return options.Fn()\n    }\n    return \"\"\n})\n```\n\nNote 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:\n\n```go\nctx := map[string]interface{}{\"yep\": \"message\"}\n```\n\nHere, `\"message\"` is converted to `true` because it is an non-empty string. See `IsTrue()` function for more informations on boolean conversion.\n\n\n#### Else Block Evaluation\n\nWe can enhance the `si` block helper to evaluate the `else block` by calling `options.Inverse()` if conditional is false:\n\n```go\nsource := `{{#si yep}}YEP !{{else}}NOP !{{/si}}`\n\nctx := map[string]interface{}{\"yep\": false}\n\nraymond.RegisterHelper(\"si\", func(conditional bool, options *raymond.Options) string {\n    if conditional {\n        return options.Fn()\n    }\n    return options.Inverse()\n})\n```\n\nOutputs:\n```\nNOP !\n```\n\n\n#### Block Parameters\n\nIt's possible to receive named parameters from supporting helpers.\n\n```html\n{{#each users as |user userId|}}\n  Id: {{userId}} Name: {{user.name}}\n{{/each}}\n```\n\nIn this particular example, `user` will have the same value as the current context and `userId` will have the index/key value for the iteration.\n\nThis allows for nested helpers to avoid name conflicts.\n\nFor example:\n\n```html\n{{#each users as |user userId|}}\n  {{#each user.books as |book bookId|}}\n    User: {{userId}} Book: {{bookId}}\n  {{/each}}\n{{/each}}\n```\n\nWith this context:\n\n```go\nctx := map[string]interface{}{\n    \"users\": map[string]interface{}{\n        \"marcel\": map[string]interface{}{\n            \"books\": map[string]interface{}{\n                \"book1\": \"My first book\",\n                \"book2\": \"My second book\",\n            },\n        },\n        \"didier\": map[string]interface{}{\n            \"books\": map[string]interface{}{\n                \"bookA\": \"Good book\",\n                \"bookB\": \"Bad book\",\n            },\n        },\n    },\n}\n```\n\nOutputs:\n\n```html\n  User: marcel Book: book1\n  User: marcel Book: book2\n  User: didier Book: bookA\n  User: didier Book: bookB\n```\n\nAs you can see, the second block parameter is the map key. When using structs, it is the struct field name.\n\nWhen using arrays and slices, the second parameter is element index:\n\n```go\nctx := map[string]interface{}{\n    \"users\": []map[string]interface{}{\n        {\n            \"id\": \"marcel\",\n            \"books\": []map[string]interface{}{\n                {\"id\": \"book1\", \"title\": \"My first book\"},\n                {\"id\": \"book2\", \"title\": \"My second book\"},\n            },\n        },\n        {\n            \"id\": \"didier\",\n            \"books\": []map[string]interface{}{\n                {\"id\": \"bookA\", \"title\": \"Good book\"},\n                {\"id\": \"bookB\", \"title\": \"Bad book\"},\n            },\n        },\n    },\n}\n```\n\nOutputs:\n\n```html\n    User: 0 Book: 0\n    User: 0 Book: 1\n    User: 1 Book: 0\n    User: 1 Book: 1\n```\n\n\n### Helper Parameters\n\nWhen calling a helper in a template, raymond expects the same number of arguments as the number of helper function parameters.\n\nSo this template:\n\n```html\n{{add a}}\n```\n\nWith this helper:\n\n```go\nraymond.RegisterHelper(\"add\", func(val1, val2 int) string {\n    return strconv.Itoa(val1 + val2)\n})\n```\n\nWill simply panics, because we call the helper with one argument whereas it expects two.\n\n\n#### Automatic conversion\n\nLet's create a `concat` helper that expects two strings and concat them:\n\n```go\nsource := `{{concat a b}}`\n\nctx := map[string]interface{}{\n    \"a\": \"Jean\",\n    \"b\": \"Valjean\",\n}\n\nraymond.RegisterHelper(\"concat\", func(val1, val2 string) string {\n    return val1 + \" \" + val2\n})\n```\n\nEverything goes well, two strings are passed as arguments to the helper that outputs:\n\n```html\nJean VALJEAN\n```\n\nBut what happens if there is another type than `string` in the context ? For example:\n\n```go\nctx := map[string]interface{}{\n    \"a\": 10,\n    \"b\": \"Valjean\",\n}\n```\n\nActually, 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:\n\n```html\n10 VALJEAN\n```\n\nNote that this kind of automatic conversion is done with `bool` type too, thanks to the `IsTrue()` function.\n\n\n### Options Argument\n\nIf a helper needs the `Options` argument, just add it at the end of helper parameters:\n\n```go\nraymond.RegisterHelper(\"add\", func(val1, val2 int, options *raymond.Options) string {\n    return strconv.Itoa(val1 + val2) + \" \" + options.ValueStr(\"bananas\")\n})\n```\n\nThanks to the `options` argument, helpers have access to the current evaluation context, to the `Hash` arguments, and they can manipulate the private data variables.\n\nThe `Options` argument is even necessary for Block Helpers to evaluate block and \"else block\".\n\n\n#### Context Values\n\nHelpers fetch current context values with `options.Value()` and `options.ValuesStr()`.\n\n`Value()` returns an `interface{}` and lets the helper do the type assertions whereas `ValueStr()` automatically converts the value to a `string`.\n\nFor example:\n\n```go\nsource := `{{concat a b}}`\n\nctx := map[string]interface{}{\n    \"a\":      \"Marcel\",\n    \"b\":      \"Beliveau\",\n    \"suffix\": \"FOREVER !\",\n}\n\nraymond.RegisterHelper(\"concat\", func(val1, val2 string, options *raymond.Options) string {\n    return val1 + \" \" + val2 + \" \" + options.ValueStr(\"suffix\")\n})\n```\n\nOutputs:\n\n```html\nMarcel Beliveau FOREVER !\n```\n\nHelpers can get the entire current context with `options.Ctx()` that returns an `interface{}`.\n\n\n#### Helper Hash Arguments\n\nHelpers access hash arguments with `options.HashProp()` and `options.HashStr()`.\n\n`HashProp()` returns an `interface{}` and lets the helper do the type assertions whereas `HashStr()` automatically converts the value to a `string`.\n\nFor example:\n\n```go\nsource := `{{concat suffix first=a second=b}}`\n\nctx := map[string]interface{}{\n    \"a\":      \"Marcel\",\n    \"b\":      \"Beliveau\",\n    \"suffix\": \"FOREVER !\",\n}\n\nraymond.RegisterHelper(\"concat\", func(suffix string, options *raymond.Options) string {\n    return options.HashStr(\"first\") + \" \" + options.HashStr(\"second\") + \" \" + suffix\n})\n```\n\nOutputs:\n\n```html\nMarcel Beliveau FOREVER !\n```\n\nHelpers can get the full hash with `options.Hash()` that returns a `map[string]interface{}`.\n\n\n#### Private Data\n\nHelpers access private data variables with `options.Data()` and `options.DataStr()`.\n\n`Data()` returns an `interface{}` and lets the helper do the type assertions whereas `DataStr()` automatically converts the value to a `string`.\n\nHelpers can get the entire current data frame with `options.DataFrame()` that returns a `*DataFrame`.\n\nFor 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.\n\nFor example:\n\n```go\nsource := `{{#voodoo kind=a}}Voodoo is {{@magix}}{{/voodoo}}`\n\nctx := map[string]interface{}{\n    \"a\": \"awesome\",\n}\n\nraymond.RegisterHelper(\"voodoo\", func(options *raymond.Options) string {\n    // create data frame with @magix data\n    frame := options.NewDataFrame()\n    frame.Set(\"magix\", options.HashProp(\"kind\"))\n\n    // evaluates block with new data frame\n    return options.FnData(frame)\n})\n```\n\nHelpers that need to evaluate the block with a private data frame and a new context can call `options.FnCtxData()`.\n\n\n### Utilites\n\nIn addition to `Escape()`, raymond provides utility functions that can be usefull for helpers.\n\n\n#### `Str()`\n\n`Str()` converts its parameter to a `string`.\n\nBooleans:\n\n```go\nraymond.Str(3) + \" foos and \" + raymond.Str(-1.25) + \" bars\"\n// Outputs: \"3 foos and -1.25 bars\"\n```\n\nNumbers:\n\n``` go\n\"everything is \" + raymond.Str(true) + \" and nothing is \" + raymond.Str(false)\n// Outputs: \"everything is true and nothing is false\"\n```\n\nMaps:\n\n```go\nraymond.Str(map[string]string{\"foo\": \"bar\"})\n// Outputs: \"map[foo:bar]\"\n```\n\nArrays and Slices:\n\n```go\nraymond.Str([]interface{}{true, 10, \"foo\", 5, \"bar\"})\n// Outputs: \"true10foo5bar\"\n```\n\n\n#### `IsTrue()`\n\n`IsTrue()` returns the truthy version of its parameter.\n\nIt returns `false` when parameter is either:\n\n  - an empty array\n  - an empty slice\n  - an empty map\n  - `\"\"`\n  - `nil`\n  - `0`\n  - `false`\n\nFor all others values, `IsTrue()` returns `true`.\n\n\n## Context Functions\n\nIn addition to helpers, lambdas found in context are evaluated.\n\nFor example, that template and context:\n\n```go\nsource := \"I {{feeling}} you\"\n\nctx := map[string]interface{}{\n    \"feeling\": func() string {\n        rand.Seed(time.Now().UTC().UnixNano())\n\n        feelings := []string{\"hate\", \"love\"}\n        return feelings[rand.Intn(len(feelings))]\n    },\n}\n```\n\nRandomly renders `I hate you` or `I love you`.\n\nThose context functions behave like helper functions: they can be called with parameters and they can have an `Options` argument.\n\n\n## Partials\n\n### Template Partials\n\nYou can register template partials before execution:\n\n```go\ntpl := raymond.MustParse(\"{{\u003e foo}} baz\")\ntpl.RegisterPartial(\"foo\", \"\u003cspan\u003ebar\u003c/span\u003e\")\n\nresult := tpl.MustExec(nil)\nfmt.Print(result)\n```\n\nOutput:\n\n```html\n\u003cspan\u003ebar\u003c/span\u003e baz\n```\n\nYou can register several partials at once:\n\n```go\ntpl := raymond.MustParse(\"{{\u003e foo}} and {{\u003e baz}}\")\ntpl.RegisterPartials(map[string]string{\n    \"foo\": \"\u003cspan\u003ebar\u003c/span\u003e\",\n    \"baz\": \"\u003cspan\u003ebat\u003c/span\u003e\",\n})\n\nresult := tpl.MustExec(nil)\nfmt.Print(result)\n```\n\nOutput:\n\n```html\n\u003cspan\u003ebar\u003c/span\u003e and \u003cspan\u003ebat\u003c/span\u003e\n```\n\n\n### Global Partials\n\nYou can registers global partials that will be accessible by all templates:\n\n```go\nraymond.RegisterPartial(\"foo\", \"\u003cspan\u003ebar\u003c/span\u003e\")\n\ntpl := raymond.MustParse(\"{{\u003e foo}} baz\")\nresult := tpl.MustExec(nil)\nfmt.Print(result)\n```\n\nOr:\n\n```go\nraymond.RegisterPartials(map[string]string{\n    \"foo\": \"\u003cspan\u003ebar\u003c/span\u003e\",\n    \"baz\": \"\u003cspan\u003ebat\u003c/span\u003e\",\n})\n\ntpl := raymond.MustParse(\"{{\u003e foo}} and {{\u003e baz}}\")\nresult := tpl.MustExec(nil)\nfmt.Print(result)\n```\n\n\n### Dynamic Partials\n\nIt's possible to dynamically select the partial to be executed by using sub expression syntax.\n\nFor example, that template randomly evaluates the `foo` or `baz` partial:\n\n```go\ntpl := raymond.MustParse(\"{{\u003e (whichPartial) }}\")\ntpl.RegisterPartials(map[string]string{\n    \"foo\": \"\u003cspan\u003ebar\u003c/span\u003e\",\n    \"baz\": \"\u003cspan\u003ebat\u003c/span\u003e\",\n})\n\nctx := map[string]interface{}{\n    \"whichPartial\": func() string {\n        rand.Seed(time.Now().UTC().UnixNano())\n\n        names := []string{\"foo\", \"baz\"}\n        return names[rand.Intn(len(names))]\n    },\n}\n\nresult := tpl.MustExec(ctx)\nfmt.Print(result)\n```\n\n\n### Partial Contexts\n\nIt's possible to execute partials on a custom context by passing in the context to the partial call.\n\nFor example:\n\n```go\ntpl := raymond.MustParse(\"User: {{\u003e userDetails user }}\")\ntpl.RegisterPartial(\"userDetails\", \"{{firstname}} {{lastname}}\")\n\nctx := map[string]interface{}{\n    \"user\": map[string]string{\n        \"firstname\": \"Jean\",\n        \"lastname\":  \"Valjean\",\n    },\n}\n\nresult := tpl.MustExec(ctx)\nfmt.Print(result)\n```\n\nDisplays:\n\n```html\nUser: Jean Valjean\n```\n\n\n### Partial Parameters\n\nCustom data can be passed to partials through hash parameters.\n\nFor example:\n\n```go\ntpl := raymond.MustParse(\"{{\u003e myPartial name=hero }}\")\ntpl.RegisterPartial(\"myPartial\", \"My hero is {{name}}\")\n\nctx := map[string]interface{}{\n    \"hero\": \"Goldorak\",\n}\n\nresult := tpl.MustExec(ctx)\nfmt.Print(result)\n```\n\nDisplays:\n\n```html\nMy hero is Goldorak\n```\n\n\n## Utility Functions\n\nYou can use following utility fuctions to parse and register partials from files:\n\n- `ParseFile()` - reads a file and return parsed template\n- `Template.RegisterPartialFile()` - reads a file and registers its content as a partial with given name\n- `Template.RegisterPartialFiles()` - reads several files and registers them as partials, the filename base is used as the partial name\n\n\n## Mustache\n\nHandlebars is a superset of [mustache](https://mustache.github.io) but it differs on those points:\n\n- Alternative delimiters are not supported\n- There is no recursive lookup\n\n\n## Limitations\n\nThese handlebars options are currently NOT implemented:\n\n- `compat` - enables recursive field lookup\n- `knownHelpers` - list of helpers that are known to exist (truthy) at template execution time\n- `knownHelpersOnly` - allows further optimizations based on the known helpers list\n- `trackIds` - include the id names used to resolve parameters for helpers\n- `noEscape` - disables HTML escaping globally\n- `strict` - templates will throw rather than silently ignore missing fields\n- `assumeObjects` - removes object existence checks when traversing paths\n- `preventIndent` - disables the auto-indententation of nested partials\n- `stringParams` - resolves a parameter to it's name if the value isn't present in the context stack\n\nThese handlebars features are currently NOT implemented:\n\n- raw block content is not passed as a parameter to helper\n- `blockHelperMissing` - helper called when a helper can not be directly resolved\n- `helperMissing` - helper called when a potential helper expression was not found\n- `@contextPath` - value set in `trackIds` mode that records the lookup path for the current context\n- `@level` - log level\n\n\n## Handlebars Lexer\n\nYou should not use the lexer directly, but for your information here is an example:\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n\n    \"github.com/aymerick/raymond/lexer\"\n)\n\nfunc main() {\n    source := \"You know {{nothing}} John Snow\"\n\n    output := \"\"\n\n    lex := lexer.Scan(source)\n    for {\n        // consume next token\n        token := lex.NextToken()\n\n        output += fmt.Sprintf(\" %s\", token)\n\n        // stops when all tokens have been consumed, or on error\n        if token.Kind == lexer.TokenEOF || token.Kind == lexer.TokenError {\n            break\n        }\n    }\n\n    fmt.Print(output)\n}\n```\n\nOutputs:\n\n```\nContent{\"You know \"} Open{\"{{\"} ID{\"nothing\"} Close{\"}}\"} Content{\" John Snow\"} EOF\n```\n\n\n## Handlebars Parser\n\nYou should not use the parser directly, but for your information here is an example:\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n\n    \"github.com/aymerick/raymond/ast\"\n    \"github.com/aymerick/raymond/parser\"\n)\n\nfu  nc main() {\n    source := \"You know {{nothing}} John Snow\"\n\n    // parse template\n    program, err := parser.Parse(source)\n    if err != nil {\n        panic(err)\n    }\n\n    // print AST\n    output := ast.Print(program)\n\n    fmt.Print(output)\n}\n```\n\nOutputs:\n\n```\nCONTENT[ 'You know ' ]\n{{ PATH:nothing [] }}\nCONTENT[ ' John Snow' ]\n```\n\n\n## Test\n\nFirst, fetch mustache tests:\n\n    $ git submodule update --init\n\nTo run all tests:\n\n    $ go test ./...\n\nTo filter tests:\n\n    $ go test -run=\"Partials\"\n\nTo run all test and all benchmarks:\n\n    $ go test -bench . ./...\n\nTo test with race detection:\n\n    $ go test -race ./...\n\n\n## References\n\n  - \u003chttp://handlebarsjs.com/\u003e\n  - \u003chttps://mustache.github.io/mustache.5.html\u003e\n  - \u003chttps://github.com/golang/go/tree/master/src/text/template\u003e\n  - \u003chttps://www.youtube.com/watch?v=HxaD_trXwRE\u003e\n\n\n## Others Implementations\n\n- [handlebars.js](http://handlebarsjs.com) - javascript\n- [handlebars.java](https://github.com/jknack/handlebars.java) - java\n- [handlebars.rb](https://github.com/cowboyd/handlebars.rb) - ruby\n- [handlebars.php](https://github.com/XaminProject/handlebars.php) - php\n- [handlebars-objc](https://github.com/Bertrand/handlebars-objc) - Objective C\n- [rumblebars](https://github.com/nicolas-cherel/rumblebars) - rust\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faymerick%2Fraymond","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faymerick%2Fraymond","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faymerick%2Fraymond/lists"}