Code Monkey home page Code Monkey logo

selmer's Introduction

Selmer

build status

A fast, Django inspired template system in pure Clojure.

Installation

Leiningen

Clojars Project

tools.deps

{selmer {:mvn/version "<version>"}}

Marginalia documentation

Marginalia documentation

Usage

Built-in Filters

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

Built-in Tags

block comment cycle debug if ifequal ifunequal include extends firstof for now safe script style verbatim with

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:

(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:

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

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

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

(use 'selmer.parser)

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

To list the declared variables in the template:

(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:

(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:

(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:

(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:

:tag-open
:tag-close
:filter-open
:filter-close
:tag-second
(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:

(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:

(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.

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:

<ul>
{% for item in items %}
    <li>{{item}}</li>
{% endfor %}
</ul>

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:

(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:

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

(render "{{x|foo}}" {:x "<div>I'm safe</div>"})
=>"<DIV>I'M SAFE</DIV>"

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

(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 &lt;-- snip --&gt; 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 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]"}) => <a href="[email protected]">[email protected]</a>

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"}) => <a href="this.is.not.an.email.address">this.is.not.an.email.address</a>

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,{&quot;foo&quot;:27,&quot;dan&quot;:&quot;awesome&quot;}]"

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 <br />, double newlines mean new paragraph. Content will be escaped by default.

(render "{{foo|linebreaks|safe}}" {:foo "\nbar\nbaz"}) => "<p><br />bar<br />baz</p>"

linebreaks-br

like linebreaks but doesn't insert <p> tags. (render "{{foo|linebreaks-br|safe}}" {:foo "\nbar\nbaz"}) => "<br />bar<br />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"}) => "<a href='tel:01234-567890'>01234 567890</a>"

The phone filter takes two optional positional arguments:

  • national-prefix The ITU-T E.123 international subscriber dialing prefix 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"}) => "<a href='tel:+44-1234-567890'>01234 567890</a>"

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 "<b><span>foobar</span></b>"}) => "foobar"

safe

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

(render "{{data}}" {:data "<foo>"}) => "&lt;foo&gt;"

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

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 &quot;Jane&quot;} {:name &quot;John&quot;})"

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:

(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:

(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 library to implement an infix math parsing tag:

(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:

(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:

(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 %}<li class={% cycle \"blue\" \"white\" %}>{{i}}</li>{% endfor %}"
        {:items (range 5)})

=>

"<li class=\"blue\">0</li><li class=\"white\">1</li><li class=\"blue\">2</li><li class=\"white\">3</li><li class=\"blue\">4</li>"

debug

Prints the context map passed to the template using pr-str. Pretty printing can be enabled by including 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})

=>

<div class="jh-root">
 <table class="jh-type-object">
  <tbody><tr><th class="jh-key jh-object-key"><span class="jh-type-string">:foo</span></th>
    <td class="jh-value jh-object-value"><span class="jh-type-string">:bar</span></td></tr>
  </tbody>
 </table>
</div>

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:

<html>
	<body>
		{% block foo %}This text can be overridden later{% endblock %}
	</body>
</html>
{% extends "base.html" %}
{% block foo %}<p>This text will override the text in the parent</p>{% 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:

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

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

(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 %} <tr><td>{{item.name}}</td><td>{{item.age}}</td></tr> {% 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 "<foo>"}) => <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"}) =>

"<script async-attr defer-attr src=\"/myapp/js/site.js\" type=\"text/javascript\"></script>"

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"}) =>

"<script async-attr defer-attr src=\"/myapp/js/site.js\" type=\"text/javascript\"></script>"

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

"<script async-attr defer-attr src=\"/myapp/JS/SITE.JS\" type=\"text/javascript\"></script>"

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"}) =>

"<link href=\"/myapp/css/screen.css\" rel=\"stylesheet\" type=\"text/css\" />"

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"}) =>

"<link href=\"/myapp/css/screen.css\" rel=\"stylesheet\" type=\"text/css\" />"

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

"<link href=\"/myapp/CSS/SCREEN.CSS\" rel=\"stylesheet\" type=\"text/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

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

<html>
<body>
{% block header %}
{% endblock %}

{% block content %}
{% endblock %}

{% block footer %}
{% endblock %}
</body>
</html>

child-a.html

{% extends "base.html" %}
{% block header %}
<h1>child-a header</h1>
{% endblock %}

{% block footer %}
<p>footer</p>
{% endblock %}

child-b.html

{% extends "child-a.html" %}
{% block header %}
<h1>child-b header</h1>
{% endblock %}

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

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

<html>
<body>
{% block header %}
<h1>child-b header</h1>
{% endblock %}
{% block content %}
Some content
{% endblock %}

{% block footer %}
<p>footer</p>
{% endblock %}
</body>
</html>

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:

{% extends "child-a.html" %}
{% block header %}
{{block.super}}
<h1>child-b header</h1>
{% endblock %}

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

Then we'd have the following output:

<html>
<body>
{% block header %}

<h1>child-a header</h1>

<h1>child-b header</h1>
{% endblock %}
{% block content %}
Some content
{% endblock %}

{% block footer %}
<p>footer</p>
{% endblock %}
</body>
</html>

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

<html>
{% include "content.html" %}
</html>

content.html

<body>content</body>

results in:

<html>
<body>content</body>
</html>

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

base.html

<html>
{% include "content.html" with content="some content" %}
</html>

content.html

<body>{{content}}</body>

results in:

<html>
<body>{{content|default:"some content"}}</body>
</html>

You may also specify more than one value:

base.html

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

Missing values

Missing values are by default rendered as an empty string:

(render "{{missing}}" {})
=> ""

The same goes for for loops:

(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').

(defn missing-value-fn [tag context-map]
  (str "<Missing value: " (or (:tag-value tag) (:tag-name tag)) ">"))

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

(selmer.parser/render "{{not-here}}" {})
=> "<Missing value: not-here>"

or you can throw an exception:

(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:

(defn missing-value-fn [tag context-map]
  (str "<Missing value: " (or (:tag-value tag) (:tag-name tag)) ">"))

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

(selmer.parser/render "{{not-here|count}}" {})
=> "<Missing value: not-here>"

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

(defn missing-value-fn [tag context-map]
  (str "<Missing value: " (or (:tag-value tag) (:tag-name tag)) ">"))

(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 library to implement the i18n tag:

(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 ⇧

License

Copyright © 2015 Dmitri Sotnikov

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

selmer's People

Contributors

adamneilson avatar adomokos avatar bitemyapp avatar borkdude avatar boxxxie avatar camdez avatar casperc avatar cesarbp avatar dl1ely avatar escherize avatar jahson avatar jaju avatar jstaffans avatar lucacervello avatar martinzuern avatar mchughs avatar mgerlach-klick avatar minhtuannguyen avatar mpenet avatar noahtheduke avatar quetz avatar ricardojmendez avatar rutchkiwi avatar rwilson avatar schmir avatar seancorfield avatar svmbrown avatar tendant avatar yogthos avatar zuriar 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  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  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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

selmer's Issues

can support include file which has {% block %}?

{% extend "base" %}
{% block content %}

{% include "b.html" %}

{% block columns %}
xyz
{% endblock %}

{% endblock %}

b.html:
....
{% block columns %}
abc
{% endblock %}
...

I want to use xyz to replace the abc in b.html .
how?

file separator issue on windows

in template_parser.clj, line 41. (re-pattern (str "[^" File/separator "]include")

This will cause the following error when file separator is 'backslash' i.e. on windows:

PatternSyntaxException Unclosed character class near index 10

Include does not support property inside of with

Trying to pass an existing property as value to include with.
Simple example

{% include "rating.html" with rating=merchant.rating %}
and 
{% include "rating.html" with rating=merchant.customer.rating %}

But it does not work. is it supported or wrong usage?
If not supported i vote for adding this feature :)

not support chineses

on templates file add chinese words like "中文" will display as "??" on safari.

Include breaks when path contains 'include'

The following all work:

{% include "mypath/aa.html" %}
{% include "mypath/aa-inc.html" %}
{% include "mypath/incl/aa.html" %}

the following both break

{% include "mypath/aa-include.html" %}
{% include "mypath/includes/aa.html" %}

throwing

java.lang.IllegalArgumentException: No implementation of method: :make-reader of protocol: #'clojure.java.io/IOFactory found for class: nil
                core_deftype.clj:541 clojure.core/-cache-protocol-fn
                           io.clj:73 clojure.java.io/fn[fn]
                          io.clj:106 clojure.java.io/reader
                     RestFn.java:410 clojure.lang.RestFn.invoke
                    validator.clj:64 selmer.validator/validate-tags
                    validator.clj:74 selmer.validator/validate
             template_parser.clj:151 selmer.template-parser/read-template
             template_parser.clj:201 selmer.template-parser/preprocess-template
                     RestFn.java:439 clojure.lang.RestFn.invoke
              template_parser.clj:44 selmer.template-parser/insert-includes[fn]
              template_parser.clj:34 selmer.template-parser/insert-includes
             template_parser.clj:201 selmer.template-parser/preprocess-template
                     RestFn.java:410 clojure.lang.RestFn.invoke
                      parser.clj:209 selmer.parser/parse-file
                      parser.clj:232 selmer.parser/parse
                     RestFn.java:442 clojure.lang.RestFn.invoke
                       parser.clj:99 selmer.parser/render-file
                     RestFn.java:425 clojure.lang.RestFn.invoke
...

Obvious workaround is to not use 'include' in the path.

img tags with rendered src attribute not working on fedora 19

I've found an issue with selmer on a fresh install of fedora 19.
The problem is that img tags where the src attribute is renderd dynamically like so:

<img src="{{some.string.value}}"></img>

will not render. the entire img tag is missing from the rendered html output.
If the src attribute is simply hard-coded to the value expected, then the img tag is rendered properly.

The same clojure/luminus project works just fine on ubuntu/mint , and only shows this problem on fedora. this is the only problem i've noticed when comparing builds of this project between fedora and ubuntu.

I have created a minimal luminus app to demonstrate the problem:
https://github.com/ShaneKilkelly/imgtest

The single page on that project shows an example of an img tag not rendering. I have also included an uberjar build of the project in the 'target' directory, and a dump of the rendered html as seen on fedora.

For completeness, the output of java -version on fedora is:

java version "1.7.0_45"
OpenJDK Runtime Environment (fedora-2.4.3.0.fc19-x86_64 u45-b15)
OpenJDK 64-Bit Server VM (build 24.45-b08, mixed mode)

The same command on linux mint yields:

java version "1.7.0_25"
OpenJDK Runtime Environment (IcedTea 2.3.10) (7u25-2.3.10-1ubuntu0.13.04.2)
OpenJDK 64-Bit Server VM (build 23.7-b01, mixed mode)

I will be able to provide any other materials needed to debug this problem.

Thanks in advance!

[Question] Is there a way to filter the contents of a block?

Hi.

I'm looking for a way to do something like follows
{% block|upper-case %}
Foo Bar
{% endblock %}

returning the following output
FOO BAR.

I can't see how I can achieve it. Is there a way?
In this case, I don't care to name the block - I only want its contents to be passed to a function that is registered as a filter, and the block contents are replaced.

Thanks!

set-resource-path! unexpect behaviour

Hello,

I use selmer.parser/set-resource-path! function and the problem is that selmer add a / before my path.

For example :

   (selmer.parser/set-resource-path! "path/views")

generate the following path :

  /path/views

Thanks !

Checking for `contains?`

I've been poking around at selmer and wanted a way to do something like this:

{% if request.query-params|has?:"debug" %}
    <h1>Debug Info</h1>
    <pre>{{ request|pprint }}</pre>
{% endif %}

I added a few filters and they work fine:

(defn has?
  "Return whether coll has x inside it.

  Does the sane thing for maps/sets (checks keys) and vectors (checks values).

  "
  [coll x]
  (if (vector? coll)
    (some (partial = x) coll)
    (contains? coll x)))

; ...

     :has?
     (fn [coll x] (has? coll x))

     :in?
     (fn [x coll] (has? coll x))

     :has-keyword?
     (fn [coll x] (has? coll (keyword x)))

     :keyword-in?
     (fn [x coll] (has? coll (keyword x)))

Would you want a pull request to add those? Is there a less-ugly way I can handle keyword params?

extend and include issue

hi all; i am newbie to clojure. but wrote a lot django. index.html file works correctly but if i add some extends od include tag above i get an error:

java.lang.NullPointerException

template_parser.clj:143 selmer.template-parser/read-template
template_parser.clj:192 selmer.template-parser/preprocess-template
RestFn.java:410 clojure.lang.RestFn.invoke
parser.clj:213 selmer.parser/parse-file
parser.clj:236 selmer.parser/parse
RestFn.java:442 clojure.lang.RestFn.invoke
parser.clj:104 selmer.parser/render-file
RestFn.java:425 clojure.lang.RestFn.invoke
layout.clj:15 blojr.views.layout/blojr.views.layout.RenderableTemplate

i just wrote:

{% extends "inc/base.html" %}
{% block content %}
{% endblock %}

if any detail needed i can add.

render-file used in a jar file

Using render file works fine when using in a lein project, it fails however when deployed into a jar file:

java.lang.Exception: temaplate: "file:/usr/share/celestial/target/celestial-0.2.1.jar!/celestial/views/templates/home.html" not found
parser.clj:92 selmer.parser/render-file
RestFn.java:425 clojure.lang.RestFn.invoke

Going through the source it looks like its trying to access the file within the jar to get modification time which won't work if the template is contained within a jar

Thanks

How to...

Hello,

Just as your firstof tag

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

That receives arguments and deals with their actual value

I would like to have a get tag that receives arguments and deals with their actual value.

(render 
    "{% for k,v in questions %}
        {{v}}: {% get answers k %}
     {% endfor %}" 
{:answers
   {:team "SuperTeam"} 
 :questions 
   {:team "What is your team?"}})
;;=>  What is your team? SuperTeam

add-tag! is not my friend in this case, since it is receiving labels and k as strings.

  (parser/add-tag!
    :get-in
    (fn [args _ ] some-stuff ))

;; args value is (answers k)
;; I would expect it to be ({:team "SuperTeam"} :team)

I know this is possible since firstof tag does receive the actual values, but my current skills don't let me figure out how it does it.

Any help will be super appreciated.

Note:

Of course I can just send something as simple as:

(render 
    "{% for k,v in qa %}
        {{k}}: {{v}}
     {% endfor %}" 
{:qa
   {:"What is your team?" "SuperTeam"} })

But I would like to understand this project better.

If you know where I can ask for help let me know.

problems with `with`

Two issues with with: endwith isn't a recognized tag when passed to render-file, and include ... with only accepts strings instead of content referenced by symbols.

(require 'selmer.parser)

(print (slurp (clojure.java.io/resource "mywith1.html")))
;=> {% with total=business.employees|count %}{{ total }}{% endwith %}

; works
(print (selmer.parser/render (slurp (clojure.java.io/resource "mywith1.html"))
                      {:business {:employees (range 5)}}))
;=> 5

; doesn't work
(print (selmer.parser/render-file "mywith1.html"
                      {:business {:employees (range 5)}}))
;=> ExceptionInfo Unrecognized tag found {% endwith %} on line 1 for template file:.../resources/mywith1.html  clojure.core/ex-info (core.clj:4355)


(print (slurp (clojure.java.io/resource "mywith2a.html")))
;=> hello from mywith2a.html
;=> employee is {{business.employee}}
;=> {% include "mywith2b.html" with field1="abc" field2=business.employee %}

(print (slurp (clojure.java.io/resource "mywith2b.html")))
;=> hello from mywith2b.html
;=> field1: {{field1}}
;=> field2: {{field2}}

; doesn't work, field2 doesn't have content
(print (selmer.parser/render-file "mywith2a.html" {:business {:employee "fred"}}))
;=> hello from mywith2a.html
;=> employee is fred
;=> hello from mywith2b.html
;=> field1: abc
;=> field2:
;=> 

set-resource-path! failing in uberjar mode

I have an app where I have templates inside a "templates" directory on the classpath, e.g. templates/index.html so I use the following to set the resource path:

(selmer.parser/set-resource-path! (clojure.java.io/resource "templates"))

And render the templates as usual using a relative path:

(selmer/render-file "index.html" params)

This all works fine when I run the application at the REPL or with lein run. However when I run in uberjar mode it seems to fail, unable to find the file.

From what I can work out, the selmer.parser/current-resource-path atom is being set to nil, so from that I conclude that set-resource-path! isn't working in an uberjar context for some reason.

I'm running on Windows 7 with selmer 0.7.2

Extend with variable

Hello,

I would like to put a variable into extend path. Is it possible ?

I try following but it's fail :

 {% extends selmer.view %}
 {% extends {{selmer.view}} %}

Then, I would like to do :

 {% extends "{{selmer.view}}/layout.html" %}

The error is :

 No implementation of method: :make-reader of protocol: #'clojure.java.io/IOFactory found for class: nil

Thanks !

Commented block raises error

This raises an exception but shouldn't:

{% comment %}
    {% include "template-that-not-exist.html" %}
{% endcomment %}

Compile and runtime error handling

Probably want a separate validator lexer for compile-time error checking.

At runtime, wrap the FunctionNodes in a try/catch and dump the exception + surrounding content (fetch from surrounding nodes, handling appropriately) if it fails.

Short comment syntax

Both Django and Jinja provide short comment syntax {# like this #} http://stackoverflow.com/questions/719915/how-to-put-comments-in-django-templates

The Reasoning:
I may be wrong, but looks like Selmer doesn't have plugins on its own for popular IDE like
IntelliJ IDEA. Life is sad without syntax highlighting and autocompletion.
The good workaround is to enable Django template handling and treat target folder like a Django templates container.

But this trick can perform only if the syntax is the same. Comments are out of the frame for now.

Better exception when file in extends tag is not found

Currently when you have an extends tag with an incorrect path it throws the following exception:

IllegalArgumentException No implementation of method: :make-reader of protocol: 
#'clojure.java.io/IOFactory found for class: nil  clojure.core/-cache-protocol-fn
 (core_deftype.clj:541)

The offending line is this one I believe:
https://github.com/yogthos/Selmer/blob/master/src/selmer/template_parser.clj#L155

The function that throws is validator/validate because it gets a nil from resource-path. So it should be easy to check for nil on either of those two and throw a better exception.

unexpected behavior of with block

The with block behaves in an unexpected way if the bound values are not strings.

For example, the template "{%with new=old%}{%for i in new%}{{i}}{%endfor%}{%endwith%}" with context {:old [1 2 3]} renders as "[1 2 3]" instead of "123". The reason is that the parse-with function escapes bound values in with blocks. (So in the example, the vector [1 2 3] gets converted to the string "[1 2 3]".)

Could you set the escape? flag to false when parse-with calls compile-filter-body or does that break something else?

Numeric comparison of forloop.counter?

maybe i'm missing something, but i'm trying to only show the first 10 items of a collection in my template using the following syntax:

{% for listing in listings %}{% if forloop.counter < 10 %}
{{ listing.stuff }}
{% endif %}{% endfor %}

but when I have this if in my loop i see nothing. am I doing something wrong?

truncate?

hey;

are there any option to truncate tags? you know there is one in django?

{{ post.body|truncatewords:40}}

it was something like this.

i didn't see in doc. but may be there is a trick or sth else.

Template compilation error when using {% empty %} with the {% for %} tag

When I use the {% empty %} option within a for tag, I'm seeing a Template Compilation Error, Unrecognized tag found for that {% empty %} tag. Removing the empty tag clears the error, and the for tag then works as expected.

I've checked my m2 repository and Selmer 0.5.4 is there, the jar file's version of tags.clj looks to match the current github version. Can someone try to reproduce this problem please?

Trouble using include tag

I'm having trouble using the include tag in my templates when using Selmer. It keeps saying the tag is unrecognised. (Additionally the exception raised does not detail which file contained the unrecognised tag, let alone a line number.)

Simple example: (selmer.parser/render "{% include test.html %}" {})
Output: "Exception unrecognized tag: :include - did you forget to close a tag? selmer.parser/expr-tag (parser.clj:116)"

I'm using version 0.6.6 and I've tried various incantations like quoting the template path etc. Any ideas?

Cheers, Dave.

switch to using ex-info for exceptions

Currently, the parser throws generic Exception on error, it would be better to use ex-info and set a specific type. This would allow making middleware for printing out friendly errors instead of the generic stack trace.

render-file under windows

switch from [clabango "0.5"] to [selmer "0.3.2"]. The project is created with Luminus on Window system.

got error:

java.lang.Exception: temaplate: "/C:/Documents%20and%20Settings/xxx/git/eService/src/bdst/views/templates//index.html" not found

My guess is the "/" at the beginning of the path may cause the problem. And it may be something to do with the parse/render-file and util/resource-path.

Hope it's helpful.

Thanks,
Phil

`render-file` from non-resource path

Is there a way to render using a template not in the resources-path of the app? If not, and if it is not considered to be a bad practice, I'd love to see a way to configure at run-time a base path outside of the app's deployment via a config file.

PS: Will be happy to submit a patch but I don't pride myself in my clojure skills.
PPS: Thanks for all the wonderful, open-source projects!

extends using custom :tag-open

extends tag does not follows custom tags settings.

In render

{:tag-open \[
     :tag-close \]
     :filter-open \[
     :filter-close \]}

In template

[% extends "catalogo/views/templates/base.html" %]

throws:

java.lang.Exception: unrecognized tag: :extends - did you forget to close a tag?

escaping nested references

Would it be possible to accommodate keywords that contain dots? I have several Datomic entities that have these and it would be nice to reference them directly instead of creating intermediaries. Maybe with an alternate syntax from {{ / }}? {{. my.item/sub.thing .}} ?

verbatim doesn't work

Function rend-file will throw exception when the html file contain jquery template which in verbatim block. Function render seems no this problem.

How to accomplish dynamic extends?

In a child template:

{% extends "cdi/views/templates/base.html" %}
{% block content %}
{% ifequal "cdi/views/templates/base.html" layout %} is equal! {% endifequal %}
{% endblock %}

Renders:
is equal!


{% extends layout %}
{% block content %}
{% ifequal "cdi/views/templates/base.html" layout %} is equal! {% endifequal %}
{% endblock %}

Throws:
No implementation of method: :make-reader of protocol: #'clojure.java.io/IOFactory found for class: nil

Is there a way to do something like?

(selmer.parser/render-file "/my/child.html" {parent: "/my/parent.html"} )

Handling functions/lambdas in template data

Is there support for functions/lambdas in template data, like in mustache?

In mustache it is possible to do

Template:

{{#wrapped}}
  {{name}} is awesome.
{{/wrapped}}

Data:

{
  "name": "Willy",
  "wrapped": function() {
    return function(text) {
      return "<b>" + render(text) + "</b>"
    }
  }
}

Result is <b>Willy is awesome.</b>

Using included templates, problem passing in string variables

Using the latest version of Selmer, 0.7.4, and seeing problems with the include tag. Let's say I have a Selmer template like so, called img-template.html

<div>
  <img src="{{img-filename}}" class="img-responsive">
</div>

<div>
  <p>{{img-caption-1}}
  <em>{{img-caption-2}}</em>
  </p>
</div>

I want to use that small template in a larger template. In the larger template, if I use the Selmer include tag and its with option, e.g.
{% include "templates/img-template.html" with img-filename="picture.jpg" img-caption-1="The quick brown fox" img-caption-2="jumped over the lazy dog." %}
...then what I see, strangely, is that all spaces get removed from the two text/caption string/variables, so the text becomes unreadable. Wrapping the <p> with Selmer's {% safe %} tag doesn't help.

If I wrap the include inside a with tag, that also doesn't work - none of the context variables seem to be available to the included template, they're rendered blank.

{% with  img-filename="picture.jpg" img-caption-1="The quick brown fox" img-caption-2="jumped over the lazy dog." %}
{% include "templates/img-template.html" %}
{% endwith %}

Suggestions? Thanks in advance for any help.

template not found when deployed to a container inside a WAR

I get the following exception (trimmed) when deployed via WAR file to WildFly 8.1 (I'm testing Immutant 2).

Exception handling request to /myapp/: java.lang.Exception: template: "/content/myapp.war/WEB-INF/lib/myapp-0.1.0-standalone.jar/templates/500.html" not found
        at selmer.util$check_template_exists.invoke(util.clj:149) [myapp-0.1.0-standalone.jar:]
        at selmer.parser$render_file.doInvoke(parser.clj:96) [myapp-0.1.0-standalone.jar:]

The problem seems similar to #18 - render_file doesn't barf but check_template_exists does.

Based on the exception message it seems that in-jar? fails as there's no ! in the path.

I've verified that the file is there inside the JAR (inside the WAR) where Selmer appears to be checking.

out of memory error on not closed curly bracket

Minimal example is a template file containing nothing but an opening curly bracket:

$ cat resources/test.selmer
{

It does not occur when using render with "{"" as first argument..

Error:

lein repl
user=> (require '[selmer.parser :refer [render-file]])
user=> (render-file "test.selmer" {})

OutOfMemoryError Java heap space  java.util.Arrays.copyOf (Arrays.java:2367)
user=> (use 'clojure.stacktrace)
nil
user=> (print-stack-trace *e)
java.lang.OutOfMemoryError: Java heap space
 at java.util.Arrays.copyOf (Arrays.java:2367)
    java.lang.AbstractStringBuilder.expandCapacity (AbstractStringBuilder.java:130)
    java.lang.AbstractStringBuilder.ensureCapacityInternal (AbstractStringBuilder.java:114)
    java.lang.AbstractStringBuilder.append (AbstractStringBuilder.java:415)
    java.lang.StringBuilder.append (StringBuilder.java:132)
    java.lang.StringBuilder.append (StringBuilder.java:128)
    selmer.util$read_tag_content.invoke (util.clj:88)
    selmer.template_parser$insert_includes$fn__1019.invoke (template_parser.clj:42)
    selmer.template_parser$insert_includes.invoke (template_parser.clj:38)
    selmer.template_parser$preprocess_template.doInvoke (template_parser.clj:206)
    clojure.lang.RestFn.invoke (RestFn.java:410)
    selmer.parser$parse_file.invoke (parser.clj:211)
    selmer.parser$parse.doInvoke (parser.clj:234)
    clojure.lang.RestFn.invoke (RestFn.java:442)
    selmer.parser$render_file.doInvoke (parser.clj:99)
    clojure.lang.RestFn.invoke (RestFn.java:425)
    vcardprinter.core$eval2402.invoke (form-init3015907593193541128.clj:1)
    clojure.lang.Compiler.eval (Compiler.java:6619)
    clojure.lang.Compiler.eval (Compiler.java:6582)
    clojure.core$eval.invoke (core.clj:2852)
    clojure.main$repl$read_eval_print__6588$fn__6591.invoke (main.clj:259)
    clojure.main$repl$read_eval_print__6588.invoke (main.clj:259)
    clojure.main$repl$fn__6597.invoke (main.clj:277)
    clojure.main$repl.doInvoke (main.clj:277)
    clojure.lang.RestFn.invoke (RestFn.java:1096)
    clojure.tools.nrepl.middleware.interruptible_eval$evaluate$fn__1741.invoke (interruptible_eval.clj:56)
    clojure.lang.AFn.applyToHelper (AFn.java:159)
    clojure.lang.AFn.applyTo (AFn.java:151)
    clojure.core$apply.invoke (core.clj:617)
    clojure.core$with_bindings_STAR_.doInvoke (core.clj:1788)
    clojure.lang.RestFn.invoke (RestFn.java:425)
    clojure.tools.nrepl.middleware.interruptible_eval$evaluate.invoke (interruptible_eval.clj:41)
nil

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.