Safe Haskell | None |
---|---|
Language | Haskell2010 |
Language.Ginger
Description
Jinja is a dynamically-typed template language designed for generating HTML and text output.
Ginger2 provides most of the Jinja template language, as much as that makes sense in a Haskell host application.
We do, however, avoid some of the most blatant Pythonisms, especially where we felt the features in question were more of an accidental result of binding template constructs to Python constructs.
We also add some optional features that are absent from the Python implementation, but that we felt useful and worth adding.
Template Syntax
Minimal Example
<!DOCTYPE html> <html> <head> <title>{{ title }}</title> </head> {# This is a comment. Comments are removed from the output. #} <body> <menu id="nav-main"> {% for item in navigation %} <li><a href="{{ item.url }}">{{ item.label }}</a></li> {% endfor %} </menu> <div class="layout-content-main"> <h1>{{ title }}</h1> {{ body }} </div> </body> </html>
Comments
Comments are delimited by {#
... #}
markers.
It is not currently possible to nest comments.
Interpolation
The {{
and }}
markers delimit interpolation: anything between these
markers is interpreted as an expression, and the result of evaluating that
expression is written into the template output at this point.
The following expression constructs are available:
Literals
String Literals
String literals can be written with single quotes:
'Hello, world!'
...or double quotes:
"Hello, world!"
Quotes inside string literals can be escaped using backslashes:
"Hello, \"world!\""
Numeric Literals
Integers can be written in decimal:
23
Floats can be written as decimal fractions:
23.5
...or in scientific notation:
0.25e10
Booleans
Two boolean literals exist: true
and false
. For backwards compatibility
with Jinja, True
and False
are accepted as alternative spellings.
None
The literal none
designates the NULL
object (approximately equivalent
to ()
or Nothing
in Haskell). For backwards compatibility with Jinja,
None
is accepted as an alternative spelling.
Variables
Variables (built-in, passed in by the host application, or defined within the template itself) are written as barewords. Their names must follow the rules for identifiers:
- They must start with an alphabetic ASCII character or an underscore.
- All subsequent characters must be alphanumeric ASCII characters or underscores.
Lists
List literals are written as comma-separated lists between square brackets, like in most programming languages:
["foo", "bar", "baz"]
Because Ginger is dynamically typed, lists are heterogenous, so the following is perfectly fine:
["foo", 23, none, true, 1.5]
Unlike Jinja, Ginger does not distinguish lists from tuples; there is only one list type, and all lists in Ginger are immutable.
Dictionaries
Dictionaries ("dicts") are key-value association containers. They are written as comma-separated lists of dot-separated key/value pairs between curly braces, like so:
{ "foo": 23, "bar": 42 }
Keys and values are both interpreted as expressions; this means that string
keys must be written as string literals, not barewords - barewords are
interpreted as variables, so for example { foo: "bar" }
will not make a
dictionary key "foo", but rather try to look up a variable named foo
in
the current scope, and use its value as the key.
Values can be anything; keys, however, must be scalars, that is, strings,
numbers, booleans, or none
.
Dot Member Access
Members of dictionaries and (some) native objects can be obtained using dot access syntax, like so:
someDictionary.foo
Note that the member name is written as a bareword; these are *not*
interpreted as variables, and quoting member names in dot syntax like string
literals is a syntax error.
Dot syntax can also be used to access built-in methods of various types of
values; for example, the string
type provides a method upper()
, which
returns the original string converted to uppercase, so you can write:
"Hello, world!".upper()
...which will yield the string "HELLO, WORLD!"
.
Dot syntax will prioritize *attributes* (built-in properties) over *items* (data items stored in a dictionary).
Indexing
Members of dictionaries, as well as elements of lists and characters in a string, can be accessed using an index between square brackets, as follows.
Get an item from a list:
someList[3]
Get a member/item from a dictionary:
someDict["foo"]
Get the n-th character from a string:
someString[3]
Note that strings and lists will only accept integer indices, while dictionaries and native objects may accept arbitrary scalars.
Note further that, unlike dot syntax, the thing between square brackets is evaluated as an expression, so if you want to access a string key in a dictionary, it must be written as a quoted string literal. The following is an example of accessing a dictionary member through a variable key:
someDict[foo]
This will *not* look up a key named "foo", but rather, try to find a
variable named "foo" in the current scope, get its value, and use that as a
key to look up in someDict
.
Indices into strings and lists are zero-based, that is, [0]
yields the
first element of a list, or the first character of a string.
Square bracket syntax will prioritize *items* (data items stored in a container) over *attributes* (built-in methods associated with a value).
Slicing
Square bracket syntax can also be used to get sub-ranges from a list or
string, using the colon (:
) to separate the start and end of the desired
range. Negative indices imply counting from the end of the range; absent
indices refer to the start or end of the original sequence respectively.
Examples:
"Hello, world!"[1:2]
Get a substring of length 2, starting at the second character: "el".
"Hello, world!"[:2]
Get a substring of length 2, starting at the beginning of the string: He.
"Hello, world!"[-2:]
Get a substring up to the end of the string, starting two characters before the end: "d!".
"Hello, world!"[2:-2]
Get a substring from the third character up to 2 characters before the end of the string: "llo, worl".
Unary Operators
Two unary operators are available, both written in prefix notation:
not
provides boolean negation-
provides numeric negation
Binary Operators
All binary operators are written in infix notation. In order of precedence, the following classes of operators are available:
Boolean
and
- boolean ANDor
- boolean OR
Boolean Negation
Not a binary operator, but listed here to indicate precedence; see above.
Comparative and Membership
==
- equals!=
- not-equals<
- less-than; works on numbers as well as strings>
- greater-than; dito<=
- less-than-or-equal; dito>=
- greater-than-or-equal; ditoin
- membership test
Test
Not a real operator, but listed here to indicate precendence. See below for details on tests.
Concatenation
~
- String concatenation. If non-string arguments are given, they are converted to string. However, if either argument is encoded text, then the other argument will also be encoded first.
Additive
These operations are numeric. If both arguments are integers, integer operations will be used, otherwise, both arguments will be converted to float.
+
- Numeric addition.-
- Numeric subtraction.
Multiplicative
These operations are numeric. If both arguments are integers, integer operations will be used, otherwise, both arguments will be converted to float, unless specified otherwise.
*
- Numeric multiplication./
- Numeric division.%
- Integer modulus. Both arguments will be converted to int.//
- Integer division. Both arguments will be converted to int.
Power
Numeric operation; if both arguments are integers, integer arithmetic will be used, otherwise, both arguments will be cast to float.
**
Exponentiation:a ** b
means "a to the power of b".
Member access, filter, call
Not operators, but listed here to indicate precedence. See respective sections for details.
Ternary Operator
The ternary operator consists of the keywords if
and else
, and works
much like in Python:
"foo" if condition else "bar"
...means "evaluate to 'foo' if condition
holds, otherwise, evaluate to
'bar'".
Procedure Calls
Procedure calls are written like in most imperative languages, appending a comma-separated list of arguments between parentheses to the procedure to be called.
To call a procedure foo
with two arguments, 1
and 2
:
foo(1, 2)
Procedures may have optional arguments (which can be left out, using a default value instead), and arguments can also be given by name instead of positionally, e.g.:
foo(bar=23)
This calls procedure foo
with the bar
argument set to 23
, and any other
arguments left at their defaults.
Some objects can be called as procedures while also offering other APIs; an
exampe is the loop
object that is available within recursive for
loops
(see below), which exposes a number of fields providing information about
the state of the iteration, but can also be called as a procedure to recurse
into a deeper iteration level.
Filters
Filter syntax is a convenient syntactic alternative to procedure call syntax. It looks like this:
foo|center(10)
This will call the center
filter, passing the value of foo
as the first
argument, and 10
as a second argument. Thus, it is equivalent to:
center(foo, 10)
If no additional arguments are passed, the parentheses can be omitted:
foo|capitalize
This syntax is particularly useful when chaining multiple filters, e.g.:
foo|default('n/a')|lower|trim
One filter, default
, and its alias d
, cannot be implemented as
procedures, because it must inspect its argument as an unevaluated
expression - evaluating it before passing it to the filter would cause a
"Not In Scope" error when the argument isn't defined, making the entire
filter moot. Hence, this filter is only available through filter syntax, not
as a procedure.
Tests
Tests are written using the is
keyword, much like a binary operator;
however, they are special-cased in the language, for two reasons:
* They can inspect their argument unevaluated (e.g., the defined
test will
do this to determine whether the argument is in scope).
* Some tests have the same name as a procedure or filter, but their
functionality is different in a text context. E.g., lower
, when used as
a filter of procedure will convert its argument to lowercase, but when
used as a test, it will instead check whether the argument is lowercase.
In Jinja, tests may only occur in contexts where a boolean condition is
expected (e.g., the ternary operator, or an {% if ... %}
statement);
Ginger allows tests in any expression context, and treats the result as a
boolean value. For example, the following would be perfectly fine in Ginger,
but (probably) not work in Jinja:
{% set answers = { false: "odd", true: "even" } %} Foo is {{ answers[foo is even] }}.
Flow Control Statements
All statements are delimited using statement brackets: {%
... %}
.
{% filter %}
Apply a filter (see above, filter expressions) to a block of template code.
{% filter 'capitalize' %} Hello, world! {% endfilter %}
...will output:
HELLO, WORLD!
The filter itself may be specified as an expression (e.g. {% filter
capitalize %}
), or as a string that will be resolved as a variable (e.g.
{%filter
. Both are equivalent.capitalize
%}
Additional arguments may be passed just like with filter expression syntax:
{% filter center(100) %} Hello, world! {% endfilter %}
{% for %}
Loops over a collection (list or dictionary).
In its simplest form, it looks like this:
{% for user in users %} {{ user }} {% endfor %}
This will iterate over the elements of a list in the variable users
,
binding the current element to the variable user
within the scope of the
iteration body.
Inside of a for-loop block, you can access some special variables:
loop.index
- The current iteration of the loop. (1 indexed)loop.index0
- The current iteration of the loop. (0 indexed)loop.revindex
- The number of iterations from the end of the loop (1 indexed)loop.revindex0
- The number of iterations from the end of the loop (0 indexed)loop.first
- True if first iteration.loop.last
- True if last iteration.loop.length
- The number of items in the sequence.loop.cycle
- A helper function to cycle between a list of sequences. See the explanation below.loop.depth
- Indicates how deep in a recursive loop the rendering currently is. Starts at level 1loop.depth0
- Indicates how deep in a recursive loop the rendering currently is. Starts at level 0loop.previtem
- The item from the previous iteration of the loop. Undefined during the first iteration.loop.nextitem
- The item from the following iteration of the loop. Undefined during the last iteration.loop.changed(val)
- True if previously called with a different value (or not called at all).
While there are no continue
or break
statements to alter the flow of
a loop from within, it is possible to filter the iteree, by adding an if
construct to the loop header:
{% for user in users if user.username is not none %} {{ user.username }} {% endfor %}
The same effect can be achieved by wrapping the loop body in an if
statement; however, filtering the loop iteree has the advantage that the
loop
variables will count correctly.
E.g., given a list of users like so:
[ { "username": "tdammers" }, { "username": none }, { "username": "jdoe" } ]
This template will work correctly:
{% for user in users if user.username is not none %} {{ loop.index }}. {{ user.username }} {% endfor %}
...outputting:
1. tdammers 2. jdoe
Whereas this template:
{% for user in users %} {% if user.username is not none %} {{ loop.index }}. {{ user.username }} {% endif %} {% endfor %}
...would output:
1. tdammers 3. jdoe
An optional else
branch can be added to a loop, which will be used when
the iteration body has not been used at all (because the list was empty, or
because all items were filtered out):
{% for user in users %} <p>{{ user.username }}</p> {% else %} <p class="no-results">No users found.</p> {% endfor %}
Loops can also be used recursively. For this, two things are required:
- The loop needs to be declared as being recursive:
{% for ... in ... recursive %}
- The
loop
variable must be called with an appropriate iteree in order to recurse into it.
Example:
{% for branch in tree recursive %} <section> <h3>{{ branch.name }}</h3> <p>{{ branch.description }}</p> {% if "children" in branch %} {{ loop(branch.children) }} {% endif %} {% endfor %}
Please note that assignments in loops will be cleared at the end of the iteration and cannot outlive the loop scope.
To work around this, consider using namespace objects, created using the
namespace
procedure. However, if all you need to do is check the previous
and/or next item in the iteration, you can simply use loop.prev
and
loop.next
.
{% if %}
Conditionals. These work much like if then else in a typical imperative language. Three forms exist:
Simple if
with no else
:
{% if isGreeting %} Hi! {% endif %}
if
/ else
:
{% if isArriving %} Hello! {% else %} Goodbye! {% endif %}
if
elif
else
:
{% if isMorning %} Good morning! {% elif isEvening %} Good night! {% else %} Good day! {% endif %}
{% set %}
Assignment. There are two forms of the set
statement.
First form: assign an expression.
{% set name = "tdammers" %}
Second form: assign a block of encoded output.
{% set name %} tdammers {% endset %}
Variables set using either construct will be available within the current
scope; includes, imports, template inheritance, the {% with %}
statement,
and {% for %}
loops can affect scope.
{% with %}
Creates a nested scope. Any variables set or overwritten within the nested scope will only be reflected inside the nested scope; any variables from the containing scope will be available until overridden, and will revert back to their previous values when leaving the inner scope.
{% set foo = "A" %} {{ foo }} {% with %} {{ foo }} {% set foo = "B" %} {{ foo }} {% endwith %} {{ foo }}
Will yield:
A A B A
{% macro %}
Defines a macro. Macros are reusable bits of Jinja code, akin to procedures.
In fact, macros and procedures are represented as the same thing internally
in Ginger, and you can call procedures as macros (using the {% call %}
statement), and macros as procedures or filters (using expression-level
procedure call or filter syntax).
A macro definition looks like this:
{% macro userInfo(user, extraInfo=none) %} <section class="userinfo"> <h3>{{ user.username }}</h3> <p>Role: {{ user.role }}</p> <p>Status: {% if user.active %}active{% else %}inactive{% endif %} {% if extraInfo is not none %} {{ extraInfo }} {% endif %} </section> {% endmacro %}
Macros can be declared to take any number of arguments, the values of which will be bound to variables in the scope of the macro's body. Defaults can be given for each argument, making it optional; it is considered good practice to list required arguments (without defaults) before optional arguments, so that there is no ambiguity when arguments are given positionally (i.e., without explicit argument names).
Inside a macro body, a special variable, caller
, is made available if the
macro was invoked through a {% call %}
statement. In that case, caller
will be a procedure that outputs the body of the {% call %}
statement that
was used to invoke the macro.
{% call %}
Calls a macro (see above) with an additional body, which is passed through
the magic caller()
procedure.
Given this macro definition:
{% macro sectionize(title) %} <section> <h1>{{ title }}</h1> {{ caller() }} </section> {% endmacro %}
...the following will call that macro with a caller()
body:
{% call sectionize("Some Section") %} Lorem ipsum dolor sit amet. {% endcall %}
This will render as:
<section> <h1>Some Section</h1> Lorem ipsum dolor sit amet. </section>
{% include %}
{% include "foo.html" %}
This will load the template "foo.html", and insert its output at this position, as if the template source were pasted in.
By default, this implies that the included template will have access to the
scope of the location where it is included; you can change this by adding
without context
to the include statement:
{% include "foo.html" without context %}
It is also valid to write with context
, but since that is the default,
this will do nothing.
Missing templates are an error; if you want to be lenient, you can add
ignore missing
to the include, which ignore such errors. If both are given,
ignore missing
must come before with
/ without context
.
{% include "foo.html" ignore missing without context %}
{% import %}
Works much like include
, however, there are some key differences:
import
will not inject any output from the imported template. The imported template will still be evaluated in full, and any macros and top-level variables it defines (using{% macro %}
and{% set %}
) will be exported, but any output it generates will be discarded.import
defaults towithout context
, i.e., it does not have access to the scope in which the import statement appears.- Because the main purpose of
import
is to pull top-level definitions into the importing scope,import
supports syntax flavors that import specific exports (macros / variables) selectively, as well as one that binds the exports to a single variable acting as a quasi-namespace.
To import a template's exports wholesale:
{% import "util.html" %}
To bind an entire template's export to a quasi-namespace dictionary:
{% import "util.html" as util %}
To import specific exports selectively:
{% from "util.html" import abbreviateUsername, decorateUser %}
To import specific exports, renaming them to aliases:
{% from "util.html" import abbreviateUsername as abbrev, decorateUser as deco %}
{% extends %}
Used for template inheritance:
{% extends "parent.html" %}
Indicates that the current template "extends" the template
"parent.html". To render the current template, Ginger will take any blocks
(see below under "{% block %}
") from the current template, and inject
them into the corresponding blocks of the parent template.
Child templates should not directly output anything themselves; they should
only override blocks ({% block %}
) and top-level variables ({% set %}).
{% block %}
Blocks are overridable subsections of a template. The {% block %}
statement is used both to define a block and to override it: the first
template in an inheritance chain to use a block name defines it, and
determines where it appears in the output; subsequent templates in the
chain can override its contents, but not where it appears in the output.
Maybe the most common use case for this is to have a "skeleton" template that defines the overall layout of a web page, and a number of child templates that override those parts that are specific to their respective use cases.
Example:
(skeleton.html
)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>{% block "title" %}Unnamed Page{% endblock %}</title> <link rel="stylesheet" href="/static/style.css"> </head> <body> {% include "mainnav.html" %} <div class="maincontent"> {% block content %} Nothing to see here. {% endblock } </div> </body> </html>
(userlist.html
)
{% extends "skeleton.html" %} {% block "title" %}Users{% endblock %} {% block "content" %} <h1>Users</h1> <ul> {% for user in users %} <li>{{ user.username }}</li> {% endfor %} </ul> {% endblock %}
List Of Builtin Globals
These are available in Jinja, and work (mostly) the same in Ginger.
abs()
Arguments:
value
:number
(required)
Return type: number
Absolute of a number.
attr()
Arguments:
value
:dict
(required)attrName
:string
(required)
Return type: any
Get a named attribute from a dict
or dict-like object.
Unlike []
or dot member access, this will only look at attributes, not items.
batch()
Arguments:
value
:any
(required)linecount
:int
(required) - Number of items per chunk. Unlimited if not specified.fill_with=none
:any
- Filler to pad shorter chunks with. If not specified, don't pad.
Return type: list
Split up a list into chunks of length linecount
.
capitalize()
Arguments:
value
:string
(required)
Return type: string
Convert value
to title case.
center()
Arguments:
value
:string
(required)width=80
:int
fillchar=" "
:string
Return type: string
Pad string on both sides to center it in the given space
count()
Alias for length
dictsort()
Arguments:
value
:dict
(required)case_sensitive=false
:bool
by="key"
:string
- One of 'key', 'value'reverse=false
:bool
Return type: list
Sort a dict, returning a list of key-value pairs.
e()
Alias for escape
escape()
Arguments:
value
:any
(required)
Return type: encoded
Escape the argument.
even()
Arguments:
value
:int
(required)
Return type: bool
Check if value
is an even number
filesizeformat()
Arguments:
value
:number
(required)binary=false
:bool
- If set, use binary units (kiB, MiB, ...) instead of decimal (kB, MB, ...)
Return type: string
Format value
as a human-readable file size.
first()
Arguments:
value
:list
,string
, orbytes
(required)
Return type: any
Get the first element from a list, or the first character from a string.
float()
Arguments:
value
:any
(required)default=0.0
:any
Return type: float
Convert value
to float.
If default
is given, values that cannot be converted to floats will be replaced with this default value.
int()
Arguments:
value
:any
(required)default=0
:any
base=10
:any
Return type: int
Convert value
to int.
If default
is given, values that cannot be converted to integers will be replaced with this default value.
items()
Arguments:
value
:dict
(required)
Return type: list
Convert a dict to a list of its elements without the keys.
join()
Arguments:
iterable
:list
(required)d=""
:string
- Default value to use to replace empty elementsattr=none
:any
- If given, an attribute to pick from each element
Return type: string
Join an iterable into a string
json()
Alias for tojson
last()
Arguments:
value
:list
,string
, orbytes
(required)
Return type: any
Get the last element from a list, or the last character from a string.
length()
Arguments:
value
:string
,list
, ordict
(required)
Return type: int
Get the length of a string, list, or dictionary.
list()
Arguments:
value
:any
(required)
Return type: list
Convert value
to a list, if possible
lower()
Arguments:
value
:string
(required)
Return type: string
Convert value
to lowercase.
map()
Alias for builtins:map
max()
Arguments:
value
:list
(required)case_sensitive=false
:bool
- Treat upper and lowercase strings as distinct.attr=none
:string
- Get the object with the max value of this attribute.
Return type: any
Get the maximum value from a list
min()
Arguments:
value
:list
(required)case_sensitive=false
:bool
- Treat upper and lowercase strings as distinct.attr=none
:string
- Get the object with the min value of this attribute.
Return type: any
Get the minimum value from a list
namespace
odd()
Arguments:
value
:int
(required)
Return type: bool
Checks if value
is an odd number.
random()
Arguments:
value
:list
(required)
Return type: any
Pick a random element from a list
reject()
Arguments:
value
:list
(required)filter=none
:string
,filter
,test
, orprocedure
- A filter or test to apply to each element to determine whether to reject it or not.attribute=none
:string
, ornone
- If specified, the name of an attribute to extract from each element for testing.
This argument can only be passed by keyword, not positionally.
Return type: list
Reject by a test or filter, and/or an attribute.
replace()
Arguments:
value
:string
(required)old
:string
(required) - String to search fornew
:string
(required) - Replacementcount=none
:any
- Maximum number of replacements.
Return type: string
String search-and-replace.
reverse()
Arguments:
value
:list
, orstring
(required)
Return type: list
, or string
Reverse a list or string
round()
Arguments:
value
:float
(required)precision=0
:int
method="common"
:string
- One of 'common', 'ceil', 'floor'.
Return type: int
, or float
Round a floating-point value.
safe()
Arguments:
value
:string
(required)
Return type: encoded
Mark value
as pre-encoded HTML.
select()
Arguments:
value
:list
(required)filter=none
:string
,filter
,test
, orprocedure
- A filter or test to apply to each element to determine whether to select it or not.attribute=none
:string
, ornone
- If specified, the name of an attribute to extract from each element for testing.
This argument can only be passed by keyword, not positionally.
Return type: list
Select by a test or filter, and/or an attribute.
sort()
Arguments:
value
:list
(required)reverse=false
:bool
case_sensitive=false
:bool
attribute=none
:any
Return type: list
split()
Arguments:
value
:string
(required)sep=none
:string
maxsplit=none
:int
- Maximum number of splits. Unlimited if not specified.
Return type: list
Split a string by a separator.
string()
Arguments:
value
:any
(required)
Return type: string
Convert argument to string
sum()
Arguments:
value
:list
(required)attr=none
:string
- Use this attribute from each object in the liststart=none
:int
- Start at this offset into the list
Return type: any
Get the sum of the values in a list
title()
Arguments:
value
:string
(required)
Return type: string
Convert value
to title case.
tojson()
Arguments:
value
:any
(required)indent=none
:any
Return type: string
Convert value
to JSON
upper()
Arguments:
value
:string
(required)
Return type: string
Convert value
to uppercase.
wordcount()
Arguments:
value
:string
(required)
Return type: int
Counts words in value.
List Of Extension Globals
These are not available in Jinja
date()
Alias for dateformat
dateformat()
Arguments:
date
:string
, orlist
(required) - May be given as a formatted date, or as a list of[ year, month, day, hours, minutes, seconds, timezone ]
. Partial lists will be padded with appropriate defaults.format="%c"
:string
tz=none
:string
,int
, orlist
- Time zone. May be given as a string specifying an offset (±HHMM), an integer offset in minutes, or a list containing 3 elements: offset in minues (int), summer-only (bool), and timezone name (string).locale=none
:any
- Select a locale. Not yet implemented, ignored.
Return type: string
Format a date/time value.
Format strings follow the specification found here: Date.Time.Format.formatTime
Accepted input formats:
%Y-%m-%dT%H:%M:%S%Q%Z
(2025-11-28T23:54:32.1234UTC)%Y-%m-%d %H:%M:%S%Q
(2025-11-28 23:54:32.1234UTC)%Y-%m-%d %H:%M:%S%Q%z
(2025-11-28 23:54:32.1234+0100)%Y-%m-%d %H:%M:%S%Q%Z
(2025-11-28 23:54:32.1234UTC)%Y-%m-%d
(2025-11-28)
help()
Arguments:
value
:any
(required)
Return type: dict
Get documentation for the given value, if available.
Module 'regex'
regex.match()
Arguments:
regex
:any
(required)haystack
:any
(required)opts=""
:any
Return type: list
Match a regular expression against a string.
Returns an array where the first element is the entire match, and subsequent elements are matches on subexpressions (capture groups).
regex.matches()
Arguments:
regex
:any
(required)haystack
:any
(required)opts=""
:any
Return type: list
Match a regular expression against a string.
Returns an array of matches, where each match is an array where the first element is the entire match, and subsequent elements are matches on subexpressions (capture groups).
regex.test()
Arguments:
regex
:any
(required)haystack
:any
(required)opts=""
:any
Return type: bool
Match a regular expression against a string.
Returns true if at least one match exists, false otherwise.
strip()
Arguments:
value
:string
(required)chars=none
:string
- If specified: characters to strip.
Return type: string
Strip whitespace or selected characters from both ends of a string.
List Of Builtin Attributes
Bool
bool.bit_count()
Arguments: none
Return type: int
Bit count (popcount).
Counts the number of set bits.
Since a boolean only has one bit, this will always be either 0 or 1.
bool.denominator
bool.imag
bool.numerator
bool.real
bool.to_bytes
Int
int.bit_count()
Arguments: none
Return type: int
Bit count (popcount).
Counts the number of set bits in an integer.
int.denominator
int.imag
int.numerator
int.real
Float
float.imag
float.real
String
string.capitalize()
Arguments: none
Return type: string
Convert value
to title case.
string.casefold()
Arguments: none
Return type: string
Convert value
to canonical case for case-insensitive comparison
string.center()
Arguments:
value
:string
(required)width=80
:int
fillchar=" "
:string
Return type: string
Pad string on both sides to center it in the given space
string.count()
Arguments:
value
:string
(required)sub
:string
(required) - Substring to search forstart=0
:int
end=none
:int
Return type: int
Count the number of occurrences of a substring.
string.encode()
Arguments:
value
:string
(required)encoding="utf-8"
:string
- Encoding. One of 'ascii', 'utf8' (default), 'utf16le', 'utf16be', 'utf32le', 'utf32be'errors="strict"
:any
Return type: encoded
Encode string into the selected encoding.
string.endswith()
Arguments:
value
:string
(required)suffix
:string
(required)start=0
:int
end=none
:int
Return type: bool
Check whether a string ends with a given suffix.
string.isalnum()
Arguments: none
Return type: bool
Check whether a string is alpha-numeric (a letter or a digit).
string.isalpha()
Arguments: none
Return type: bool
Check whether a string is alphabetic (consists solely of letters).
string.isascii()
Arguments: none
Return type: bool
Check whether a string consists solely of 7-bit ASCII characters.
string.isdecimal()
Arguments: none
Return type: bool
Check whether a string is a decimal number
string.isdigit()
Arguments: none
Return type: bool
Check whether a string consists solely of digits.
string.islower()
Arguments: none
Return type: bool
Check whether a string is all-lowercase
string.isprintable()
Arguments: none
Return type: bool
Check whether a string contains only printable characters.
string.isspace()
Arguments: none
Return type: bool
Check whether a string contains only whitespace.
string.isupper()
Arguments: none
Return type: bool
Check whether a string is all-uppercase.
string.join()
Arguments:
value
:string
(required)iterable=[]
:list
Return type: string
str.join(iterable)
joins iterable
into a string, using str
as a separator.
string.length
string.lower()
Arguments: none
Return type: string
Convert value
to lowercase.
string.lstrip()
Arguments:
value
:any
(required)chars=none
:any
- If specified: characters to strip.
Return type: string
Strip whitespace or selected characters from the beginning of a string.
string.replace()
Arguments:
value
:string
(required)old
:string
(required) - String to search fornew
:string
(required) - Replacementcount=none
:any
- Maximum number of replacements.
Return type: string
String search-and-replace.
string.rstrip()
Arguments:
value
:any
(required)chars=none
:any
- If specified: characters to strip.
Return type: string
Strip whitespace or selected characters from the end of a string.
string.split()
Arguments:
value
:string
(required)sep=none
:string
maxsplit=none
:int
- Maximum number of splits. Unlimited if not specified.
Return type: list
Split a string by a separator.
string.splitlines()
Arguments: none
Return type: string
Split a string into lines.
string.startswith()
Arguments:
value
:string
(required)prefix
:string
(required)start=0
:int
end=none
:int
Return type: bool
Check whether a string starts with a given prefix.
string.strip()
Arguments:
value
:string
(required)chars=none
:string
- If specified: characters to strip.
Return type: string
Strip whitespace or selected characters from both ends of a string.
string.title()
Arguments: none
Return type: string
Convert value
to title case.
string.upper()
Arguments: none
Return type: string
Convert value
to uppercase.
List
Dict
dict.get()
Arguments:
value
:dict
(required)key
:scalar
(required)default=none
:any
Return type: any
Get an item from a dictionary.
dict.items()
Arguments: none
Return type: list
Get a list of key/value pairs from dictionary value
as a list
dict.keys()
Arguments: none
Return type: list
Get a list of all keys in dict value
dict.values()
Arguments: none
Return type: list
Extract the values from dictionary value
as a list
List Of Builtin Filters
These will only work in a filter context, not via procedure call syntax.
d()
Alias for default
default()
Arguments:
value
:any
(required)default
:any
(required)
Return type: any
Return default
if value
is false
, none
, or undefined, value
otherwise.
List Of Builtin Tests
These will only work in a test context (e.g., an is
-expression).
Some of these tests shadow globals of the same name but different functionality.
boolean()
Arguments:
value
:any
(required)
Return type: bool
Test whether value
is a boolean.
callable()
Arguments:
value
:any
(required)
Return type: bool
Test whether value
is callable.
defined()
Arguments:
value
:any
(required)
Return type: bool
Test whether a variable is defined.
eq()
Arguments:
value
:any
(required)
Return type: bool
Test whether value
is a eq.
escaped
false()
Arguments:
value
:any
(required)
Return type: bool
Test whether value
is boolean false
filter()
Arguments:
value
:any
(required)
Return type: bool
Test whether value
is a filter.
float()
Arguments:
value
:any
(required)
Return type: bool
Test whether value
is a float.
ge()
Alias for [ >= ](#tests_ >= )
gt()
Alias for [ > ](#tests_ > )
in()
Alias for [ in ](#tests_ in )
integer()
Arguments: none
Return type: bool
Test whether value
is an integer.
iterable()
Arguments:
value
:any
(required)
Return type: bool
Test whether value
is iterable.
Lists and list-like native objects are iterable.
le()
Alias for [ <= ](#tests_ <= )
lower()
Arguments:
value
:any
(required)
Return type: bool
Test whether value
is an all-lowercase string
lt()
Alias for [ < ](#tests_ < )
mapping()
Arguments:
value
:any
(required)
Return type: bool
Test whether value
is a mapping.
Mappings are dicts and dict-like native objects.
none()
Arguments:
value
:any
(required)
Return type: bool
Test whether value
is the none
value
number()
Arguments:
value
:any
(required)
Return type: bool
Test whether value
is a number (integer or float).
sameas
sequence()
Arguments:
value
:any
(required)
Return type: bool
Test whether value
is a sequence (i.e., a list).
string()
Arguments:
value
:any
(required)
Return type: bool
Test whether value
is a string.
test()
Arguments:
value
:any
(required)
Return type: bool
Test whether value
is a test.
true()
Arguments:
value
:any
(required)
Return type: bool
Test whether value
is boolean true
undefined()
Alias for defined
upper()
Arguments:
value
:any
(required)
Return type: bool
Test whether value
is an all-uppercase string.
Synopsis
- ginger :: (Monad m, SplitGen g) => TemplateLoader m -> POptions -> JinjaDialect -> g -> Encoder m -> Text -> Map Identifier (Value m) -> m (Either RuntimeError Encoded)
- data GingerT (m :: Type -> Type) a
- class Eval (m :: Type -> Type) a where
- data RuntimeError
- data Context (m :: Type -> Type) = Context {
- contextEncode :: Encoder m
- contextLoadTemplateFile :: TemplateLoader m
- contextVars :: !(Map Identifier (Value m))
- contextOutput :: !OutputPolicy
- defContext :: forall (m :: Type -> Type). Monad m => Context m
- data Env (m :: Type -> Type) = Env {
- envVars :: !(Map Identifier (Value m))
- envRootMay :: Maybe (Env m)
- emptyEnv :: forall (m :: Type -> Type). Env m
- defEnv :: forall (m :: Type -> Type). Monad m => Env m
- defVars :: forall (m :: Type -> Type). Monad m => Map Identifier (Value m)
- defVarsCompat :: forall (m :: Type -> Type). Monad m => Map Identifier (Value m)
- data Statement
- = PositionedS !SourcePosition !Statement
- | ImmediateS !Encoded
- | InterpolationS !Expr
- | CommentS !Text
- | ForS !(Maybe Identifier) !Identifier !Expr !(Maybe Expr) !Recursivity !Statement !(Maybe Statement)
- | IfS !Expr !Statement !(Maybe Statement)
- | MacroS !Identifier ![MacroArg] !Statement
- | CallS !Identifier ![Expr] ![(Identifier, Expr)] !Statement
- | FilterS !Identifier ![Expr] ![(Identifier, Expr)] !Statement
- | SetS !SetTarget !Expr
- | SetBlockS !SetTarget !Statement !(Maybe Expr)
- | IncludeS !Expr !IncludeMissingPolicy !IncludeContextPolicy
- | ImportS !Expr !(Maybe Identifier) !(Maybe [(Identifier, Maybe Identifier)]) !IncludeMissingPolicy !IncludeContextPolicy
- | BlockS !Identifier !Block
- | WithS ![(Identifier, Expr)] !Statement
- | GroupS ![Statement]
- data Expr
- = PositionedE !SourcePosition !Expr
- | NoneE
- | BoolE !Bool
- | StringLitE !Text
- | IntLitE !Integer
- | FloatLitE !Double
- | StatementE !Statement
- | ListE !(Vector Expr)
- | DictE ![(Expr, Expr)]
- | UnaryE !UnaryOperator !Expr
- | BinaryE !BinaryOperator !Expr !Expr
- | SliceE !Expr !(Maybe Expr) !(Maybe Expr)
- | DotE !Expr !Identifier
- | IsE !Expr !Expr ![Expr] ![(Identifier, Expr)]
- | CallE !Expr ![Expr] ![(Identifier, Expr)]
- | FilterE !Expr !Expr ![Expr] ![(Identifier, Expr)]
- | TernaryE !Expr !Expr !Expr
- | VarE !Identifier
- data Template = Template {
- templateParent :: !(Maybe Text)
- templateBody :: !Statement
- data Block = Block {
- blockBody :: !Statement
- blockScoped :: !Scoped
- blockRequired :: !Required
- data Value (m :: Type -> Type)
- data Scalar
- newtype Encoded = Encoded {}
- prettyRuntimeError :: RuntimeError -> String
- newtype Identifier = Identifier {}
- type Encoder (m :: Type -> Type) = Text -> m Encoded
- htmlEncoder :: Monad m => Encoder m
- data JinjaDialect
- data POptions = POptions {}
- defPOptions :: POptions
- data BlockTrimming
- data BlockStripping
- type TemplateLoader (m :: Type -> Type) = Text -> m (Maybe Text)
- fileLoader :: MonadIO m => FilePath -> TemplateLoader m
Interpreting Templates
Arguments
:: (Monad m, SplitGen g) | |
=> TemplateLoader m | Template loader to use for loading the initial template and
any included templates. For most use cases, |
-> POptions | Parser options, determining parser behavior. |
-> JinjaDialect | Jinja dialect; currently determines which built-in globals to load into the initial namespace. |
-> g | |
-> Encoder m | Encoder to use for automatic encoding. Use |
-> Text | Name of the initial template to load. For the |
-> Map Identifier (Value m) | Variables defined in the initial namespace. |
-> m (Either RuntimeError Encoded) |
One-stop function for parsing and interpreting a template.
data GingerT (m :: Type -> Type) a Source #
The Ginger interpreter monad. Provides error reporting / handling via
MonadError
, an execution context (Context
), and an evaluation state
(EvalState
).
Instances
MonadTrans GingerT Source # | |
Defined in Language.Ginger.Interpret.Type | |
Monad m => MonadError RuntimeError (GingerT m) Source # | |
Defined in Language.Ginger.Interpret.Type Methods throwError :: RuntimeError -> GingerT m a # catchError :: GingerT m a -> (RuntimeError -> GingerT m a) -> GingerT m a # | |
Monad m => Applicative (GingerT m) Source # | |
Defined in Language.Ginger.Interpret.Type | |
Functor m => Functor (GingerT m) Source # | |
Monad m => Monad (GingerT m) Source # | |
Monad m => MonadReader (Context m) (GingerT m) Source # | |
Monad m => MonadState (EvalState m) (GingerT m) Source # | |
class Eval (m :: Type -> Type) a where Source #
Eval
represents types that can be evaluated in some 'GingerT m' monadic
context.
data RuntimeError Source #
Constructors
ArgumentError | |
TagError | |
NonCallableObjectError Text | Object that was attempted to be used as a callable |
NotInScopeError Text | Identifier |
NotImplementedError Text | The thing that isn't implemented |
NumericError | |
TemplateFileNotFoundError Text | Template name |
TemplateParseError | |
FatalError Text | |
PositionedError !SourcePosition !RuntimeError |
Instances
data Context (m :: Type -> Type) Source #
Constructors
Context | |
Fields
|
data Env (m :: Type -> Type) Source #
Constructors
Env | |
Fields
|
defVarsCompat :: forall (m :: Type -> Type). Monad m => Map Identifier (Value m) Source #
AST
A statement in the template language.
Constructors
PositionedS !SourcePosition !Statement | Statement tagged with a source position |
ImmediateS !Encoded | Bare text written in the template, outside of any curly braces |
InterpolationS !Expr | An expression interpolation: |
CommentS !Text | Comment: |
ForS !(Maybe Identifier) !Identifier !Expr !(Maybe Expr) !Recursivity !Statement !(Maybe Statement) | @@ |
IfS !Expr !Statement !(Maybe Statement) | {% if condition %}yes branch{% else %}no branch{% endif %} |
MacroS !Identifier ![MacroArg] !Statement | {% macro name(args) %}body{% endmacro %} |
CallS !Identifier ![Expr] ![(Identifier, Expr)] !Statement | {% call macroName(args) %}body{% endcall %} |
FilterS !Identifier ![Expr] ![(Identifier, Expr)] !Statement | {% filter filterName(args, kwargs) %}body{% endfilter %} |
SetS !SetTarget !Expr | {% set name=expr %} |
SetBlockS !SetTarget !Statement !(Maybe Expr) | {% set name %}body{% endset %} |
IncludeS !Expr !IncludeMissingPolicy !IncludeContextPolicy | {% include includee ignore missing with context %} |
ImportS !Expr !(Maybe Identifier) !(Maybe [(Identifier, Maybe Identifier)]) !IncludeMissingPolicy !IncludeContextPolicy | {% import importee as localName item, other_item as other ignore missing with context %} |
BlockS !Identifier !Block | {% block name with scope required %}body{% endblock %} |
WithS ![(Identifier, Expr)] !Statement | {% with defs %}body{% endwith %} |
GroupS ![Statement] | Group of statements; not parsed, but needed for combining statements sequentially. |
Instances
Arbitrary Statement Source # | |
Show Statement Source # | |
Eq Statement Source # | |
Ord Statement Source # | |
RenderSyntax Statement Source # | |
Defined in Language.Ginger.Render Methods renderSyntax :: Statement -> Builder Source # | |
Monad m => Eval m Statement Source # | |
An expression. Expressions can occur in interpolations ({{ ... }}
), and
in various places inside statements.
Constructors
PositionedE !SourcePosition !Expr | |
NoneE | |
BoolE !Bool | |
StringLitE !Text | |
IntLitE !Integer | |
FloatLitE !Double | |
StatementE !Statement | |
ListE !(Vector Expr) | |
DictE ![(Expr, Expr)] | |
UnaryE !UnaryOperator !Expr | @UnaryE op rhs |
BinaryE !BinaryOperator !Expr !Expr | @BinaryE op lhs rhs |
SliceE !Expr !(Maybe Expr) !(Maybe Expr) | @SliceE slicee start length |
DotE !Expr !Identifier | @DotE lhs rhs |
IsE !Expr !Expr ![Expr] ![(Identifier, Expr)] | IsE scrutinee test args kwargs |
CallE !Expr ![Expr] ![(Identifier, Expr)] | CallE callee args kwargs |
FilterE !Expr !Expr ![Expr] ![(Identifier, Expr)] | FilterE arg0 filter args kwargs |
TernaryE !Expr !Expr !Expr | TernaryE cond yes no |
VarE !Identifier |
A template consists of an optional parent template (specified in the
source using the {% extends %}
construct), and a body statement.
Constructors
Template | |
Fields
|
A block represents a section of a template that can be overridden in derived templates ("template inheritance").
Constructors
Block | |
Fields
|
Representing Values
data Value (m :: Type -> Type) Source #
A value, as using by the interpreter.
Constructors
ScalarV !Scalar | |
ListV !(Vector (Value m)) | |
DictV !(Map Scalar (Value m)) | |
NativeV !(NativeObject m) | |
ProcedureV !(Procedure m) | |
TestV !(Test m) | |
FilterV !(Filter m) | |
MutableRefV !RefID |
Instances
Constructors
NoneScalar | |
BoolScalar !Bool | |
StringScalar !Text | |
EncodedScalar !Encoded | |
BytesScalar !ByteString | |
IntScalar !Integer | |
FloatScalar !Double |
Instances
Arbitrary Scalar Source # | |
FromJSON Scalar Source # | |
Defined in Language.Ginger.Value | |
FromJSONKey Scalar Source # | |
Defined in Language.Ginger.Value | |
ToJSON Scalar Source # | |
ToJSONKey Scalar Source # | |
Defined in Language.Ginger.Value | |
IsString Scalar Source # | |
Defined in Language.Ginger.Value Methods fromString :: String -> Scalar # | |
Show Scalar Source # | |
Eq Scalar Source # | |
Ord Scalar Source # | |
ToScalar Scalar Source # | |
Applicative m => FromValue Scalar m Source # | |
Defined in Language.Ginger.Value | |
ToValue Scalar a Source # | |
(Monad m, FromValue a m) => FromValue (Map Scalar a) m Source # | |
Defined in Language.Ginger.Value |
Represents an encoded string value, as opposed to a raw (unencoded) string,
which we represent as a plain Text
.
prettyRuntimeError :: RuntimeError -> String Source #
Pretty-print a RuntimeError
. The output is meant to be useful as a
user-facing error message.
newtype Identifier Source #
Identifiers are used to represent variable names and object fields.
Constructors
Identifier | |
Fields |
Instances
Configuration
htmlEncoder :: Monad m => Encoder m Source #
data JinjaDialect Source #
Constructors
DialectGinger2 | |
DialectJinja2 |
Instances
Parser and Parser Options
Constructors
POptions | |
Fields |
data BlockTrimming Source #
Constructors
NoTrimBlocks | |
TrimBlocks |
Instances
data BlockStripping Source #
Constructors
NoStripBlocks | |
StripBlocks |
Instances
Template Loaders
fileLoader :: MonadIO m => FilePath -> TemplateLoader m Source #