Skip to content

ruroru/majavat

Repository files navigation

Majavat

A templating engine for Clojure

Installation

Add majavat to dependency list

[org.clojars.jj/majavat "1.16.0"]

Usage

Rendering templates

(:require
  [jj.majavat :as majavat]
  [jj.majavat.renderer.sanitizer :refer [->Html]])

(def render-fn (majavat/build-renderer "index.html"))
;; or build html renderer, which will sanitize input
(def html-render-fn (majavat/build-html-renderer "index.html"))

(render-fn {:user "jj"})
(html-render-fn {:user "jj"})

Additional options can be passed with

(def render-fn (majavat/build-renderer "index.html" {:cache?     false
                                                     :pre-render {:key "value"}
                                                     :filters    {:reverse (fn [value args]
                                                                             (string/reverse value))}
                                                     :renderer   (->StringRenderer {:sanitizer (->Html)})}))

(render-fn {:user "jj"})

All supported options:

Option Default Value Supported Options
renderer StringRenderer Any Renderer implementation
cache? true true, false
template-resolver ResourceResolver TemplateResolver
pre-render {} Map
filters {} Map

Creating templates

Inserting value

Rendering file.txt with content

Hello {{ name }}!
(def render-fn (build-renderer "file.txt"))
(render-fn {:name "world"}) ;; => returns Hello world!

or with a filter

Hello {{ name | upper-case }}!
(def render-fn (build-renderer "file.txt"))

(render-fn {:name "world"}) ;; => returns Hello WORLD!
Built In Filters
Filter Type Example
append String "hello" | append " world" → "hello world"
capitalize String "hello world" → "Hello world"
int String "123" → 123
long String "123" → 123L
lower-case String "HELLO WORLD" → "hello world"
prepend String "hello" | prepend " world" → "world hello"
slugify String "Foo Bar" → "foo-bar"
title-case String "hello world" → "Hello World"
trim String " hello " → "hello"
upper-case String "hello world" → "HELLO WORLD"
upper-roman String "iv" → "IV"
name Keyword :name → "name"
abs Number -1 → 1.0
ceil Number 1.99 → 2
dec Number 5 → 4
file-size Number 2048 → "2 KB"
floor Number 1.4 → 1.0
inc Number 5 → 6
round Number 1.99 → 2
default "foo" nil nil → "foo"
date "yyyy" LocalDate Instance of LocalDate → "2025"
date "yyyy" LocalDateTime Instance of LocalDateTime → "2025"
date "hh/mm" LocalTime Instance of LocalTime → "11/11"
date "hh/mm" "Asia/Tokyo" Instant Instance of Instant → "11/11"
date "hh/mm" "Asia/Tokyo" ZonedDateTime Instance of ZonedDateTime → "11/11"
User Provided filters Filters

Assoc :filter to option map, when building renderer, with this value

{:quote (fn [value args]
          (format "\"%s\" - %s" value (first args)))}

Conditionals

Rendering input file with content:

"Hello {% if name %}{{name}}{% else %}world{% endif %}!"
(def render-fn (build-renderer "input-file"))

(render-fn {:name "jj"}) ;; returns "Hello jj!"
(render-fn {}) ;; returns "Hello world!"

or

"Hello {% if not name %}world{% else %}jj{% endif %}!"
(def render-fn (build-renderer "input-file"))

(render-fn {:name "foo"}) ;; returns "Hello jj!"
(render-fn {}) ;; returns "Hello world!"

Looping

for

Rendering input file with content:

{% for item in items %}
- {{ item }} is {{ loop.index }} of {{ loop.total }}
{% endfor %}
(def render-fn (build-renderer "input-file"))

(render-fn {:items ["Apple" "Banana" "Orange"]}) ;; returns "- Apple is 0 of 3\n- Banana is 1 of 3\n- Orange is 2 of 3"

The loop context provides access to:

loop.total - total number of items in the collection

loop.index - current 0-based index position

loop.first? - true only for the first item

loop.last? - true only for the last item

