Macros

Table of contents

  1. Defining macro
  2. ‘call’ statement
  3. ‘applymacro’ filter

Defining macro

Ths sample above violates ‘DRY’ rule. It contains the code which could be generalized. And Jinja2 supports features for such kind generalization. This feature called ‘macro’. The sample can be rewritten the following way:

{% macro MethodsDecl(class, access) %}
{% for method in class.methods | rejectattr('isImplicit') | selectattr('accessType', 'in', access) %}
    {{ method.fullPrototype }};
{% endfor %}
{% endmacro %}

class {{ class.name }}
{
public:
    {{ MethodsDecl(class, ['Public']) }}
protected:
    {{ MethodsDecl(class, ['Protected']) }}
private:
    {{ MethodsDecl(class, ['Private', 'Undefined']) }}
};

{% endfor %}

MethodsDecl statement here is a macro which takes two arguments. First one is a class with method definitions. The second is a tuple of access specifiers. Macro takes non-implicit methods from the methods collection (rejectattr('isImplicit') filter) then select such methods which have right access specifier (selectattr('accessType', 'in', access)), then just prints the method full prototype. Finally, the macro is invoked as a regular function call: MethodsDecl(class, ['Public']) and replaced with rendered macro body.

‘call’ statement

There is another way to invoke macro: the call statement. Simply put, this is a way to call macro with callback. Let’s take another sample:

{% macro InlineMethod(resultType='void', methodName, methodParams=[]) %}
inline {{ resultType }} {{ methodName }}({{ methodParams | join(', ') }} )
{
    {{ caller() }}
}
{% endmacro %}

{% call InlineMethod('const char*', enum.enumName + 'ToString', [enum.nsScope ~ '::' ~ enum.enumName ~ ' e']) %}
    switch (e)
    {
{% for item in enum.items %}
    case {{enum.nsScope}}::{{item}}:
        return "{{item}}";
{% endfor %}
    }
    return "Unknown Item";
{% endcall %}

Here is an InlineMacro which just describe the inline method definition skeleton. This macro doesn’t contain the actual method body. Instead of this it calls caller special function. This function invokes the special callback macro which is a body of call statement. And this macro can have parameters as well. More detailed this mechanics described in the Jinja2 documentation.

‘applymacro’ filter

With help of applymacro filter macro can be called in filtering context. applymacro works similar to map (or test) filter with one exception: instead of name of other filter it takes name of macro via macro param and pass the rest of arguments to it. The object which is been filtered is passed as the first positional argument. For example:

{% macro toUpper(str) %}{{ str | upper }}{% endmacro %}
{{ 'Hello World!' | applymacro(macro='toUpper') }}

produces the result HELLO WORLD. applymacro can be applied to the sequences via map filter. Also, macro name can be caller. In this case outer call statement will be invoked during macro application.