Pogonos is yet another Clojure(Script) implementation of the Mustache templating language.
- Compliant to the Mustache spec v1.3.0 including lambdas and dynamic partials
- Fast but clean implementation
- User-friendly error messages for parsing errors
- Handy API for use from the CLI
- Supports Clojure, ClojureScript, self-hosted ClojureScript and Babashka
Add the following to your project's dependencies:
In this section, you'll see how to use Pogonos, but if you're not so familiar with the Mustache language itself, you might want to read its documentation first.
The easiest way to use the library is to just call render-string
:
(require '[pogonos.core :as pg])
(pg/render-string "Hello, {{name}}!" {:name "Rich"})
;=> "Hello, Rich!"
render-string
takes two arguments; a string that represents a Mustache template,
and a map of the values passed to the template.
The keys of the map must be keywords.
Then, the function will render the template and return the resulting string. If you'd rather write out the rendering result to somewhere, instead of generating a string, you can use outputs to specify where to output the result. See Outputs for details.
render-string
has two look-alike cousins named render-file
and render-resource
.
The only difference between render-string
and those functions is that render-string
directly takes a template string as an argument whereas render-file
and
render-resource
load a template stored in a text file on the file system
or a resource file placed somewhere on the classpath.
Let's say you have a template file located at resources/sample.mustache
whose content looks like the following:
$ cat resources/sample.mustache
Hello, {{name}}!
Then, you can render the template using render-file
:
;; loads a template from a text file on the file system
(pg/render-file "resources/sample.mustache" {:name "Rich"})
Or if you have the template file on your classpath, you can also render it
with render-resource
(Here we assume the resources
directory is
included in the classpath):
;; loads a template from a resource file on the classpath
(pg/render-resource "sample.mustache" {:name "Rich"})
All the render functions mentioned above are more suitable for one-shot rendering. But if you want to render the same template with different contexts over and over again, it would be more efficient to prepare a parsed template prior to rendering.
To prepare a parsed template, use parse-string
(or parse-file
/ parse-resource
accordingly):
(def template (pg/parse-string "Hello, {{name}}!"))
template
;=> #pogonos.nodes.Root{:body ["Hello, " #pogonos.nodes.Variable{:keys (:name), :unescaped? false} "!"]}
And then, you can render the parsed template using the render
function:
(pg/render template {:name "Rich"})
;=> "Hello, Rich!"
(pg/render template {:name "Alex"})
;=> "Hello, Alex!"
At the time, Pogonos does NOT have an internal mechanism to implicitly cache parsed results of previously rendered templates for better rendering performance. So, if you want to use Pogonos in situations where the rendering performance matters, you may have to cache parsed templates yourself.
Since 0.2.0
, Pogonos also provides another set of functions: check-string
, check-file
and check-resource
.
These functions try to parse the input template and check if the template
contains any Mustache syntax error. If any, they will report it as an exception.
Otherwise, they will return nil
silently:
(pg/check-string "Hello, {{name")
;; Execution error (ExceptionInfo) at pogonos.error/error (error.cljc:52).
;; Missing closing delimiter "}}" (1:14):
;;
;; 1| Hello, {{name
;; ^^
(pg/check-string "Hello, {{name}}!")
;=> nil
The verbosity of error messages can be controlled by an option. See Error messages for details.
What the check-*
functions do is semantically equivalent to "parsing a template
and discarding the parsed result". However, the check-*
functions are generally
more efficient than parse-*
in this regard because the former functions do not
actually build a syntax tree.
An output is the way to specify where to output the rendering result.
By default, Pogonos's render functions output the result as a string.
You can emulate this behavior by specifying (pogonos.output/to-string)
as the output like the following:
(require '[pogonos.output :as output])
(pg/render-string "Hello, {{name}}!" {:name "Clojure"}
{:output (output/to-string)})
;=> "Hello, Clojure!"
You can also write out the rendering result to a file or to stdout via output:
;; writes the rendering result to a file
(pg/render-string "Hello, {{name}}!" {:name "Clojure"}
{:output (output/to-file "hello.txt")})
;; writes the rendering result to the stdout
(pg/render-string "Hello, {{name}}!" {:name "Clojure"}
{:output (output/to-stdout)})
In general, it's more efficient to write out the rendering result directly to a file than to generate a resulting string and then write it out to the file.
The Mustache spec provides a feature named partials. Partials can be used to include contents from other templates.
Let's say you have a partial resources/user.mustache
that looks like:
$ cat resources/user.mustache
<strong>{{name}}</strong>
You can render a template that has a partial in it using the render functions out of the box:
(pg/render-string "<h2>Users</h2>{{#users}}{{>user}}{{/users}}"
{:users [{:name "Rich"} {:name "Alex"}]})
;=> "<h2>Users</h2><strong>Rich</strong><strong>Alex</strong>"
By default, render-string
and render-resource
try to find
partials on the classpath, and render-file
on the file system.
To specify where to find partials explicitly, use the :partials
option:
(require '[pogonos.partials :as partials])
(pg/render-string "<h2>Users</h2>{{#users}}{{>user}}{{/users}}"
{:users [{:name "Rich"} {:name "Alex"}]}
{:partials (partials/file-partials "resources")})
(pg/render-string "<h2>Users</h2>{{#users}}{{>user}}{{/users}}"
{:users [{:name "Rich"} {:name "Alex"}]}
{:partials (partials/resource-partials)})
You can even specify a map to utilize inline partials:
(pg/render-string "<h2>Users</h2>{{#users}}{{>user}}{{/users}}"
{:users [{:name "Rich"} {:name "Alex"}]}
{:partials {:user "<strong>{{name}}</strong>"}}
To disable partials, specify nil
for :partials
:
(pg/render-string "<h2>Users</h2>{{>partial}}" {} {:partials nil})
;=> "<h2>Users</h2>"
Pogonos aims to provide user-friendly error messages for parse errors as one of its features, so that users can easily find where and why the error occurred.
For example, if you miss the closing delimiter of a Mustache tag, you'll see a detailed error message, like the following:
(pg/render-string "Hello, {{name" {:name "Clojure"})
;; Execution error (ExceptionInfo) at pogonos.error/error (error.cljc:52).
;; Missing closing delimiter "}}" (1:14):
;;
;; 1| Hello, {{name
;; ^^
You can suppress these somewhat "verbose" error messages if you want,
by specifying the option {:suppress-verbose-errors true}
:
(pg/render-string "Hello, {{name" {:name "Clojure"}
{:suppress-verbose-errors true})
;; Execution error (ExceptionInfo) at pogonos.error/error (error.cljc:52).
;; Missing closing delimiter "}}" (1:14)
Even while disabling verbose error messages, you can get them back by calling
perr
explicitly:
(pg/perr *e)
;; Missing closing delimiter "}}" (1:14):
;;
;; 1| Hello, {{name
;; ^^
Pogonos 0.2.0+ provides a new API for calling functions via the -X
/-T
option of the Clojure CLI.
To use it as a -X
program, add settings like the following to your deps.edn
:
{:aliases
{...
template {:extra-deps {pogonos/pogonos {:mvn/version "<version>"}}
:ns-default pogonos.api}
...}}
To use it as a -T
tool, install Pogonos with the following command:
clojure -Ttools install io.github.athos/pogonos '{:git/tag <version>}' :as template
Then, you can call the API from the CLI like:
# as -X program
$ clojure -X:template <function name> ...
# as -T tool
$ clojure -Ttemplate <function name> ...
The functions available from the CLI are as follows:
You will see the usage of each function in the next sections.
The render
function renders the specified Mustache template.
The example below renders a template file named hello.mustache
with the data {:name "Clojurian"}
:
$ cat hello.mustache
Hello, {{name}}!
$ clojure -X:template render :file '"hello.mustache"' :data '{:name "Clojurian"}'
Hello, Clojurian!
$
The :file
option specifies the path to the template file to be rendered. The :data
option specifies a map of values passed to the template.
If no template is specified, Pogonos will try to read the template from stdin:
$ echo 'Hello, {{name}}!' | clojure -X:template render :data '{:name "Clojurian"}'
Hello, Clojurian!
$
The following table shows the available options for render
:
Option | Description |
---|---|
:string |
Renders the given template string |
:file |
Renders the specified template file |
:resource |
Renders the specified template resource on the classpath |
:output |
The path to the output file. If not specified, the rendering result will be emitted to stdout by default. |
:data |
A map of values passed to the template |
:data-file |
If specified, reads an EDN map from the file specified by that path and pass it to the template |
The check
function performs a syntax check on a given Mustache template, reporting any syntax errors that the Mustache template contains.
The example below checks a template file named broken.mustache
that contains a syntax error:
$ cat broken.mustache
This is a broken {{template
$ clojure -X:template check :file '"broken.mustache"'
Checking template broken.mustache
[ERROR] Missing closing delimiter "}}" (broken.mustache:1:28):
1| This is a broken {{template
^^
$
If no template is specified, Pogonos will try to read the template to be checked from stdin:
$ echo '{{#foo}}' | clojure -X:template check
[ERROR] Missing section-end tag {{/foo}} (1:9):
1| {{#foo}}
^^
$
The following table shows the available options for check
:
Option | Description |
---|---|
:string |
Checks the given template string |
:file |
Checks the specified template file |
:dir |
Checks the template files in the specified directory |
:resource |
Checks the specified template resource on the classpath |
:include-regex |
Includes only the templates that match the given pattern |
:exclude-regex |
Excludes the templates that match the given pattern |
:only-show-errors |
Hides progress messages |
:suppress-verbose-errors |
Suppresses verbose error messages |
Note that the :file
, :dir
and :resource
options allow multiple items to be specified separated by the file path separator (:
(colon) on Linux/macOS and ;
(semicolon) on Windows).
For example, the following command will check three template files named foo.mustache
, bar.mustache
and baz.mustache
(Here, we assume that the file path separator is :
):
$ clojure -X:template check :file '"foo.mustache:bar.mustache:baz.mustache"'
Pogonos 0.2.1+ supports Babashka. It's supported in the following three ways:
- All the public function provided in
pogonos.core
can be used on Babashka, just like on the JVM - The API for the CLI usage can also be used on Babashka
- Pogonos provides a standalone command that can be installed with
bbin
and works just like the CLI tool
The following sections will focus on 3.
Pogonos provides a standalone command named pgns
. To install the pgns
command, run the following:
bbin install io.github.athos/pogonos
The pgns
command can be used exactly like the CLI tool:
$ echo 'Hello, {{name}}!' | pgns render :data '{:name "Clojurian"}'
Hello, Clojurian!
$
Backed by babashka.cli, the pgns
command also provides more UNIXy options:
$ echo 'Hello, {{name}}!' | pgns render --data '{:name "Clojurian"}'
Hello, Clojurian!
$
Run pgns help
for more detailed usage.
Copyright © 2020 Shogo Ohta
This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0.
This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version, with the GNU Classpath Exception which is available at https://www.gnu.org/software/classpath/license.html.