Skip to content

Urquelle/templ

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation




build statistics meta
travis:
appveyor:
github:
issues:
quality:
loc:
commits:
editor:
platform:

table of contents

simple c++ example

in the following example a datastructure is created and put into the engine's context. the subsequent call of templ_render is given the created datastructure to render the string template.

#include "templ.cpp"

int
main(int argc, char **argv) {
    using namespace templ::api;

    templ_init(MB(100), MB(100), MB(100));

    Templ_Vars vars = templ_vars();
    Templ_Var *name = templ_var("name", "noob");
    templ_vars_add(&vars, name);

    Templ *templ = templ_compile_string("hello {{ name }}");
    char *result = templ_render(templ, &vars);

    os_file_write("test.html", result, utf8_strlen(result));
    if ( status_is_error() ) {
        for ( int i = 0; i < status_num_errors(); ++i ) {
            Status *error = status_error_get(i);
            fprintf(stderr, "%s in %s line %lld\n", status_message(error),
                status_filename(error), status_line(error));
        }

        for ( int i = 0; i < status_num_warnings(); ++i ) {
            Status *warning = status_warning_get(i);
            fprintf(stderr, "%s in %s line %lld\n", status_message(warning),
                status_filename(warning), status_line(warning));
        }

        status_reset();
    }

    templ_reset();

    return 0;
}

jinja template examples

data folder contains a couple jinja templates with statements that are supported by the implementation so far.

{% extends "template.tpl" if true %}

{% block title %}
    main - {{ default_title }}
{% endblock %}

{% block main %}
    {{ super() }}

    {% include "literals.tpl" without context %}
    {% include "exprs.tpl"    with    context %}
    {% include "stmts.tpl"    without context %}
    {% include "utf8.tpl"     without context %}
    {% include "filter.tpl"   with    context %}
    {% include "tests.tpl"    without context %}
    {% include "macros.tpl"   without context %}
{% endblock main %}

{% block custom %}
    <div>custom content</div>
{% endblock %}

json

there's a simple, and built-in support for json which is implemented in the src/json.cpp. the json_parse method, will parse a given json string, and return a Json structure.

Json structure can be fed to templ_var method and get a Templ_Var * instance in return, which can be used in templ_render context.

    Json json = json_parse(R"foo([
        {
            "name": "noob",
            "age" : "25",
            "address": {
                "city": "frankfurt",
                "street": "siegerstr. 2"
            }
        },
        {
            "name": "reinhold",
            "age" : "23",
            "address": {
                "city": "leipzig",
                "street": "mozartstr. 20"
            }
        }
    ])foo");

    Templ *templ = templ_compile_string("{{ users[0].name }}: {{ users[0].address.city }} -- {{ users[1].name }}: {{ users[1].address.city }}");

    Templ_Var *users = templ_var("users", json);
    Templ_Vars vars = templ_vars();

    templ_vars_add(&vars, users);

    char *result = templ_render(templ, &vars);

unicode

templ supports unicode with the utf-8 encoding for string literals as well as names. be aware though that right now only limited amount of transformation in filters is supported.

below is a list of character ranges which have lower-/uppercase conversion support.

ABCDEFGHIJKLMNOPQRSTUVWXYZÄÜÖẞ
АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ
ÆÅÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞĀĂĄĆĈĊČĎĐĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮİIJĴĶŸ
ŁŃŅŇ

abcdefghijklmnopqrstuvwxyzäüöß
абвгдеёжзийклмнопрстуфхцчшщъыьэюя
æåçèéêëìíîïðñòóôõöøùúûüýþāăąćĉċčďđēĕėęěĝğġģĥħĩīĭįıijĵķÿ
łńņň

characters that are not supported will be printed back as they are.

table with all character blocks.

template

{% set シ个 = "原ラ掘聞" %}
{{ シ个 }}
{% set приветствие = "здравствуйте" %}
{{ приветствие }}
{{ "🤩✨🥰" * 10 }}

output

原ラ掘聞
здравствуйте
🤩✨🥰🤩✨🥰🤩✨🥰🤩✨🥰🤩✨🥰🤩✨🥰🤩✨🥰🤩✨🥰🤩✨🥰🤩✨🥰

expressions

below is a list of expressions that are supported right now

literals

string literals

string literals are supported with quotation marks as well as with apostrophe.

"string literal"
'also string literal'

numbers

integer and floating point numbers are supported.

42
42.0

lists

list literals start with an opening bracket and contain a comma separated list of elements, which in turn have to be a valid jinja expression.

['europe', 'asia', 'australia']

lists can be assigned as a value to a variable and be used in a for statement as both, literals and variables.

{% for it in ['europe', 'asia', 'australia'] %}
    {{ it }}
{% endfor %}

{% set continents = ['europe', 'asia', 'australia'] %}
{% for it in continents %}
    {{ it }}
{% endfor %}

tuple

tuple are basically lists with the exception of being read-only.

('x', 'y')

dictionaries

dictionaries are supported as expressions in assignments and in expression in for loops.

{% set d = {'name': 'adam', 'age': '30'} %}

{% for it in {'name': 'eve', 'age': '25'} %}
    ...
{% endfor %}

booleans

boolean values

true
false

math

3+5*7/2

