Skip to content

README

whisker is inspired by Mustache but is more stable, robust and predictable. It is a fast template engine for V with a simple syntax.

Features

  1. Logic-less: Different but expressive and powerful. 2. Four Data Types: Booleans, Strings, Lists, and Maps. 3. Composable: Supports nested iteration and partial recursion. 4. Simple Data Model: Template data be constructed in V source code or imported and exported using JSON. 5. Partials: External, partial templates can be plugged into the primary template. 6. Safe by Default: Tag contents are HTML escaped by default. 7. Customisable: The delimiters can be changed from the default {{...}}.

Motivation

The following blog posts provide more context:

  1. Announcing whisker - easier way to do templates in V: We take a look at current template engines available in V and announce a new template engine. 2. Writing whisker’s tokeniser using the Theory of Computation: We show how we use fundamental CS principles to implement an FSM-based tokeniser for whisker.

Prerequisites

You must have V installed. Refer to the official instructions for help with installation.

If you already have V installed, use v up to update the toolchain and standard library.

Installation

From VPM

v install hungrybluedev.whisker

This should install the package as the hungrybluedev.whisker module.

To use it, use import hungrybluedev.whisker and proceed as normal.

From GitHub

Run the following to install whisker from GitHub using V's package manager:

v install --git https://github.com/hungrybluedev/whisker

This should install in hungrybluedev.whisker first and then relocate itto whisker. Now, in your project, you can import whisker and use whiskerright away!

Usage

The main struct is whisker.template.Template which can be generated eitherdirectly from template strings or be loaded from disk from template files. Asingle template should be reused for different data models to produce outputswhich differ in content but not semantic structure.

NoteThere might be slight white-space consistencies between the generatedand expected results. For machine-verification, it is recommended to comparethe parsed and reconstructed outputs for your particular file format.

Direct String Templates

  1. Load a template:Use template.from_strings(input: input_str, partials: partial_map)to generate a template from direct string inputs. Here, input_str isa string and partial_map is a map[string]string. The map's keys are thenames of the template that are replaced by the direct template strings. Leavethe partials field empty if there are none required.
  2. Run with Data Model: Use run(data) to generate the output string. Thedata can be represented in V source code directly (refer to the spec forexamples), or it can be loaded from JSON (usingdatamodel.from_json(data_string)).

This is a copy-paste-able example to get started immediately:

module main

// Imports if you install from GitHub:
import whisker.datamodel
import whisker.template

// Imports if you install from VPM:
import hungrybluedev.whisker.template
import hungrybluedev.whisker.datamodel

fn main() {
simple_template := template.from_strings(input: 'Hello, {{name}}!')!
data := datamodel.from_json('{"name": "World"}')!

println(simple_template.run(data)!) // prints "Hello, World!"
}

Template Files

  1. Load a template:Use template.load_file(input: input_str, partials: partial_map) togenerate a template from file names. The difference here is that instead ofproviding content, you provide the relative file paths. The names of thepartials need to be exact though, so keep an eye on that.
  2. Run with Data Model: Same as before. You canuse os.read_file(path_to_json) to read the JSON contents and then plug thisinto the datamodel.from_json function.

It is not necessary, but it is recommended to use filenames thatcontain *.wskr.* somewhere in the file name.Check json_test.v and html_test.v forexamples with template files.

The CLI

whisker may also be used as a standalone command-line program to processtemplate files. It does not support direct template string input for the sake ofsimplicity.

Build whisker with v cmd/whisker and run cmd/whisker/whisker --help forusage instructions. You can specify a bin subdirectory as output folder andadd it to path as well:

# Create an output directory
mkdir cmd/bin

# Build the executable
v cmd/whisker -o cmd/bin/whisker

# Run the executable
cmd/bin/whisker --help

Check whisker_cli_test.v for a concretedemonstration.

Syntax

Normal Text Is Unaffected

Input

Sample text

Output

Sample text

Double Curly Braces Indicate Sections

Input

Hello, {{name}}!

Data

{
"name": "world"
}

Output

Hello, world!

Changing Delimiters

Input

{{=[ ]=}}
module main

fn main() {
println('[greeting]')
}

Data

{
"greeting": "Have a nice day!"
}

Output

module main

fn main() {
println('Have a nice day!')
}

Booleans, Positive, and Negative Sections

Input

<nav>
<ul>
<li>Home</li>
<li>About</li>
{{-logged_in}}
<li>Log In</li>
{{/logged_in}} {{+logged_in}}
<li>Account: {{user.name}}</li>
{{/logged_in}}
</ul>
</nav>

Data 1

{
"logged_in": false
}

Output 1

<nav>
<ul>
<li>Home</li>
<li>About</li>
<li>Log In</li>
</ul>
</nav>

Data 2

{
"logged_in": true,
"user": {
"name": "whisker"
}
}

Output 2

<nav>
<ul>
<li>Home</li>
<li>About</li>

<li>Account: whisker</li>
</ul>
</nav>

Positive and negative sections also apply to lists and maps. An empty list or map means a negative section and a non-empty one represents a positive section.

List:

Input

{{+vacation}}
<h1>Currently on vacation</h1>
<ul>
{{*.}}
<li>{{.}}</li>
{{/.}}
</ul>
{{/vacation}} {{-vacation}}
<p>Nobody is on vacation currently</p>
{{/vacation}}

Data 1

{
"vacation": []
}

Output 1

<p>Nobody is on vacation currently</p>

Data 2

{
"vacation": ["Homer", "Marge"]
}

Output 2

<h1>Currently on vacation</h1>
<ul>
<li>Homer</li>
<li>Marge</li>
</ul>

Map:

Input


{{+user}}
<p>Welcome {{last_name}}, {{first_name}}</h1>
{{/user}}
{{-user}}
<p>Create account?</p>
{{/user}}

Data 1

{
"user": {}
}

Output 1

<p>Create account?</p>

Data 2

{
"user": {
"last_name": "Simpson",
"first_name": "Homer"
}
}

Output 2

<p>Welcome Simpson, Homer</h1>

Map Iteration:

Input

<ul>
{{*user}}
<li>{{key}}: {{value}}</li>
{{/user}}
</ul>

Data

{
"user": {
"First Name": "Homer",
"Last Name": "Simpson"
}
}

Output

<ul>
<li>First Name: Homer</li>
<li>Last Name: Simpson</li>
</ul>

Maps, Lists, and Partials

Input

<ol>
{{*items}} {{>item}} {{/items}}
</ol>

Partial: item

<li>{{name}}: {{description}}</li>

Data

{
"items": [
{
"name": "Banana",
"description": "Rich in potassium and naturally sweet."
},
{
"name": "Orange",
"description": "High in Vitamin C and very refreshing."
}
]
}

Output

<ol>
<li>Banana: Rich in potassium and naturally sweet.</li>
<li>Orange: High in Vitamin C and very refreshing.</li>
</ol>

All the examples shown here are tested in CI inthe readme_test.v file.

For the full specification, refer to the unit tests and test cases inthe spec directory.

License

This project is distributed under the MIT License.

Acknowledgements

Thanks to the original Mustache project for inspiration and the specification.