Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/yogthos/selmer

A fast, Django inspired template system in Clojure.
https://github.com/yogthos/selmer

clojure html html-template template-engine

Last synced: about 6 hours ago
JSON representation

A fast, Django inspired template system in Clojure.

Awesome Lists containing this project

README

        

Selmer
======

![build status](https://github.com/yogthos/Selmer/actions/workflows/main.yml/badge.svg)

A fast, [Django](https://docs.djangoproject.com/en/dev/ref/templates/builtins/) inspired template system in pure Clojure.

- [Installation](#installation)
- [Marginalia documentation](#marginalia-documentation)
- [Usage](#usage)
- [Error Handling](#error-handling)
- [Variables and Tags](#variables-and-tags)
- [Filters](#filters)
- [Tags](#tags)
- [Template Inheritance](#template-inheritance)
- [Missing values](#missing-values)
- [Internationalization](#internationalization)
- [License](#license)

## Installation

#### Leiningen

[![Clojars Project](http://clojars.org/selmer/latest-version.svg)](http://clojars.org/selmer)

#### tools.deps

```edn
{selmer {:mvn/version ""}}
```

## Marginalia documentation

[Marginalia documentation](https://rawgithub.com/yogthos/Selmer/master/docs/uberdoc.html)

## Usage

### [Jump to Filters](#filters)

#### Built-in Filters

[abbreviate](#abbreviate)
[add](#add)
[addslashes](#addslashes)
[block.super](#blocksuper)
[capitalize](#capitalize)
[center](#center)
[count](#count)
[count-is](#count-is)
[currency-format](#currency-format)
[date](#date)
[default](#default)
[default-if-empty](#default-if-empty)
[double-format](#double-format)
[email](#email)
[empty?](#empty)
[not-empty](#not-empty)
[first](#first)
[take](#take)
[drop](#drop)
[drop-last](#drop-last)
[get-digit](#get-digit)
[hash](#hash)
[join](#join)
[json](#json)
[last](#last)
[length](#length)
[length-is](#length-is)
[linebreaks](#linebreaks)
[linebreaks-br](#linebreaks-br)
[linenumbers](#linenumbers)
[lower](#lower)
[name](#name)
[phone](#phone)
[pluralize](#pluralize)
[range](#range)
[rand-nth](#rand-nth)
[remove](#remove)
[remove-tags](#remove-tags)
[safe](#safe)
[sort](#sort)
[sort-by](#sort-by)
[sort-by-reversed](#sort-by-reversed)
[sort-reversed](#sort-reversed)
[subs](#subs)
[sum](#sum)
[str](#str)
[title](#title)
[upper](#upper)
[urlescape](#urlescape)
[multiply](#multiply)
[divide](#divide)
[round](#round)
[between?](#between)
[replace](#replace)

### [Jump to Tags](#tags)

#### Built-in Tags

[block](#block)
[comment](#comment)
[cycle](#cycle)
[debug](#debug)
[if](#if)
[ifequal](#ifequal)
[ifunequal](#ifunequal)
[include](#include)
[extends](#extends)
[firstof](#firstof)
[for](#for)
[now](#now)
[safe](#safe-tag)
[script](#script)
[style](#style)
[verbatim](#verbatim)
[with](#with)

### [Jump to Template Inheritance](#template-inheritance)

### Templates

Selmer templates consist of plain text that contains embedded expression and filter tags. While Selmer
is primarily meant for HTML generation, it can be used for templating any text.

Selmer compiles the template files and replaces any tags with the corresponding functions for handling
dynamic content. The compiled template can then be rendered given a context map.

For example, if we wanted to render a string containing a name variable we could write the following:

```clojure
(use 'selmer.parser)

(render "Hello {{name}}!" {:name "Yogthos"})
=>"Hello Yogthos!"
```

alternatively, it's possible to use string interpolation macro to inject symbols found in the environment directly into the template:

```clojure
(let [a 1
b "hello"]
(<< "{{b|upper}}, {{a}} + {{a}} = 2"))

;;=> "HELLO, 1 + 1 = 2"
```

To render a file we can call `render-file` instead:

```clojure
(use 'selmer.parser)

(render-file "home.html" {:name "Yogthos"})
```

To list the declared variables in the template:

```clojure
(known-variables "{{name}}")
=>#{:name}
```

#### \*\*Important\*\*

When rendering files Selmer will cache the compiled template. A recompile will be triggered if the last
modified timestamp of the file changes. Note that changes in files referenced by the template **will not**
trigger a recompile. This means that if your template extends or includes other templates you must touch
the file that's being rendered for changes to take effect.

Alternatively you can turn caching on and off using `(selmer.parser/cache-on!)` and
`(selmer.parser/cache-off!)` respectively.

### Resource Path

By default the templates are located relative to the `ClassLoader` URL. If you'd like to set a custom location for the
templates, you can use `selmer.parser/set-resource-path!` to do that:

```clojure
(selmer.parser/set-resource-path! "/var/html/templates/")
```

It's also possible to set the root template path in a location relative to the resource path of the application:

```clojure
(set-resource-path! (clojure.java.io/resource "META-INF/foo/templates"))
```

This allows the templates to be refrerenced using `include` and `extends` tags without having to specify the full path.

To reset the resource path back to the default simply pass it a `nil`:

```clojure
(selmer.parser/set-resource-path! nil)
```

The application will then look for templates at this location. This can be useful if you're deploying the application
as a jar and would like to be able to modify the HTML without having to redeploy it.

### Custom Markers

By default, Selmer uses `{%` and `%}` to indicate the start and the end of an expression, while using `{{` and `}}` for variables.
This might conflict with clientside frameworks such as AngularJS. In this case you can specify custom tags by passing
a map containing any of the following keys to the parser:

```clojure
:tag-open
:tag-close
:filter-open
:filter-close
:tag-second
```

```clojure
(render "[% for ele in foo %]{{[{ele}]}}[%endfor%]"
{:foo [1 2 3]}
{:tag-open \[
:tag-close \]})
=>"{{1}}{{2}}{{3}}"
```

### Namespaced Keys

Note that if you're using namespaced keys, such as `:foo.bar/baz`, then you will need to escape the `.` as follows:

```clojure
(parser/render "{{foo..bar/baz}}" {:foo.bar/baz "hello"})
```

## Error Handling

Selmer will attempt to validate your templates by default, if you wish to disable validation for any reason it can be done by
calling `(selmer.validator/validate-off!)`.

Whenever an error is detected by the validator an instance of `clojure.lang.ExceptionInfo` will be thrown.
The exception will contain the following keys:

* `:type` - `:selmer-validation-error`
* `:error` - the error message
* `:error-template` - the error page template
* `:template` - template file that contains the error
* `:validation-errors` - a vector of validation errors

Each error in the `:validation-errors` vector is a map containing the details specific to the error:

* `:line` - the line on which the error occurred
* `:tag` - the tag that contains the error

The template under the `:error-template` key can be used to render a friendly error page.
Selmer provides a middleware wrapper for this purpose:

```clojure
(ns myapp.handler
(:require [selmer.middleware :refer [wrap-error-page]]
[environ.core :refer [env]]))

...

#(if (env :dev) (wrap-error-page %) %)
```

The middleware will render a page like the one below whenever any parsing errors are encountered.

![](https://raw.github.com/yogthos/Selmer/master/error_page.png)

## Variables and Tags

Variables are used to inject dynamic content into the text of the template. The values for the variables
are looked up in the context map as can be seen in the example above. When a value is missing then an
empty string is rendered in its place.

By default variables are defined using the double curly braces: `{{myvar}}`.

A variables can also be nested data structures, eg:

`(render "{{person.name}}" {:person {:name "John Doe"}})`

`(render "{{foo.bar.0.baz}}" {:foo {:bar [{:baz "hi"}]}})`

It works with string keys too. For optimal performance, prefer maps with keyword keys. Occasional
string keys are ok, but heavily nested context maps with all string key lookups are slower to render.

`(render "{{foo.bar.baz}}" {:foo {:bar {"baz" "hi"}}})`

Tags are used to add various functionality to the template such as looping and conditions.
For example, if we wanted to create a list from a collection of items we could use the `for` tag
as follows:

```xml


    {% for item in items %}
  • {{item}}

  • {% endfor %}

```

## Filters

In many cases you may wish to postprocess the value of a variable. For example, you might want to convert
it to upper case, pluralize it, or parse it as a date. This can be done by specifying a filter following the
name of the variable. The filters are separated using the `|` character.

For example, if we wanted to convert the variable to upper case we could write `{{user-name|upper}}`. When
rendered with `{:user-name "Yogthos"}` it would produce `YOGTHOS` as its output.

Some filters can take parameters. `{{domain|hash:"md5"}}` rendered with `{:domain "example.org"}` would produce
`1bdf72e04d6b50c82a48c7e4dd38cc69`. If a parameter begins with `@` it will be looked up in the context map and,
if found, will be replaced with its value before
being passed to the filter function. For example, `@foo.bar` will treated as `(get-in context-map [:foo :bar] "@foo.bar")`.

Finally, you can easily register custom filters in addition to those already provided. A filter is simply a function
that accepts a value and returns its replacement:

```clojure
(use 'selmer.filters)

(add-filter! :embiginate clojure.string/upper-case)
(render "{{shout|embiginate}}" {:shout "hello"})
=>"HELLO"

(add-filter! :empty? empty?)
(render "{{files|empty?}}" {:files []})
=>"true"
```

by default the content of the filter will be escaped, if you'd like to make a safe filter then wrap it's body
in a vector with a `:safe` keyword:

```clojure
(add-filter! :foo (fn [x] [:safe (.toUpperCase x)]))

(render "{{x|foo}}" {:x "

I'm safe
"})
=>"
I'M SAFE
"
```

It is possible to disable escaping (if, for example, your target format is not HTML/XML) using the `selmer.util/without-escaping` macro:

```clojure
(require '[selmer.util :refer [without-escaping]])

(without-escaping
(render "{{x}}" {:x "I <3 NY"}))
=>"I <3 NY"
```

Alternatively, you can turn off escaping permanently in all threads with the `selmer.util/turn-off-escaping!` function.

### Built-in Filters

#### abbreviate

Abbreviate the input string to given width if it exceeds a maxium
width. If only a maximum width is given, abbreviated and maximum width
are the same. The first parameter is the maximum width, the optional
second parameter the abbreviated width.

`(render "{{f|abbreviate:19}}" {:f "an abbreviate example text"}) => "an abbreviate ex..."`

`(render "{{f|abbreviate:19:12}}" {:f "an abbreviate example text"}) => "an abbrev..."`

`(render "{{f|abbreviate:26:12}}" {:f "an abbreviate example text"}) => "an abbreviate example text"`

The last example shows: if the string fits in the maximum width the
full string is used even if the abbreviated form would be shorter.

By default `...` is used as replacement for the abbreviated part of
the string. You can easily change that with the `abbr-ellipsis` filter:

`(render "{{f|abbr-ellipsis:\"… etc. pp.\"|abbreviate:19}}" {:f "an abbreviate example text"}) => "an abbrev… etc. pp."`

`(render "{{f|abbr-ellipsis:\"\"|abbreviate:19}}" {:f "an abbreviate example text"}) => "an abbreviate examp"`

Note that the ellipsis can't be longer than the abbreviated width.

With the `abbr-left`, `abbr-right` and `abbr-middle` filters you can
also control in which position the abbreviation happens. Filter
`abbr-right` is provided for completeness, even though it's the
default.

`(render "{{f|abbr-left|abbreviate:19:12}}" {:f "an abbreviate example text"}) => "...mple text"`

`(render "{{f|abbr-middle|abbreviate:19}}" {:f "an abbreviate example text"}) => "an abbre...ple text"`

You also can combine the position and ellipsis filter:

`(render "{{f|abbr-ellipsis:\" <-- snip --> \"|abbr-middle|abbreviate:19}}" {:f "an abbreviate example text"}) => "an <-- snip --> ext"`

Please note that the `abbr-left`, `abbr-right`, `abbr-middle` and
`abbr-ellipsis` filters can only be used just before an `abbreviate`
filter!

#### add
Can add Integers and Doubles. If one of the parameters cannot be casted into one of the two, all parameters will be concatenated to a String.

`(render "{{add_me|add:2:3:4}}" {:add_me 2})` => `11`

`(render "{{h|add:e:l:l:o}}" {:h "h"})` => `"hello"`

#### addslashes
Nota bene, the slashes aren't actually in the input string, but they *are* going to be in the input. Just trying to write valid Clojure code.

`(render "{{name|addslashes}}" {:name "\"Russian tea is best tea\""})` => `"\"Russian tea is best tea\""`

#### block.super

Can be used inside a block to insert the content from the parent block in its place

`{% block foo %} {{block.super}} some content{% endblock %}`

#### capitalize
`(render "{{name|capitalize}}" {:name "russian tea is best tea"})` => `"Russian tea is best tea"`

#### center
`(render "{{name|center:20}}" {:name "yogthos"})` => `" yogthos "`

#### count
`(render "{{name|count}}" {:name "Yogthos"})` => `"7"`

`(render "{{items|count}}" {:items [1 2 3 4]})` => `"4"`

#### count-is
`(render "{{x|count-is:3}}" {:x [1 2 3]})` => `"true"`
`(render "{{x|count-is:0}}" {})` => `"true"`

#### currency-format
`"{{amount|currency-format}}" {:amount 123})` => `"$123.00"`

Uses `java.text.NumberFormat/getCurrencyInstance` for formatting the currency value.
The formatter defaults to the default locale for this instance of the Java Virtual Machine.

An ISO 639 2-letter language code can be added as a locale.

`"{{amount|currency-format:de}}" {:amount 123})` => `"€ 123,00"`

Additionally, the locale can be followed by the country code.

`"{{amount|currency-format:de:DE}}" {:amount 123})` => `"€ 123,00"`

#### date
Format a date. Supports a number of predefined formats, whose output may differ according to the current locale and / or JVM version. Valid formats are: `shortDate`, `shortTime`, `shortDateTime`, `mediumDate`, `mediumTime`, `mediumDateTime`, `longDate`, `longTime`, `longDateTime`, `fullDate`, `fullTime`, and `fullDateTime`.

`(render "{{d|date:shortDate}}" {:d (java.util.Date.)})` => `"8/3/13"`

`(render "{{d|date:shortDate}}" {:d nil})` => `""`

To more precisely control the output format, pass a format string:

`(render "{{d|date:\"yyyy-MM-dd\"}}" {:d (java.util.Date.)})` => `"2013-08-03"`

An ISO 639 2-letter language code can be added to force a particular locale:

`(render "{{now|date:\"MMMM\":fr}}" {:now (java.util.Date.)})` => `"mars"`

To conveniently render the current date, see the [now](#now) tag.

#### default
`(render "{{name|default:"I <3 ponies"}}" {:name "yogthos"})` => `"yogthos"`
`(render "{{name|default:"I <3 ponies"}}" {:name nil})` => `"I <3 ponies"`
`(render "{{name|default:"I <3 ponies"}}" {:name []})` => `"[]"`

`(render "{{name|default:"I <3 ponies"}}" {})` => `"I <3 ponies"`

#### default-if-empty
`(render "{{name|default-if-empty:"I <3 ponies"}}" {:name "yogthos"})` => `"yogthos"`
`(render "{{name|default-if-empty:"I <3 ponies"}}" {:name nil})` => `"I <3 ponies"`
`(render "{{name|default-if-empty:"I <3 ponies"}}" {:name []})` => `"I <3 ponies"`
`(render "{{name|default-if-empty:"I <3 ponies"}}" {})` => `"I <3 ponies"`

#### double-format
`(render "{{tis-a-number|double-format:2}}" {:tis-a-number 10.00001})` => `10.00`
`(render "{{tis-a-number|double-format}}" {:tis-a-number 10.00001})` => `10.0`

#### email
Renders an email address as a selectable link.

`(render "{{address|email}}" {:address "[email protected]"})` => `[email protected]`

Nota bene, the email filter takes an optional `validate?` argument. If it is present and equal to `false`, the email filter will process any argument as above:

`(render "{{address|email:false}}" {:address "this.is.not.an.email.address"})` => `this.is.not.an.email.address`

However, if it is not present or is present but not equal to `false`, an obviously invalid email address will cause an exception to be thrown. Validation is done by a simple regular expression; it will not catch all invalid email addresses.

#### empty?
`(render "{% if xs|empty? %}foo{% endif %}" {:xs []})` => `"foo"`

#### not-empty
`(render "{% if xs|not-empty %}foo{% endif %}" {:xs [1 2]})` => `"foo"`

#### first
`(render "{{seq-of-some-sort|first}}" {:seq-of-some-sort [:dog :cat :bird :bird :bird :is :the :word]})` => `:dog`

#### take
`(render "{{seq-of-some-sort|take:3}}" {:seq-of-some-sort [:dog :cat :bird :bird :bird :is :the :word]})` => `[:dog :cat :bird]`

#### drop
`(render "{{seq-of-some-sort|drop:4}}" {:seq-of-some-sort [:dog :cat :bird :bird :bird :is :the :word]})` => `[:bird :is :the :word]`

`(render "{{seq-of-some-sort|drop:4}}" {:seq-of-some-sort [:dog :cat :bird :bird :bird :is :the :word]})` => `[:bird :is :the :word]`

`(render "{{seq-of-some-sort|drop:4|join:\" \"}}" {:seq-of-some-sort ["dog" "cat" "bird" "bird" "bird" "is" "the" "word"]})` => `bird is the word`

#### drop-last
Similar to drop:

`(render "{{seq-of-some-sort|drop-last:4}}" {:seq-of-some-sort [:dog :cat :bird :bird :bird :is :the :word]})` =\> `[:dog :cat :bird :bird]`

#### get-digit
`(render "{{tis-a-number|get-digit:1}}" {:tis-a-number 12.34567})` => `7`

#### hash
available hashes: `md5`, `sha`, `sha256`, `sha384`, `sha512`

`(render "{{domain|hash:\"md5\"}}" {:domain "example.org"})` => `"1bdf72e04d6b50c82a48c7e4dd38cc69"`

#### join
`(render "{{sequence|join}}" {:sequence [1 2 3 4]})` => `"1234"`
`(render "{{sequence|join:\", \"}}" {:sequence [1 2 3 4]})` => `"1, 2, 3, 4"`

#### json
by default content will be escaped

`(render "{{data|json}}" {:data [1 2 {:foo 27 :dan "awesome"}]})` => `"[1,2,{"foo":27,"dan":"awesome"}]"`

if you wish to render it unescaped use the `safe` filter:

`(render "{{f|json|safe}}" {:f {:foo 27 :dan "awesome"}})`

#### last
`(render "{{sequence|last}}" {:sequence 12.34567})` => `7`
`(render "{{sequence|last}}" {:sequence [1 2 3 4]})` => `4`

#### length
`(render "{{sequence|length}}" {:sequence [1 2 3 4]})` => `4`

#### length-is
`(render "{{sequence|length-is:4}}" {:sequence [1 2 3 4]})` => `true`

#### linebreaks
Single newlines become `
`, double newlines mean new paragraph. Content will
be escaped by default.

`(render "{{foo|linebreaks|safe}}" {:foo "\nbar\nbaz"})` => `"


bar
baz

"`

#### linebreaks-br
like `linebreaks` but doesn't insert `

` tags.
`(render "{{foo|linebreaks-br|safe}}" {:foo "\nbar\nbaz"})` => `"
bar
baz"`

#### linenumbers
Displays text with line numbers.
`(render "{{foo|linenumbers}}" {:foo "foo\n\bar\nbaz"})` => `"1. foo\n2. bar\n3. baz"`

#### lower
`(render "{{foo|lower}}" {:foo "FOOBaR"})` => `"foobar"`

#### name
`(render "{{foo|name}}" {:foo :foobar})` => `"foobar"`

#### number-format
`(render "{{amount|number-format:%.3f}}" {:amount 123.04455})` => `"123.045"`

An ISO 639 2-letter language code can be added as a locale.

`(render "{{amount|number-format:%.3f:de}}" {:amount 123.04455})` => `"123,045"`

#### phone
Renders a phone number as a selectable link, for use with telephony systems (including mobile phones).

`(render "{{number|phone}}" {:number "01234 567890"})` => `"01234 567890"`

The `phone` filter takes two optional positional arguments:

* `national-prefix` The [ITU-T E.123](https://en.wikipedia.org/wiki/E.123) [international subscriber dialing prefix](https://en.wikipedia.org/wiki/List_of_country_calling_codes) to prepend in place of a leading zero. Default is do not prepend.
* `validate?` if present and equal to "false", do not throw exception if number appears invalid. Default behaviour is do throw an exception.

Both arguments are optional, but because they are positional the `national-prefix` must come before `validate?` when
both arguments are supplied.

Thus:

`(render "{{number|phone:44:true}}" {:number "01234 567890"})` => `"01234 567890"`

Validation is done by a simple regular expression; it will not catch all invalid phone numbers.

#### pluralize
Returns the correct (English) pluralization based on the variable. This works with many words, but certainly not all (eg. foot/feet, mouse/mice, etc.)

`(render "{{items|count}} item{{items|pluralize}}" {:items []})` => `"0 items"`

`(render "{{items|count}} item{{items|pluralize}}" {:items [1]})` => `"1 item"`

`(render "{{items|count}} item{{items|pluralize}}" {:items [1 2]})` => `"2 items"`

`(render "{{fruit|count}} tomato{{fruit|pluralize:\"es\"}}" {:fruit []})` => `"0 tomatoes"`

`(render "{{people|count}} lad{{people|pluralize:\"y\":\"ies\"}}" {:people [1]})` => `"1 lady"`

`(render "{{people|count}} lad{{people|pluralize:\"y\":\"ies\"}}" {:people [1 2]})` => `"2 ladies"`

`(render "{{people}} lad{{people|pluralize:\"y\":\"ies\"}}" {:people 2})` => `"2 ladies"`

#### range
returns a range (sequence) of values from 0 to the value, if no arguments are provided.
`(render "{{n|range}}" {:n 3})` => `"(0 1 2)"`

Accepts a start value and a step value:

`(render "{{n|range:1}}" {:n 3})` => `"(1 2)"`

`(render "{{n|range:0:2}}" {:n 3})` => `"(0 2)"`

The value can also be a numeric literal:

`(render "{{3|range}}" {})` => `"(0 1 2)"`

`(render "{{3|range:1}}" {})` => `"(1 2)"`

`(render "{{3|range:0:2}}" {})` => `"(0 2)"`

#### rand-nth
returns rand-nths value from a collection:
`(render "{{foo|rand-nth}}" {:foo [1 2 3]})` => `"2"`

#### remove
removes specified characters from the string:
`(render "{{foo|remove:\"aeiou\"}}" {:foo "abcdefghijklmnop"})` => `"bcdfghjklmnp"`
#### remove-tags
Removes the specified HTML tags from the string:
`(render "{{ value|remove-tags:b:span }}" {:value "foobar"})` => `"foobar"`

#### safe
By default Selmer will HTML escape all variables, The `safe` filter exempts the variable from being html-escaped:

`(render "{{data}}" {:data ""})` => `"<foo>"`

`(render "{{data|safe}}" {:data ""})` => `""`

#### sort
`(render "{{ value|sort }}" {:value [1 4 2 3 5]})` => `"(1 2 3 4 5)"`

#### sort-by
`(render "{{ value|sort-by:name }}" {:value [{:name "John"} {:name "Jane"}]})` => `"({:name "Jane"} {:name "John"})"`

#### sort-reversed
same as sort, but in reverse order

#### sort-by-reversed
same as sort-by, but in reverse order

#### str
Like the clojure function `str`. So you can do crazy stuff like:
`(render "{{people|length-is:2|last|str|join:\"-\"}} lad{{people|pluralize:\"y\":\"ies\"}}" {:people [1 2]})` => `"t-r-u-e ladies"`
Without raising an exception.

#### subs
Like the clojure function `subs`.
`(render "{{s|subs:0:3:\" ..."}}", {:s "Foo bar baz"})` => `"Foo ..."`

#### title
Capitalize the words of a string
`(render "{{s|title}}" {:s "my fancy title"})` => `"My Fancy Title"`

#### upper
`(render "{{shout|upper}}" {:shout "hello"})` => `"HELLO"`

#### urlescape
`(render "{{data|urlescape}}" {:data "clojure url"})` => `"clojure+url"`

#### round

Returns the closest integer to the argument, with ties rounding up.

`(render "{{foo|round}}" {:foo 3.33333})` =\> `"3"`

#### multiply

Multiplies the value by the given number. Throws error if one of the both
values are neither Long nor Double.

`(render "{{foo|multiply:2}}" {:foo 3.3})` =\> `"6.6"`

#### divide

Multiplies the value by the given number. Throws error if one of the both
values are neither Long nor Double.

`(render "{{foo|divide:2}}" {:foo 6})` =\> `"3"`

#### replace

Replaces all occurrences of the first string with the second string.

`(render "{{foo|replace:foo:bar}}" {:foo "foo test foo ..."})` =\> `"bar test
bar ..."`

#### between?

`(render "{{foo|between?:2:4}}" {:foo 3})` =\> `"true"`

## Tags

Selmer supports two types of tags. The tags can be inline, which means that they consist of a single
tag statement such as `include` or `extends`, or contain a body and intermediate tags,
such as `if`, `else`, `endif`.

For example if we wanted to iterate over a collection of items, we could write the following:

```clojure
(render
"{% for user in users %}{{user.name}}{% endfor %}"
{:users [{:name "John"} {:name "Jane"}]})
=>"JohnJane"
```

It's also possible to define custom tags using the `add-tag!` macro:

```clojure
(use 'selmer.parser)

(add-tag! :foo
(fn [args context-map]
(str "foo " (first args))))

(render "{% foo quux %} {% foo baz %}" {})
=>"foo quux foo baz"
```
it's also possible to add block tags. When adding a block tag, the handler
function should accept the tag arguments, the context map, and the content.
The content will be keyed on the opening tag name as can be seen below.

The example uses [infix](https://github.com/rm-hull/infix) library to implement
an infix math parsing tag:

```clojure
(require
'[infix.core :refer [base-env]]
'[jasentaa.parser :as p :refer [parse-all]]
'[infix.grammar :refer [expression]]
'[selmer.parser :as selmer])

(selmer/add-tag! :math
(fn [args context-map]
((parse-all expression (apply str args))
(merge base-env context-map))))

(selmer/render "{% math x + y * z %}" {:x 1 :y 2 :z 3})
=>"7"

(selmer/add-tag! :uppercase
(fn [args context-map content]
(.toUpperCase (get-in content [:uppercase :content])))
:enduppercase)

(render "{% uppercase %}foo {{bar}} baz{% enduppercase %}" {:bar "injected"})
=>"FOO INJECTED BAZ"
```

The args passed to the handler by default are not resolved, so if the tag arg contains
a variable `{{variable}}`, a filter `{{variable|filter}}` or a tag `{% if foo %}bar{% endif %}`,
you will get them as-is inside a string:

```
{% custom-tag {{variable}} %} -> "{{variable}}"
{% custom-tag {{variable|filter}} %} -> "{{variable|filter}}"
{% custom-tag {% if foo %}bar{% endif %} %} -> "{% if foo %}bar{% endif %}"
```

Similarly, if you have a literal as the arg to a tag, the handler will receive it double quoted:

```
{% custom-tag "Hello John" %} -> "\"Hello John\""
```

In both cases, you can use `resolve-arg` to resolve the variable, filter, tag or literal so that:

```clojure
(resolve-arg "Hello {{variable}}" {:variable "John"})
=> "Hello John"
(resolve-arg "Hello {{variable|upper}}" {:variable "John"})
=> "Hello JOHN"
(resolve-arg "Hello {% if variable = \"John\" %}Mr {{variable}}{% endif %}" {:variable "John"})
=> "Hello Mr John"
(resolve-arg "Hello John" {})
=> "Hello John"
```

Here's an example custom tag which works like if/else but if a string ends with some other string,
and we want to be able to use variables, filters and tags for the string:

```clojure
(add-tag!
:ifendswith
(fn [args context-map content]
(let [args (map #(resolve-arg % context-map) args)]
(if (str/ends-with? (first args) (second args))
(-> content :ifendswith :content)
(-> content :else :content))))
:else :endifendswith)
```

### Built-in Tags

#### include

This tag is only available for `render-file` since `render` function only operates on a template consisting of a single string.

replaces itself with the contents of the referenced template

`{% include "path/to/comments.html" %}`

optionally, you can supply default arguments any tags matching these will have the `default` filter applied using the value supplied:

`{% include "templates/inheritance/child.html" with name="Jane Doe" greeting="Hello!" %}`

#### block

Allows specifying a block of content that can be overwritten using the template inheritance discussed below.

`{% block foo %}This text can be overridden later{% endblock %}`

#### cycle

Will cycle through the supplied values.

```
(render "{% for i in items %}

  • {{i}}
  • {% endfor %}"
    {:items (range 5)})
    ```
    =>
    ```
    "
  • 0
  • 1
  • 2
  • 3
  • 4
  • "
    ```

    #### debug

    Prints the context map passed to the template using `pr-str`. Pretty printing can be enabled by including [json-html](https://github.com/yogthos/json-html) or implementing `json-html.core/edn->html` function that accepts a map as its argument and returns a string.

    ```
    (render "{% debug %}" {:foo :bar})
    ```
    =>
    ```



    :foo
    :bar



    ```

    #### extends

    This tag is used to reference a parent template. The blocks in parents are recursively overridden by
    the blocks from child templates.

    * Note: child templates can **only** contain blocks. Any tags or text outside the blocks will be
    ignored!

    For example, say we have a base template called `base.html` and a child template `child.html`:

    ```xml


    {% block foo %}This text can be overridden later{% endblock %}

    ```

    ```xml
    {% extends "base.html" %}
    {% block foo %}

    This text will override the text in the parent

    {% endblock %}
    ```

    #### if

    It's an `if` -- only render the body if the conditional is true.

    `{% if condition %}yes!{% endif %}`

    `{% if not condition %}yes!{% endif %}`

    `{% if condition %}yes!{% else %}no!{% endif %}`

    it's possible to use `any` and `all` operators to check multiple values:

    `(render "{% if any foo bar baz %}hello{% endif %}" {:bar "foo"})`

    `(render "{% if not any foo bar baz %}hello{% endif %}" {})`

    `(render "{% if all foo bar %}hello{% endif %}" {:foo "foo" :bar "bar"})`

    numeric comparisons are also supported using the `=`, `<`, `>`, `<=` and `>=` operators

    `(render "{% if 5 >= x %}yes!{% endif %}" {:x 3})`

    `(render "{% if x <= y %}yes!{% endif %}" {:x 3 :y 5})`

    `(render "{% if x = 5.0 %}yes!{% else %}no!{% endif %}" {:x 5})`

    `(render "{% if x > 5 %}yes!{% else %}no!{% endif %}" {:x 6})`

    `(render "{% if vals|length <= 3 %}yes!{% else %}no!{% endif %}" {:vals (range 3)})`

    you can compare strings or keywords with `=` as well:

    `(render "{% if x = \"banana\" %}yellow{% endif %}" {:x "banana"})`

    `(render "{% if x = :banana %}yellow{% endif %}" {:x :banana})`

    filters work for the conditions:

    ```clojure
    (add-filter! :empty? empty?)
    (render "{% if files|empty? %}no files{% else %}files{% endif %}"
    {:files []})
    ```

    You can have elif (else if) clauses if you want:

    ```clojure
    (render "{% if pl > 9000 %}
    it's over 9000!
    {% elif pl > 100 %}
    it's in a middle zone
    {% elif status = \"decent\" %}
    still pretty ok
    {% else %}
    lower than 10... not ok
    {% endif %}"
    {:pl 14 :status "decent"})
    => "still pretty ok"
    ```

    #### ifequal
    Only render the body if the two args are equal (according to clojure.core/=).

    `{% ifequal foo bar %}yes!{% endifequal %}`

    `{% ifequal foo bar %}yes!{% else %}no!{% endifequal %}`

    `{% ifequal foo "this also works" %}yes!{% endifequal %}`

    #### ifunequal
    Only render the body if the two args are unequal (according to clojure.core/=).

    `{% ifunequal foo bar %}yes!{% endifunequal %}`

    **for/endfor** *block*

    #### for
    Render the body one time for each element in the list. Each render will introduce the following variables into the context:

    * `forloop.first`
    * `forloop.last`
    * `forloop.counter`
    * `forloop.counter0`
    * `forloop.revcounter`
    * `forloop.revcounter0`
    * `forloop.length`
    * `forloop.parentloop`
    * `forloop.previous`

    `forloop.previous` is a hash map that contains the loop variable name:

    ```
    {% for item in some.values %}
    {% if forloop.previous.item = item %}
    repeated
    {% else %}
    {{item}} is new!
    {% endif %}
    {% endfor %}
    ```

    `{% for x in some-list %}element: {{x}} first? {{forloop.first}} last? {{forloop.last}}{% endfor %}`

    you can iterate over nested data structures, eg:

    `{% for item in items %} {{item.name}}{{item.age}} {% endfor %}`

    array elements can be destructured in for loops:

    `(render "{% for x,y in items %}{{x}},{{y}}{% endfor %}" {:items [["a" "b"] ["c" "d"]]})` => `"a,bc,d"`

    you can also specify the default content if there are no items using the `{% empty %}` tag:

    `(render "{% for i in foo %} {{i}} {% empty %}no elements{% endfor %}" {})` => `"no elements"`

    filters can be used inside the for loop:

    `(render "{% for x in foo.bar|sort %}{{x}}{% endfor %}" {:foo {:bar [1 4 3 5]}})` => `"1345"`

    `(render "{% for i in x %}{% for j in i %}{{j}}-{{forloop.parentloop.counter}}{% endfor %}{% endfor %}" {:x [[:a :b]]})` => `":a-1:b-1"`

    #### sum
    Sums multiple variables together
    `(render "{% sum foo bar baz %}" {:foo 3 :bar 2 :baz 1})` => `"6"`

    `(render "{% sum foo \\1.1 %}" {:foo 1.1})` => `"2.2"`

    #### now
    renders current time

    `(render (str "{% now \"dd MM yyyy\" %}") {})` => `"\"01 08 2013\""`

    #### comment
    ignores any content inside the block

    `(render "foo bar {% comment %} baz test {{x}} {% endcomment %} blah" {})` => `"foo bar blah"`

    A short form is also available:

    `(render "foo bar {# baz test {{x}} #} blah" {})` => `"foo bar blah"`

    #### firstof
    renders the first occurance of supplied keys that doesn't resolve to false:

    `(render "{% firstof var1 var2 var3 %}" {:var2 "x" :var3 "not me"})` => `"x"`


    safe

    safe tag will prevent escaping of any content inside it:

    `(render "{% safe %}{{foo|upper}}{% endsafe %}" {:foo ""})` => ``

    Note, the escaping of variables can also be controlled through the dynamic binding of `selmer.util/*escape-variables`.

    #### script

    The script tag will generate an HTML script tag and prepend the value of the `selmer/context` key
    to the URI. When `selmer/context` key is not present then the original URI is set.

    `(render "{% script \"/js/site.js\" %}" {:selmer/context "/myapp"})` =>
    ```
    ""
    ```

    Since 1.11.1 URI can be a name of context parameter with optional filters.

    `(render "{% script path %}" {:selmer/context "/myapp" :path "/js/site.js"})` =>
    ```
    ""
    ```

    `(render "{% script path|upper %}" {:selmer/context "/myapp" :path "/js/site.js"})` =>
    ```
    ""
    ```
    #### style

    The style tag will generate an HTML style tag and prepend the value of the `selmer/context` key
    to the URI. When `selmer/context` key is not present then the original URI is set.

    `(render "{% style \"/css/screen.css\" %}" {:selmer/context "/myapp"})` =>
    ```
    ""
    ```

    Since 1.11.1 URI can be a name of context parameter with optional filters.

    `(render "{% style path %}" {:selmer/context "/myapp" :path "/css/screen.css"})` =>
    ```
    ""
    ```

    `(render "{% style path|upper %}" {:selmer/context "/myapp" :path "/css/screen.css"})` =>
    ```
    ""
    ```
    #### verbatim
    prevents any tags inside from being parsed:

    `(render "{% verbatim %}{{if dying}}Still alive.{{/if}}{% endverbatim %}" {})` => `"{{if dying}}Still alive.{{/if}}"`

    #### with
    injects the specified keys into the context map:

    `(render "{% with total=business.employees|count %}{{ total }}{% endwith %}" {:business {:employees (range 5)}})` => `"5 employees"`

    ## Template Inheritance

    **note**: tags using template inheritance can only be used with `render-file` since `render` will only consider the input string itself

    ### Extending Templates

    Templates can inherit from other templates using the `extends` tag. When extending a template, any blocks in the parent
    will be overwritten by blocks from the child with the same name. For example if we had the following scenario:

    `base.html`

    ```xml

    {% block header %}
    {% endblock %}

    {% block content %}
    {% endblock %}

    {% block footer %}
    {% endblock %}

    ```

    `child-a.html`

    ```xml
    {% extends "base.html" %}
    {% block header %}

    child-a header


    {% endblock %}

    {% block footer %}

    footer


    {% endblock %}
    ```

    `child-b.html`

    ```xml
    {% extends "child-a.html" %}
    {% block header %}

    child-b header


    {% endblock %}

    {% block content %}
    Some content
    {% endblock %}
    ```

    If we called `(render-file "child-b.html" {})` then the compiled template would look as follows:

    ```xml

    {% block header %}

    child-b header


    {% endblock %}
    {% block content %}
    Some content
    {% endblock %}

    {% block footer %}

    footer


    {% endblock %}

    ```

    It's also possible to include content from the parent block using the `{{block.super}}` hint. If we change `child-b.html`
    to look as follows:

    ```xml
    {% extends "child-a.html" %}
    {% block header %}
    {{block.super}}

    child-b header


    {% endblock %}

    {% block content %}
    Some content
    {% endblock %}
    ```

    Then we'd have the following output:

    ```xml

    {% block header %}

    child-a header

    child-b header


    {% endblock %}
    {% block content %}
    Some content
    {% endblock %}

    {% block footer %}

    footer


    {% endblock %}

    ```

    ### Including Templates

    Templates can also `include` other templates. In this case the contents of the child are simply spliced in place
    of the tag:

    `base.html`

    ```xml

    {% include "content.html" %}

    ```

    `content.html`

    ```xml
    content
    ```

    results in:

    ```xml

    content

    ```

    It's also possible to specify default values for the included templates using
    `with`:

    `base.html`

    ```xml

    {% include "content.html" with content="some content" %}

    ```

    `content.html`

    ```xml
    {{content}}
    ```

    results in:

    ```xml

    {{content|default:"some content"}}

    ```

    You may also specify more than one value:

    `base.html`

    ```xml

    {% include "content.html" with content="some content" url="/path/to/page" %}

    ```

    ## Missing values

    Missing values are by default rendered as an empty string:

    ```clojure
    (render "{{missing}}" {})
    => ""
    ```
    The same goes for for loops:
    ```clojure
    (parser/render "{% for e in items %}{% endfor %}" {})
    => ""
    ```

    It is possible to overwrite this behavior to output a different value when encountering a mising value. This is done by calling `selmer.util/set-missing-value-formatter!` to provide a function that produces the desired output.

    `set-missing-value-formatter!` takes a function of two arguments, a map of info about the tag and the context map, which is called on a missing value. The function should return the value to be output in place of an empty string (which is the default from 'default-missing-value-formatter').

    ```clojure
    (defn missing-value-fn [tag context-map]
    (str ""))

    (selmer.util/set-missing-value-formatter! missing-value-fn)

    (selmer.parser/render "{{not-here}}" {})
    => ""
    ```

    or you can throw an exception:

    ```clojure
    (defn missing-value-fn [tag context-map]
    (throw (Exception. "Nope")))

    (selmer.util/set-missing-value-formatter! missing-value-fn)

    (selmer.parser/render "{{not-here}}" {}) => Exception: Nope
    ```

    When you set a custom missing value handler, by default filters are bypassed for missing values:

    ```clojure
    (defn missing-value-fn [tag context-map]
    (str ""))

    (selmer.util/set-missing-value-formatter! missing-value-fn)

    (selmer.parser/render "{{not-here|count}}" {})
    => ""
    ```

    but this can be overwritten so filters are evaluated for missing values:

    ```clojure
    (defn missing-value-fn [tag context-map]
    (str ""))

    (selmer.util/set-missing-value-formatter! missing-value-fn :filter-missing-values true)

    (selmer.parser/render "{{not-here|count}}" {})
    => "0"
    ```

    Although for most use cases, this will not make sense.

    ## Internationalization

    While there is no built in support for internationalization, it can be added via a custom tag.
    The following example uses the [tongue](https://github.com/tonsky/tongue) library to implement
    the `i18n` tag:

    ```clojure
    (require '[tongue.core :as tongue]
    '[selmer.parser :as parser])

    (def translate
    ;; [locale key & args] => string
    (tongue/build-translate
    {:en {:animals
    {:dog "dog"
    :cat "cat"}}
    :fr {:animals
    {:dog "chien"
    :cat "chat"}}}))

    (parser/add-tag! :i18n
    (fn [[k] context]
    (->> k (keyword) (translate (or (:i18n/locale context) :en)))))

    (parser/render "{% i18n animals/dog %}" {})
    ;=> "dog"
    (parser/render "{% i18n animals/dog %}" {:i18n/locale :fr})
    ;=> "chien"
    ```

    [**Back To Top ⇧**](#selmer)

    ## License

    Copyright © 2015 Dmitri Sotnikov

    Distributed under the Eclipse Public License, the same as Clojure.