Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/blakewilliams/bat
Intuitive templating engine for Go
https://github.com/blakewilliams/bat
go golang template template-engine templates
Last synced: 2 months ago
JSON representation
Intuitive templating engine for Go
- Host: GitHub
- URL: https://github.com/blakewilliams/bat
- Owner: BlakeWilliams
- License: mit
- Created: 2022-07-31T15:47:14.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2024-01-02T15:34:45.000Z (about 1 year ago)
- Last Synced: 2024-04-15T22:30:20.467Z (9 months ago)
- Topics: go, golang, template, template-engine, templates
- Language: Go
- Homepage:
- Size: 105 KB
- Stars: 6
- Watchers: 3
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Bat
A mustache like (`{{foo.bar}}`) templating engine for Go. This is still very
much WIP, but contributions and issues are welcome.## Usage
Given a file, `index.batml`:
```
Hello {{Team.Name}}
```Create a new template and execute it:
```go
content, _ := ioutil.ReadFile("index.bat")
bat.NewTemplate(content)t := team{
Name: "Foo",
}
bat.Execute(map[string]any{"Team": team})
```### Engine
Bat provides an engine that allows you to register templates and provides
default, as well as user provided helper functions to those templates.```go
engine := bat.NewEngine(bat.HTMLEscape)
engine.Register("index.bat", "Hello {{Team.Name}}
")
```or, you can use `AutoRegister` to automatically register all templates in a
directory. This is useful with the Go embed package:```go
//go:embed templates
var templates embed.FSengine := bat.NewEngine(bat.HTMLEscape)
engine.AutoRegister(templates, ".html")engine.Render("templates/users/signup.html", map[string]any{"Team": team})
```#### Built-in helpers
- `safe` - marks a value as safe to be rendered. This is useful for rendering
HTML. For example, `{{safe("Foo
")}}` will render `Foo
`.
- `len` - returns the length of a slice or map. For example, `{{len(Users)}}` will
return the length of the `Users` slice.
- `partial` - renders a partial template. For example, `{{partial("header", {foo: "bar"})}}`
will render the `header` template with the provided map as locals.
- `layout` - Wraps the current template with the provided layout. For example,
`{{ 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.Here's an overview of more advanced usage:
### Primitives
Bat supports the following primitives that can be used within `{{}}`
expressions:- booleans - `true` and `false`
- nil - `nil`
- strings - `"string value"` and `"string with \"escaped\" values"`
- integers - `1000` and `-1000`
- maps - `{ foo: 1, bar: "two" }`### Data Access
Templates accept data in the form of `map[string]any`. The strings must be
valid identifiers in order to be access, which start with an alphabetical
character following by any number of alphanumerical characters.The template `{{userName}}` would attempt to access the `userName` key from the
provided data map.e.g.
```go
t := bat.NewTemplate(`{{userName}}!`)
out := new(bytes.Buffer)// outputs "gogopher!"
t.Execute(out, map[string]{"Username": "gogopher"}
```Chaining and method calls are also supported:
```go
type Name struct {
First string
Last string
}type User struct {
Name Name
}func (n Name) Initials() string {
return n.First[0:1] + n.Last[0:1]
}t := bat.NewTemplate(`{{user.Name.Initials()}}!`)
out := new(bytes.Buffer)user := User{
Name: Name{
First: "Fox",
Last: "Mulder",
}
}// outputs "FM!"
t.Execute(out, map[string]{"user": user}
```Finally, map/slice/array access is supported via `[]`:
```html
{{user[0].Name.First}}
```### Conditionals
Bat supports `if` statements, and the `!=` and `==` operators.
```html
{{if user != nil}}
Login
{{else}}
View your profile
{{end}}
```### Not
The `!` operator can be used to negate an expression and return a boolean
```html
{{!true}}
```The above will render `false`.
### Iterators
Iteration is supported via the `range` keyword. Supported types are slices, maps, arrays, and channels.
```html
{{range $index, $name in data}}Hello {{$name}}, number {{$index}}
{{end}}
```Given `data` being defined as: `[]string{"Fox Mulder", "Dana Scully"}`, the resulting output would look like:
```html
Hello Fox Mulder, number 0
Hello Dana Scully, number 1
```In the example above, range defines two variables which **must** begin with a $
so they don't conflict with `data` passed into the template.The `range` keyword can also be used with a single variable, providing only the
key or index to the iterator:```html
{{range $index in data}}Hello person {{$index}}
{{end}}
```Given `data` being defined as: `[]string{"Fox Mulder", "Dana Scully"}`, the resulting output would look like:
```html
Hello person 0
Hello person 1
```If a map is passed to `range`, it will attempt to sort it before iteration if
the key is able to be compared and is implemented in the `internal/mapsort`
package.### Helper functions
Helper functions can be provided directly to templates using the `WithHelpers` function when instantiating a template.
e.g.
```
helloHelper := func(name string) string {
return fmt.Sprintf("Hello %s!", name)
}t := bat.NewTemplate(`{{hello "there"}}`, WithHelpers(map[string]any{"hello": helloHelper}))
// output "Hello there!"
out := new(bytes.Buffer)
t.Execute(out, map[string]any{})
```### Escaping
Templates can be provided a custom escape function with the signature
`func(string) string` that will be called on the resulting output from `{{}}`
blocks.There are two escape functions that can be utilized, `NoEscape` which does no
escaping, and `HTMLEscape` which delegates to `html.EscapeString`, which
escapes HTML.The default escape function is `HTMLEscape` for safety reasons.
e.g.
```go
// This template will escape HTML from the output of `{{}}` blocks
t := NewTemplate("{{foo}}", WithEscapeFunc(HTMLEscape))
```Escaping can be avoided by returning the `bat.Safe` type from the result of a
`{{}}` block.e.g.
```go
t := bat.NewTemplate(`{{output}}`, WithEscapeFunc(HTMLEscape))// output "Hello there!"
out := new(bytes.Buffer)// outputs <h1>Hello!</h1^gt;
t.Execute(out, map[string]any{"output": "Hello!
"})// outputs
Hello!
t.Execute(out, map[string]any{"output": bat.Safe("Hello!
")})
```### Math
Basic math is supported, with some caveats. When performing math operations,
the left most type is converted into the right most type, when possible:```go
// int32 - int64
100 - 200 // returns int64
```The following operations are supported:
- `-` Subtraction
- `+` Addition
- `*` Multiplication
- `/` Division
- `%` ModulusMore comprehensive casting logic would be welcome in the form of a PR.
### Comments
Comments are supported as complete statements or at the end of a statement.
```html
{{ // This is a comment }}
``````html
{{ foo // This is also a comment }}
```## TODO
- [x] Add `each` functionality (see the section on `range`)
- [x] Add `if` and `else` functionality
- [x] Emit better error messages and validate them with tests (template execution)
- [ ] Emit better error messages from lexer and parser
- [x] Create an engine struct that will enable partials, helper functions, and
custom escaping functions.
- [x] Add escaping support to templates
- [x] Support strings in templates
- [x] Support integer numbers
- [x] Add basic math operations
- [x] Simple map class `{ "foo": bar }` for use with partials
- [ ] Improve stringify logic in the executor (`bat.go`)
- [x] Support channels in `range`
- [ ] Trim whitespace by default, add control characters to avoid trimming.
- [x] Support method calls
- [x] Support helpers
- [x] Support map/slice array access `[]`
- [ ] Validate helper methods have 0 or 1 return values## Maybe
- Add &&, and || operators for more complex conditionals
- ~Replace `{{end}}` with named end blocks, like `{{/if}}`~ rejected
- Add support for `{{else if }}`
- ~Support the not operator, e.g. `if !foo`~ done
- Track and error on undefined variable usage in the parsing stage## Don't
- Add parens for complex options
- Variable declarations that look like provided data access (use $ for template locals, plain identifiers for everything else)
- ~Add string concatenation~