{"id":18073429,"url":"https://github.com/blakewilliams/bat","last_synced_at":"2026-03-07T19:33:07.213Z","repository":{"id":50681017,"uuid":"519818669","full_name":"BlakeWilliams/bat","owner":"BlakeWilliams","description":"Intuitive templating engine for Go","archived":false,"fork":false,"pushed_at":"2024-01-02T15:34:45.000Z","size":108,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-03T19:52:52.997Z","etag":null,"topics":["go","golang","template","template-engine","templates"],"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/BlakeWilliams.png","metadata":{"files":{"readme":"README.md","changelog":null,"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}},"created_at":"2022-07-31T15:47:14.000Z","updated_at":"2024-10-28T17:43:35.000Z","dependencies_parsed_at":"2023-12-25T22:39:03.481Z","dependency_job_id":null,"html_url":"https://github.com/BlakeWilliams/bat","commit_stats":{"total_commits":72,"total_committers":1,"mean_commits":72.0,"dds":0.0,"last_synced_commit":"5e87587f3af8bb6f9870fa0f0f0d0d06ee04c48d"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BlakeWilliams%2Fbat","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BlakeWilliams%2Fbat/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BlakeWilliams%2Fbat/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BlakeWilliams%2Fbat/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/BlakeWilliams","download_url":"https://codeload.github.com/BlakeWilliams/bat/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248520657,"owners_count":21117905,"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","golang","template","template-engine","templates"],"created_at":"2024-10-31T10:06:54.090Z","updated_at":"2026-03-07T19:33:07.154Z","avatar_url":"https://github.com/BlakeWilliams.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Bat\n\nA mustache like (`{{foo.bar}}`) templating engine for Go. This is still very\nmuch WIP, but contributions and issues are welcome.\n\n## Usage\n\nGiven a file, `index.batml`:\n\n```\n\u003ch1\u003eHello {{Team.Name}}\u003c/h1\u003e\n```\n\nCreate a new template and execute it:\n\n```go\ncontent, _ := ioutil.ReadFile(\"index.bat\")\nbat.NewTemplate(content)\n\nt := team{\n    Name: \"Foo\",\n}\nbat.Execute(map[string]any{\"Team\": team})\n```\n\n### Engine\n\nBat provides an engine that allows you to register templates and provides\ndefault, as well as user provided helper functions to those templates.\n\n```go\nengine := bat.NewEngine(bat.HTMLEscape)\nengine.Register(\"index.bat\", \"\u003ch1\u003eHello {{Team.Name}}\u003c/h1\u003e\")\n```\n\nor, you can use `AutoRegister` to automatically register all templates in a\ndirectory. This is useful with the Go embed package:\n\n```go\n//go:embed templates\nvar templates embed.FS\n\nengine := bat.NewEngine(bat.HTMLEscape)\nengine.AutoRegister(templates, \".html\")\n\nengine.Render(\"templates/users/signup.html\", map[string]any{\"Team\": team})\n```\n\n#### Built-in helpers\n\n- `safe` - marks a value as safe to be rendered. This is useful for rendering\n  HTML. For example, `{{safe(\"\u003ch1\u003eFoo\u003c/h1\u003e\")}}` will render `\u003ch1\u003eFoo\u003c/h1\u003e`.\n- `len` - returns the length of a slice or map. For example, `{{len(Users)}}` will\n  return the length of the `Users` slice.\n- `partial` - renders a partial template. For example, `{{partial(\"header\", {foo: \"bar\"})}}`\n  will render the `header` template with the provided map as locals.\n- `layout` - Wraps the current template with the provided layout. For example,\n  `{{ layout(\"layouts/application\") }}` will render the current template wrapped with template registered as \"layouts/application\". All data available to the current template will be available to the layout.\n\nHere's an overview of more advanced usage:\n\n### Primitives\n\nBat supports the following primitives that can be used within `{{}}`\nexpressions:\n\n- booleans - `true` and `false`\n- nil - `nil`\n- strings - `\"string value\"` and `\"string with \\\"escaped\\\" values\"`\n- integers - `1000` and `-1000`\n- maps - `{ foo: 1, bar: \"two\" }`\n\n### Data Access\n\nTemplates accept data in the form of `map[string]any`. The strings must be\nvalid identifiers in order to be access, which start with an alphabetical\ncharacter following by any number of alphanumerical characters.\n\nThe template `{{userName}}` would attempt to access the `userName` key from the\nprovided data map.\n\ne.g.\n\n```go\nt := bat.NewTemplate(`{{userName}}!`)\nout := new(bytes.Buffer)\n\n// outputs \"gogopher!\"\nt.Execute(out, map[string]{\"Username\": \"gogopher\"}\n```\n\nChaining and method calls are also supported:\n\n```go\ntype Name struct {\n    First string\n    Last string\n}\n\ntype User struct {\n    Name Name\n}\n\nfunc (n Name) Initials() string {\n    return n.First[0:1] + n.Last[0:1]\n}\n\nt := bat.NewTemplate(`{{user.Name.Initials()}}!`)\nout := new(bytes.Buffer)\n\nuser := User{\n    Name: Name{\n        First: \"Fox\",\n        Last: \"Mulder\",\n    }\n}\n\n// outputs \"FM!\"\nt.Execute(out, map[string]{\"user\": user}\n```\n\nFinally, map/slice/array access is supported via `[]`:\n\n```html\n\u003ch1\u003e{{user[0].Name.First}}\u003c/h1\u003e\n```\n\n### Conditionals\n\nBat supports `if` statements, and the `!=` and `==` operators.\n\n```html\n{{if user != nil}}\n\u003ca href=\"/login\"\u003eLogin\u003c/a\u003e\n{{else}}\n\u003ca href=\"/profile\"\u003eView your profile\u003c/a\u003e\n{{end}}\n```\n\n### Not\n\nThe `!` operator can be used to negate an expression and return a boolean\n\n```html\n{{!true}}\n```\n\nThe above will render `false`.\n\n### Iterators\n\nIteration is supported via the `range` keyword. Supported types are slices, maps, arrays, and channels.\n\n```html\n{{range $index, $name in data}}\n\u003ch1\u003eHello {{$name}}, number {{$index}}\u003c/h1\u003e\n{{end}}\n```\n\nGiven `data` being defined as: `[]string{\"Fox Mulder\", \"Dana Scully\"}`, the resulting output would look like:\n\n```html\n\u003ch1\u003eHello Fox Mulder, number 0\u003c/h1\u003e\n\n\u003ch1\u003eHello Dana Scully, number 1\u003c/h1\u003e\n```\n\nIn the example above, range defines two variables which **must** begin with a $\nso they don't conflict with `data` passed into the template.\n\nThe `range` keyword can also be used with a single variable, providing only the\nkey or index to the iterator:\n\n```html\n{{range $index in data}}\n\u003ch1\u003eHello person {{$index}}\u003c/h1\u003e\n{{end}}\n```\n\nGiven `data` being defined as: `[]string{\"Fox Mulder\", \"Dana Scully\"}`, the resulting output would look like:\n\n```html\n\u003ch1\u003eHello person 0\u003c/h1\u003e\n\n\u003ch1\u003eHello person 1\u003c/h1\u003e\n```\n\nIf a map is passed to `range`, it will attempt to sort it before iteration if\nthe key is able to be compared and is implemented in the `internal/mapsort`\npackage.\n\n### Helper functions\n\nHelper functions can be provided directly to templates using the `WithHelpers` function when instantiating a template.\n\ne.g.\n\n```\nhelloHelper := func(name string) string {\n    return fmt.Sprintf(\"Hello %s!\", name)\n}\n\nt := bat.NewTemplate(`{{hello \"there\"}}`, WithHelpers(map[string]any{\"hello\": helloHelper}))\n\n// output \"Hello there!\"\nout := new(bytes.Buffer)\nt.Execute(out, map[string]any{})\n```\n\n### Escaping\n\nTemplates can be provided a custom escape function with the signature\n`func(string) string` that will be called on the resulting output from `{{}}`\nblocks.\n\nThere are two escape functions that can be utilized, `NoEscape` which does no\nescaping, and `HTMLEscape` which delegates to `html.EscapeString`, which\nescapes HTML.\n\nThe default escape function is `HTMLEscape` for safety reasons.\n\ne.g.\n\n```go\n// This template will escape HTML from the output of `{{}}` blocks\nt := NewTemplate(\"{{foo}}\", WithEscapeFunc(HTMLEscape))\n```\n\nEscaping can be avoided by returning the `bat.Safe` type from the result of a\n`{{}}` block.\n\ne.g.\n\n```go\nt := bat.NewTemplate(`{{output}}`, WithEscapeFunc(HTMLEscape))\n\n// output \"Hello there!\"\nout := new(bytes.Buffer)\n\n// outputs \u0026lt;h1\u0026gt;Hello!\u0026lt;/h1^gt;\nt.Execute(out, map[string]any{\"output\": \"\u003ch1\u003eHello!\u003c/h1\u003e\"})\n\n// outputs \u003ch1\u003eHello!\u003c/h1\u003e\nt.Execute(out, map[string]any{\"output\": bat.Safe(\"\u003ch1\u003eHello!\u003c/h1\u003e\")})\n```\n\n### Math\n\nBasic math is supported, with some caveats. When performing math operations,\nthe left most type is converted into the right most type, when possible:\n\n```go\n// int32 - int64\n   100   -   200 // returns int64\n```\n\nThe following operations are supported:\n\n- `-` Subtraction\n- `+` Addition\n- `*` Multiplication\n- `/` Division\n- `%` Modulus\n\nMore comprehensive casting logic would be welcome in the form of a PR.\n\n### Comments\n\nComments are supported as complete statements or at the end of a statement.\n\n```html\n{{ // This is a comment }}\n```\n\n```html\n{{ foo // This is also a comment }}\n```\n\n## TODO\n\n- [x] Add `each` functionality (see the section on `range`)\n- [x] Add `if` and `else` functionality\n- [x] Emit better error messages and validate them with tests (template execution)\n- [ ] Emit better error messages from lexer and parser\n- [x] Create an engine struct that will enable partials, helper functions, and\n      custom escaping functions.\n- [x] Add escaping support to templates\n- [x] Support strings in templates\n- [x] Support integer numbers\n- [x] Add basic math operations\n- [x] Simple map class `{ \"foo\": bar }` for use with partials\n- [ ] Improve stringify logic in the executor (`bat.go`)\n- [x] Support channels in `range`\n- [ ] Trim whitespace by default, add control characters to avoid trimming.\n- [x] Support method calls\n- [x] Support helpers\n- [x] Support map/slice array access `[]`\n- [ ] Validate helper methods have 0 or 1 return values\n\n## Maybe\n\n- Add \u0026\u0026, and || operators for more complex conditionals\n- ~Replace `{{end}}` with named end blocks, like `{{/if}}`~ rejected\n- Add support for `{{else if \u003cexpression\u003e}}`\n- ~Support the not operator, e.g. `if !foo`~ done\n- Track and error on undefined variable usage in the parsing stage\n\n## Don't\n\n- Add parens for complex options\n- Variable declarations that look like provided data access (use $ for template locals, plain identifiers for everything else)\n- ~Add string concatenation~\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fblakewilliams%2Fbat","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fblakewilliams%2Fbat","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fblakewilliams%2Fbat/lists"}