Code Monkey home page Code Monkey logo

whisker's Introduction

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 it to whisker. Now, in your project, you can import whisker and use whisker right away!

Usage

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

Note There might be slight white-space consistencies between the generated and expected results. For machine-verification, it is recommended to compare the 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 is a string and partial_map is a map[string]string. The map's keys are the names of the template that are replaced by the direct template strings. Leave the partials field empty if there are none required.
  2. Run with Data Model: Use run(data) to generate the output string. The data can be represented in V source code directly (refer to the spec for examples), or it can be loaded from JSON (using datamodel.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) to generate a template from file names. The difference here is that instead of providing content, you provide the relative file paths. The names of the partials need to be exact though, so keep an eye on that.
  2. Run with Data Model: Same as before. You can use os.read_file(path_to_json) to read the JSON contents and then plug this into the datamodel.from_json function.

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

The CLI

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

Build whisker with v cmd/whisker and run cmd/whisker/whisker --help for usage instructions. You can specify a bin subdirectory as output folder and add 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 concrete demonstration.

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 in the readme_test.v file.

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

License

This project is distributed under the MIT License.

Acknowledgements

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

whisker's People

Contributors

gechandesu avatar hungrybluedev avatar slavaschmidt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

whisker's Issues

Fix recursive templates

Describe the bug

Recursive templates that contain lists are currently broken. This is a side-effect of broken nested-list handling as documented in #1.

Expected Behavior

The commented out test case in the partial spec should work.

Current Behavior

It causes invalid memory access errors.

Reproduction Steps

Run the test case shown above.

Possible Solution

It is possible that fixing #1 should fix this as well.

Additional Information/Context

No response

V version

Latest

Environment details (OS name and version, etc.)

All environments.

Please add description how to iterate over a map into the Readme

Describe the feature

Please add description how to iterate over a map into the Readme

Use Case

Currently, the Readme describes iteration over items of an array and using a map with hard-coded key values.
It would be great to have an example of how to iterate over a map using dynamic key/value pairs.

Proposed Solution

No response

Other Information

No response

Acknowledgements

  • I may be able to implement this feature request
  • This feature might incur a breaking change

Version used

0.2.0

Environment details (OS name and version, etc.)

any

Automated package installation test in CI

Describe the feature

The project is currently tested on a separate machine to ensure that the package can be installed and used properly. It needs to be included in the CI so that third-party users of this library should not face any inconveniences.

Use Case

This will ensure that the package is always installable for users.

Proposed Solution

Add a sample project and a separate workflow file that runs this.

Other Information

No response

Acknowledgements

  • I may be able to implement this feature request
  • This feature might incur a breaking change

Version used

Latest

Environment details (OS name and version, etc.)

All environments.

Add documentation for public API functions

Describe the feature

Currently, the source code has very few comments. Documentation comments should be added at the very least so that module documentation can be generated.

Use Case

For user reference.

Proposed Solution

Add documentation comments to public module functions.

Other Information

No response

Acknowledgements

  • I may be able to implement this feature request
  • This feature might incur a breaking change

Version used

0.1.x

Environment details (OS name and version, etc.)

All environments.

Behaviour in regard of missing variables is not documented

Describe the feature

The template can't be rendered if the variable it refers to is missing in the data. This is different from mustache, where "If the top context is reached and the name key is still not found, nothing will be rendered."

This behavior is confusing and deserves mention in the documentation.

Ideally, it should suggest how to make conditional rendering possible.

Use Case

variable is missing in the data but mentioned in the template

Proposed Solution

No response

Other Information

No response

Acknowledgements

  • I may be able to implement this feature request
  • This feature might incur a breaking change

Version used

0.2.0

Environment details (OS name and version, etc.)

any

Nested lists do not work (yet)

Describe the bug

Right now, the list section duplication code is incomplete. It works for flat lists but fails for nested lists.

Expected Behavior

Expecting nested lists to work. An example is the commented out part here.

Current Behavior

Invalid memory access.

Reproduction Steps

Just run the code that is shown in the highlighted section and is currently commented out in the spec.

Possible Solution

The copying algorithm needs to reattach jump points when the list section is being copied.

Additional Information/Context

No response

V version

Latest

Environment details (OS name and version, etc.)

All environments.

Boolean emptiness property for arrays

Describe the feature

Please provide a way to conditionally render parts of the template where the condition is emptiness / non-emptiness of the list.

Use Case

When rendering lists as HTML, more often than not, it is needed to render parts of the layout only if there are some elements in the list, for example:

  • Table header and/or footer are only to be shown when there are rows in the table
  • Heading has to be visible only if there are paragraphs
  • Ordered and unordered lists might have an introduction and/or conclusion that only need to be shown if there are elements in the list

Proposed Solution

There are two possible ways to implement this functionality:

  • provide a synthetic property depending on the list emptiness, for example, when reading json. This gives full flexibility how the new key should be named
  • On the tokenizer level by extending the meaning of positive and negative sections for lists:
<+list>
<h1>Currenty on vacation</h1>
<ul>
<*list><li>{{.}}</li></list>
</ul>
</list>
<-list>Nobody is no vacation now</list>

The second way feels more elegant and will work for manually constructed data models as well.

Other Information

No response

Acknowledgements

  • I may be able to implement this feature request
  • This feature might incur a breaking change

Version used

0.2.0

Environment details (OS name and version, etc.)

any

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.