below is a list of supported math operations

+
-
*
**
/
//
%

statements

list of supported statements

block

blocks are supported with an optional name in the endblock statement. as the inheritance of templates is also supported, parent block's content can be overwritten entirely, or be included alongside your own content with the super() method.

{% block <name> %}
{% endblock <name> %}

do

{% do <expression> %}

extends

{% extends "<template>" <if expr> %}

filter

{% filter <name1> | <name2> %}
    <anweisungen>
{% endfilter %}

for

for statement supports multiple return values, else branch, almost all loop variables, break and continue statements.

{% for <iterator> in <menge> %}
    <anweisungen>
{% else %}
    <anweisungen>
{% endfor %}

the following loop variables can be used inside a for loop:

  • loop.index
  • loop.index0
  • loop.revindex
  • loop.revindex0
  • loop.first
  • loop.last
  • loop.length
  • loop.cycle
  • loop.depth
  • loop()

if

flow control statement if is supported with elif and else branches.

{% if <condition> %}
    <statements>
{% elif <condition> %}
    <statements>
{% else %}
    <statements>
{% endif %}

you can use any valid jinja and supported expressions as condition that have a boolean value as result.

true
false
1 < 2
a is eq "foo"
firstname == "arminius" and lastname == "der cherusker"

import

{% import "<template>" as <sym> %}
{% from "<template>" import <sym1> as <alias1> %}

include

additional templates can be included into a template. include statement supports if expression, and the additional annotations with context, without context, ignore missing.

{% include "<template>" <if ausdruck> %}

macro

{% macro <name>(parameter, ...) %}
{% endmarcro %}

raw

{% raw %}
{% endraw %}

set

{% set <lvalue expression> = <rvalue expression> %}

set block

{% set <lvalue expression> %}
    <statements>
{% endset %}

filter

ongoing process of implementing the vast amount of filters. the following filters are implemented in dev:

  • abs
  • attr
  • batch
  • capitalize
  • center
  • default
  • dictsort
  • escape
  • filesizeformat
  • first
  • float
  • format
  • lower
  • max
  • min
  • reject
  • rejectattr
  • reverse
  • select
  • selectattr
  • slice
  • sum
  • truncate
  • upper

tests

most of the tests present in the jinja2 spec are already implemented in dev.

  • callable
  • defined
  • devisibleby
  • equal
  • even
  • ge
  • gt
  • in
  • iterable
  • le
  • lt
  • mapping
  • ne
  • none
  • number
  • odd
  • sameas
  • sequence
  • string
  • undefined

introspection

lightweight introspection is built in. for it to work you have to provide a meta json file, which describes the data layout of the given raw pointer.

in the example below the User struct

struct Address {
    char *city;
};

struct User {
    int age;
    char *name;
    Address address;
};

is described with the following json

[
    {
        "name"  : "age",
        "offset": 0,
        "kind"  : 1
    },
    {
        "name"  : "name",
        "offset": 8,
        "kind"  : 0
    },
    {
        "name"  : "address",
        "offset": 16,
        "kind"  : 5,
        "format": [{
            "name"  : "city",
            "offset": 0,
            "kind"  : 0
        }]
    }
]

to use the c++ data in template you first have to create a Templ_Var * instance, which can be done as follows:

User user = { 20, "alex", { "paris" } };
Templ_Var *user = templ_var("user", &user, json_parse(json_format_string));
...

the kind field in the meta json file has to be the int value from the Json_Node_Kind enum

enum Json_Node_Kind {
    JSON_STR,
    JSON_INT,
    JSON_FLOAT,
    JSON_BOOL,
    JSON_ARRAY,
    JSON_OBJECT,
    JSON_NULL,
};

custom procs

templ supports registering of custom procedures which then can be executed in a template. you can register three types of procedures.

global

global procs are standalone and not bound to any context. they can be used everywhere you can use builtin procs as well.

PROC_CALLBACK(custom_hello) {
    using namespace templ::devapi;

    return val_str("hello, world");
}

int main() {
    using namespace templ::api;
    using namespace templ::devapi;

    templ_init(MB(100), MB(100), MB(100));
    templ_register_proc("hello", custom_hello, 0, 0, type_str);
    ...
}

and then later in the template:

{{ hello() }}
{% set var = hello() }}

type

you can also register procedures that are bound to a certain type. for that the following api calls can be used, that you can import separately by using namespace templ::devapi:

templ::templ_register_any_proc;    // register procedure for every datatype
templ::templ_register_seq_proc;    // register procedure for sequence datatypes only
templ::templ_register_num_proc;    // register procedure for numeric datatypes only
templ::templ_register_bool_proc;   // register procedure for bool
templ::templ_register_dict_proc;   // register procedure for dictionary
templ::templ_register_float_proc;  // register procedure for float
templ::templ_register_int_proc;    // register procedure for int
templ::templ_register_range_proc;  // register procedure for range
templ::templ_register_list_proc;   // register procedure for list
templ::templ_register_string_proc; // register procedure for string

note: all type procs can also be used as filters on that type, so {{ "abc".my_custom_proc() }} is the same as {{ "abc" | my_custom_proc }}

tester

tester are used in the is expression as a procedure to test against a given value, and have to evaluate to bool.