Current section

Files

Jump to
pomade README.md
Raw

README.md

# pomade

_Add a little Gleam to your Mustache_

[![Package Version](https://img.shields.io/hexpm/v/pomade)](https://hex.pm/packages/pomade)
[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://pomade.hexdocs.pm/)

## What

`pomade` is a [Mustache](https://mustache.github.io) library written in Gleam. I
made it primarily because I couldn't find a pure Gleam implementation of the
Mustache templating language, but also because I wanted to make something fun
and learn Gleam in the process.

### Compatibility

Version 1.0 fully supports the required portions of the Mustache specification:

- Variables
- Raw variables
- Custom delimiters
- Sections
- Inverted sections
- Partials

`pomade` passes the full [suite of tests](https://github.com/mustache/spec) for
these mandatory features.

The optional, as-of-yet unsupported aspects are:

- Dynamic names for partials
- Inheritance (blocks and parents)
- Lambdas and functions


## How

### Installing it

```sh
gleam add pomade@1
```
### Using it

First, import the required libraries:

```gleam
import gleam/dict
import gleam/string_tree.{type StringTree}

import pomade
```

In addition to the rendering and compilation API, `pomade` provides support for
heterogeneous hashmaps and values, which Mustache requires as input.

#### Rendering templates

The most basic use case is rendering a template with a single call:

```gleam
pub fn render_template() -> Result(StringTree, pomade.Error) {
  let template = "Hello, {{target}}!"
  let data = pomade.dict(dict.from_list([#("target", pomade.string("world"))]))
  pomade.compile_and_render(template, data, dict.new())
  // -> Ok(StringTree)
  // -> "Hello, world!"
}
```

By default, the API returns `StringTree`s, but there's a version that returns
`String`s:

```gleam
pub fn render_string() -> Result(String, pomade.Error) {
  let template = "Goodbye, {{target}}"
  let data = pomade.dict(dict.from_list([#("target", pomade.string("horses"))]))
  pomade.compile_and_render_string(template, data, dict.new())
  // -> Ok("Goodbye, horses") 
}
```

#### Using partials

You can also render templates with partials, by passing a `Dict(String, String)`
in the partials argument, mapping a partial name to the source of that partial:

```gleam
pub fn render_with_partials() -> Result(String, pomade.Error) {
  let template = "Fly, you {{>other_template}}!"
  let data =
    pomade.dict(dict.from_list([#("adjective", pomade.string("fools"))]))
  let partials = dict.from_list([#("other_template", "{{adjective}}")])
  pomade.compile_and_render_string(template, data, partials)
  // -> Ok("Fly, you fools!")
}
```

`pomade` takes care of caching the compiled partial, in a way that's compliant
with the Mustache spec, to save on the costs of compiling the template at
runtime every time it's needed.

#### Pre-compilation

If you anticipate that you'll be rendering a template often during the lifespan
of your program (as in, for example, a web application), you can pre-compile
templates to save some time:

```gleam
pub fn precompile() -> Result(String, pomade.Error) {
  let template_source = "No. I am your {{relative}}."
  let assert Ok(template) = pomade.compile(template_source)
  render_compiled(template)
}
```

You can then, of course, simply expand the pre-compiled template:

```gleam
pub fn render_compiled(
  template: pomade.Template,
) -> Result(String, pomade.Error) {
  let data =
    pomade.dict(dict.from_list([#("relative", pomade.string("father"))]))
  pomade.render_string(template, data, dict.new())
  // -> Ok("No. I am your father.")
  //
  // Or:
  // pomade.render(template, data, dict.new())
  // -> Ok(StringTree)
}
```

## Information for nerds

### Working on it

First, ensure that you've fetched the git submodules locally, so that the unit
tests can find the Mustache specification test suite:

```sh
git submodule update --init
```

Then, you can run:

```sh
gleam test
```

Or whatever.

### Benchmarking it

`pomade` comes with a few Glychee benchmarks, shamelessly lifted (ported) from
the repository of the Ruby version of Mustache. You can find them under
`dev/pomade`, and you can run them like so:

```sh
gleam run -m "pomade/compile_template_benchmark"
```

#### Results vs `bbmustache` (via `chaplin`)

With the following hardware and configuration:

```
Operating System: macOS
CPU Information: Apple M1
Number of Available Cores: 8
Available memory: 8 GB
Elixir 1.20.4
Erlang 29.1.1
JIT enabled: true

Benchmark suite executing with the following configuration:
warmup: 4 s
time: 4 s
memory time: 8 s
reduction time: 4 s
parallel: 1
inputs: none specified
Estimated total run time: 20 s
Excluding outliers: false
```

We have the following benchmarks:

##### Compile template benchmark

```
Name                                ips        average  deviation         median         99th %
pomade: compile template        82.27 K       12.15 μs    ±27.74%       11.54 μs       16.13 μs
chaplin: compile template       40.84 K       24.48 μs    ±12.77%       23.88 μs       32.54 μs

Comparison:
pomade: compile template        82.27 K
chaplin: compile template       40.84 K - 2.01x slower +12.33 μs

Memory usage statistics:

Name                         Memory usage
pomade: compile template         27.85 KB
chaplin: compile template         5.68 KB - 0.20x memory usage -22.17188 KB

**All measurements for memory usage were the same**

Reduction count statistics:

Name                      Reduction count
pomade: compile template           1.52 K
chaplin: compile template          0.40 K - 0.26x reduction count -1.12800 K

**All measurements for reduction count were the same**
```

##### Render template without HTML escaping

```
Name                           ips        average  deviation         median         99th %
pomade: html escape        83.71 K       11.95 μs    ±27.42%       10.92 μs       23.71 μs
chaplin: html escape       46.16 K       21.66 μs    ±13.53%       21.17 μs       30.08 μs

Comparison:
pomade: html escape        83.71 K
chaplin: html escape       46.16 K - 1.81x slower +9.72 μs

Memory usage statistics:

Name                    Memory usage
pomade: html escape         23.79 KB
chaplin: html escape         7.23 KB - 0.30x memory usage -16.56250 KB

**All measurements for memory usage were the same**

Reduction count statistics:

Name                 Reduction count
pomade: html escape           1.36 K
chaplin: html escape          0.65 K - 0.48x reduction count -0.71000 K

**All measurements for reduction count were the same**
```

##### Render template with HTML escaping

```
Name                           ips        average  deviation         median         99th %
pomade: html escape        82.00 K       12.19 μs    ±30.01%       11.50 μs       17.29 μs
chaplin: html escape       43.12 K       23.19 μs    ±14.29%       22.54 μs       42.17 μs

Comparison:
pomade: html escape        82.00 K
chaplin: html escape       43.12 K - 1.90x slower +11.00 μs

Memory usage statistics:

Name                    Memory usage
pomade: html escape         23.79 KB
chaplin: html escape         8.45 KB - 0.36x memory usage -15.34375 KB

**All measurements for memory usage were the same**

Reduction count statistics:

Name                 Reduction count
pomade: html escape           1.43 K
chaplin: html escape          0.90 K - 0.63x reduction count -0.53400 K

**All measurements for reduction count were the same**
```

##### Render pre-compiled template with a collection with 1000 items

```
Name                                 ips        average  deviation         median         99th %
pomade: render collection         1.73 K        0.58 ms    ±11.12%        0.56 ms        0.76 ms
chaplin: render collection        0.35 K        2.88 ms     ±4.14%        2.90 ms        3.09 ms

Comparison:
pomade: render collection         1.73 K
chaplin: render collection        0.35 K - 4.97x slower +2.30 ms

Memory usage statistics:

Name                          Memory usage
pomade: render collection          0.78 MB
chaplin: render collection         2.98 MB - 3.83x memory usage +2.20 MB

**All measurements for memory usage were the same**

Reduction count statistics:

Name                       Reduction count
pomade: render collection         129.29 K
chaplin: render collection        376.78 K - 2.91x reduction count +247.49 K

**All measurements for reduction count were the same**
```

##### Render pre-compiled template with a collection with 1000 partials

```
Name                      ips        average  deviation         median         99th %
render partials        3.91 K      256.07 μs    ±17.10%      244.83 μs      435.27 μs

Memory usage statistics:

Name               Memory usage
render partials       521.98 KB

**All measurements for memory usage were the same**

Reduction count statistics:

Name            Reduction count
render partials         66.84 K

**All measurements for reduction count were the same**
```

## License

`pomade` is distributed under the MIT License. You can find more details in
`LICENSE`.

## Important

You won't find any AI slop here.