each

In situations where loop context is not needed, each can be used

{% each item in items %}
- {{ item }}
{% endeach %}
(def render-fn (build-renderer "input-file"))

(render-fn {:items ["Apple" "Banana" "Orange"]}) ;; returns "- Apple\n- Banana\n- Orange"

Including template

file.txt content

foo

Rendering input file with content:

included {% include "file.txt" %}
(def render-fn (build-renderer "input-file"))

(render-fn {}) ;; returns "included foo"

Setting value

You can set value within a template via:

hello {% let foo = "baz" %}{{ foo }}{% endlet %}
(def render-fn (build-renderer "input-file"))

(render-fn {}) ;; returns "hello baz"

or

hello {% let foo = bar %}{{ foo.baz }}{% endlet %}
(def render-fn (build-renderer "input-file"))

(render-fn {:bar {:baz "baz"}}) ;; returns "hello baz"

Extending template

file.txt content

foo
{% block %}
baz

Rendering input file with content:

{% extends "file.txt" %}
bar
(def render-fn (build-renderer "input-file"))

(render-fn {}) ;; returns "foo\nbar\nbaz"

Comments

input-file with content

foo{# bar baz #}
(def render-fn (build-renderer "input-file"))

(render-fn {}) ;; returns "foo"

csrf

CSRF token can be added via

{% csrf-token %}

and when rendering file :csrf-token has to be provided

(def render-fn (build-renderer "input-file"))

(render-fn {:csrf-token "foobarbaz"}) ;; returns <input type="hidden" name="csrf_token" value="foobarbaz"> 

Query string

input-file with content

/foo{% query-string foo %}
(def render-fn (build-renderer "input-file"))

(render-fn {:foo {:count 2}}) ;; returns "/foo?count=2"

Now

input-file with content

default format {% now %}
formatted {% now "yyyy-MM-dd" %}
formatted with tz {% now "yyyy-MM-dd hh:mm " "Asia/Tokyo" %}
(def render-fn (build-renderer "input-file"))

(render-fn {}) ;; returns "default format 2011/11/11 11:11\nformatted 2011-11-11\ntormatted with tz 2011-11-11 23:11"

Verbatim

input-file with content

{% verbatim %}foo{{bar}}{%baz%}{#qux#}quux{% endverbatim %}
(def render-fn (build-renderer "input-file"))

(render-fn {}) ;; returns "foo{{bar}}{%baz%}{#qux#}quux"

Renderer Protocol

Configuration

Supported options:

:sanitizer - Input sanitization strategy

render

Renders a template using the provided context.

  • template - template AST
  • context - Map of variables for template interpolation

Returns - Rendered output

Built-in Implementations

StringRenderer

Returns rendered output as a String clojure

(->StringRenderer {:sanitizer (->Html)})

InputStreamRenderer

Returns rendered output as an InputStream for streaming large content

(->InputStreamRenderer {:sanitizer (->Json)})

PartialRenderer

Returns a partially rendered AST.

(->PartialRenderer {})

TemplateResolver

The TemplateResolver protocol provides a uniform interface for accessing template content from different sources.

Protocol Methods

open-reader

Returns reader for that template, or nil if not found.

(open-reader "/templates/header.html")

template-exists?

Check if template exists at a path.

(template-exists? resolver "/templates/footer.html") ;; => true

Built-in Implementations

  • ResourceResolver (default) - Reads from classpath
  • FsResolver - Reads from filesystem

Sanitizer

Sanitizer protocol provides a way to sanitize and cleanup values.

Usage

(sanitize (->Html) "<foo>bar</baz>") ;; => &lt;foo&gt;bar&lt;/baz&gt;

Built-in Implementations

  • Html - implementation for html pages
  • Json - implementation for Json

Available Extensions

License

Copyright © 2025 ruroru

This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which is available at https://www.eclipse.org/legal/epl-2.0/.

This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version, with the GNU Classpath Exception which is available at https://www.gnu.org/software/classpath/license.html.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •