Racket implementation of Mustache templates
Racket HTML
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
examples
rastache
.gitignore
.gitmodules
LICENSE
README.org

README.org

Rastache

Racket implementation of Mustache templates.

Rastache is compliant with the Mustache spec v1.1+λ

Get It

Rastache can be installed as a collection within Racket with package manager:

$ raco pkg install "git://github.com/rcherrueau/rastache.git?path=rastache"

Or manually:

$ git clone git://github.com/rcherrueau/rastache.git
$ cd rastache/rastache
$ raco link .
$ raco setup

Usage

Once installed, here is the simplest way to use Rastache:

#lang racket/base

(require rastache)

; rast-compile/render: input-port hash -> output-port
(rast-compile/render (open-input-string "{{foo}}")
                     #hash{ (foo . "bar") }
                     (current-output-port))

The function rast-compile/render takes three parameters, the template as an input port, the context as a hash table and an output port in which Rastache displays the rendered template.

The function rast-compile/render is the composition of two auxiliary functions, rast-compile and rast-render. Using those two separately enables the compilation of a template and its memoization for later uses.

#lang racket/base

(require rastache)

(let* ([template (open-input-file "huge-template.mustache")]
       [compiled-template (rast-compile template)])
  (define ctx1 #hash{#|context 1|#})
  (define ctx2 #hash{#|context 2|#})
  (define ctx3 #hash{#|context 3|#})

  (for ([ctx (list ctx1 ctx2 ctx3)])
    (rast-render compiled-template ctx (current-output-port))
    (newline)))

Rastache also offers facilities for reading strings and files.

; rast-compile/open-string: string -> (listof token)
(rast-compile/open-string "{{foo}}")

; rast-compile/open-file: path -> (listof token)
(rast-compile/open-file "path/to/file.mustache")

See examples directory for more usage examples.

Running Tests

At the moment the project is under development but passes all Mustache spec tests. If you want to run the tests yourself, do the following:

$ git clone git://github.com/rcherrueau/rastache.git
$ cd rastache/rastache/tests/
$ raco test tests.rkt

Supported Functionality

Variables

The most basic tag type is the variable. A {{name}} tag in a basic template will try to find the name key in the current context.

All variables are HTML escaped by default. If you want to return unescaped HTML, use the triple mustache {{{name}}} or &.

Template:

* {{name}}
* {{age}}
* {{company}}
* {{{company}}}
* {{& company}}

Context:

#hash{ (name . "Chris")
       (company . "<b>GitHub</b>") }

Output:

* Chris
*
* &lt;b&gt;GitHub&lt;/b&gt;
* <b>GitHub</b>
* <b>GitHub</b>

Dot notation

Dot notation may be used to access nested keys.

Template:

* {{type}}
* {{delorean.name}}: {{delorean.speed}}

Context:

#hash{ (type . "Time Machine")
       (delorean . #hash{ (name . "DeLorean")
                          (speed . "88 mph") }) }

Output:

* Time Machine
* DeLorean: 88 mph

Lambdas

If the value of a key is a lambda, it is called with the current context as its first argument. The second argument is a rendering function that uses the current context as its context argument.

Template:

Hello, {{lambda}}!

Context:

`#hash{ (planet . "world")
        (lambda . ,(λ (_ render) (render "{{planet}}"))) }

Output:

Hello, world!

Sections

Sections render blocks of text one or more times, depending on the value of the key in the current context.

A section begins with a pound and ends with a slash. That is, {{#person}} begins a “person” section while {{/person}} ends it.

The behavior of the section is determined by the value of the key.

False Values of Empty Lists

Template:

Shown.
{{#person}}
  Never shown!
{{/person}}

Context:

#hash{ (person . #f) }

Output:

Shown.

Non-Empty Lists

If the person key exists and has a non-false value, the HTML between the pound and slash will be rendered and displayed one or more times.

When the value is a non-empty list, the text in the block will be displayed once for each item in the list. The context of the block will be set to the current item for each iteration. In this way we can loop over collections.

Template:

Death List Five:
{{#death}}
<b>{{name}}</b>
{{/death}}

Context:

#hash{ (death . [#hash{ (name . "O-Ren Ishii") }
                 #hash{ (name . "Vernita Green") }
                 #hash{ (name . "Budd") }
                 #hash{ (name . "Elle Driver") }
                 #hash{ (name . "Bill") }]) }

Output:

Death List Five:
<b>O-Ren Ishii</b>
<b>Vernita Green</b>
<b>Budd</b>
<b>Elle Driver</b>
<b>Bill</b>

When looping over an array of strings, a . can be used to refer to the current item in the list.

Template:

{{#tmnt}}
* {{.}}
{{/tmnt}}

Context:

#hash{ (tmnt . ["Leonardo"
                "Michelangelo"
                "Donatello"
                "Raphael"]) }

Output:

* Leonardo
* Michelangelo
* Donatello
* Raphael

Lambdas

If the value of a section key is a lambda, it is called with the section’s literal block of text, un-rendered, as its first argument. The second argument is a special rendering function that uses the current context as its context argument.

Template:

<{{#lambda}}-{{/lambda}}>

Context:

`#hash{ (planet . "Earth")
        (lambda . ,(λ (text render)
                     (render (string-append text
                                            "{{planet}}"
                                            text)))) }

Output:

<-Earth->

Inverted Sections

An inverted section begins with a caret (hat) and ends with a slash. That is {{^person}} begins a “person” inverted section while {{/person}} ends it.

Template:

{{#repo}}
<b>{{name}}</b>
{{/repo}}
{{^repo}}
No repos :{
{{/repo}}

Context:

#hash{ (repo . []) }

Output:

No repos :{

Comments

Comments begin with a bang and are ignored. The following template:

<h1>Today{{! ignore me }}.</h1>

Will render as follows:

<h1>Today.</h1>

Comments may contain newlines.

Partials

Partials allow you to include other templates. It begins with a greater than sign, like {{> partialkey}}.

The partialkey can be a simple string, thus Rastache interprets partialkey as a file path.

Template:

Hello{{>partials/names}}

Context:

#hash{ (people . [ #hash{ (name . "Marty") }
                   #hash{ (name . "Emmet") }
                   #hash{ (name . "Einstein") } ]) }

Partial file `partials/names’:

{{#people}}, {{name}}{{/people}}

Output:

Hello, Marty, Emmet, Einstein

The partialkey can also be an URIs, as specified in RFC 2396. Thus Rastache uses `get-pure-port’ with redirection parameter set to 1 to get the resource.

Template:

Hello{{>https://github.com/rcherrueau/rastache/raw/master/rastache/tests/partials/names}}

Context:

#hash{ (people . [ #hash{ (name . "Marty") }
                   #hash{ (name . "Emmet") }
                   #hash{ (name . "Einstein") } ]) }

Output:

Hello, Marty, Emmet, Einstein

Set Delimiter

Set Delimiter tags start with an equal sign and change the tag delimiters from {{ and }} to custom strings.

Consider the following contrived example:

* {{default_tags}}
{{=<% %>=}}
* <% erb_style_tags %>
<%={{ }}=%>
* {{ default_tags_again }}

Here we have a list with three items. The first item uses the default tag style, the second uses erb style as defined by the Set Delimiter tag, and the third returns to the default style after yet another Set Delimiter declaration.

Why?

I’ve given myself a project as I learn Racket. I’m particularly interested in Racket facilities for defining expander and reader and for packaging those two into a conveniently named language. For all these reasons, mustache implementation seems a good project.

License

Copyright (C) 2014 Ronan-Alexandre Cherrueau

This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA