Using the Jinga Template engine in Programming

Summary

We describe how the Python Jinga Template engine can be used in programming with such languages as C,C++,Java and C#.

Introduction

The C and C++ languages have a preprocessor known as CPP which makes it possible to do some program generation at « compile-time ». In fact this is really « preprocessor-time », a stage that is prior to actual compilation.

The Java language has done away with the C preprocessor, and there are times that a programmer can regret this. C# has a very simplified preprocessor.

There are freely available preprocessors like m4 which can be used to replace the C preprocessor and which can be used with any language (providing there is no conflict between the preprocessor lexical units and the languages's own lexical units). However the advent of the Web (and Terence Parr's string templates) have re-introduced a preprocessing engine trough “templates” that offer power that goes considerably beyond m4 (or the C/C++ preprocessor), in that they can call on arbitrary scripts in such a language as Python to create an associative dictionnary which is used to fill a template.

A template can have not just conditional sections (akin to CPP's #ifdef ) but also loops, which are very useful, for example in filling in tables.

For the most part these template engines have been used in Web site generation (such as with Django) but we would like to demonstrate here that they could be used with plain code such as C, C++ and Java,.

We shall focus on one engine called Jinja2, written in Python. We will assume that the reader is familiar with basic Python. The target language will be any of the C-like languages such as C,C++, Java and C#. With a little adaptation the code we present here could be adapted to many other languages.

There is nothing deep or specially new presented here. The purpose of the article is to be a tutorial so as to help the reader get started with the technique.

Jinja2

Installation

We shall suppose the reader is working under Windows, but it should not be hard to adapt the advice so as to work under other operating systems such as GNU/Linux.

The website for Jinja2 is http://jinja.pocoo.org/

Jinja2 appears to be the current version of Jinja. Before you visit this site please read the next paragraph.

In order to install Jinga2 I first installed easy_install by downloading setuptools-0.6c11.win32-py2.7.exe from http://pypi.python.org/pypi/setuptools#downloads. I chose the version of setuptools that matched the version of Python that is on my machine (version 2.7, and you can see this in the name of the file).

Then I just ran this executable, and found that easy_install was in my PATH system variable with no effort on my part!

Then to download Jinja2 I just ran

easy_install Jinja2

from the DOS command line, while the computer is connected to the Internet. This installation tool makes life very easy for you, it looks up the appropriate website and downloads and installs the appropriate module (Jinja2!).

To check that Jinja2 has been correctly installed, run Python and try executing

import jinja2

If all is well there should be no errors.

First example

And now for our first attempt at programming with Jinja2.

A string containing the template is defined as follows:

u'''{% for x in names %}printf("Hello %s","{{x}}");

{% endfor %}'''

Note that the u prefix says the string is a unicode string, because Jinja2 is designed to work with such strings.

Let's analyse the string:

The first thing to point out is that there are parts of the string that are inside “{%” and “%}”, other parts of the string that are inside “{{“ and “}}” and the rest looks like ordinary code.

The parts that are not ordinary code are called tags. There are two types of tags as the syntax suggests.

  1. What's between “{%and “%}are template “control” instructions. In the above example the control instructions represent a kind of For loop.

  2. What's between “{{and “}}are expressions to be evaluated. In the above example the expression is just a plain variable.

What the for loop does is repeat the processed code between the {% for x in names %} and the {% endfor %}. The intent is that names has a value which evaluates to a container like a list for example. The various values of x are obtained from this list and then printf("Hello %s","{{x}}"); is processed, which means that {{x}} is replaced by the value of x.

The preprocessing is called using special Jinja2 code.

The code is as follows:

from jinja2 import Template

t = Template(u'''{% for x in names %}printf("Hello %s","{{x}}");

{% endfor %}''')

print t.render(names=[“Jack”,”Jill”,”Lindsay”])

This prints the following:

printf("Hello %s","Jack");

printf("Hello %s","Jill");

printf("Hello %s","Lindsay");

Second example

We shall build a template file and a Python file that uses this template.

The template file is just a text file should contain this:

{% for x in names %}printf("Hello %s","{{x}}");

{% endfor %}



This file can have any extension whatsoever, but I used .jtpl by taste.

I called the file template1.jtpl

Now for the code that follows Jinja's recommendations:



# JinjaTest1.py is an attempt to write a module that exploits Jinja templates.

# the current file is *not* a template

# -*- coding: utf-8 -*-

import codecs

import jinja2

fsloader = jinja2.FileSystemLoader(r'd:\dev\py\templates')

env = jinja2.Environment(loader=fsloader)

template = env.get_template('jinja_template1.jtpl')

rt = template.render(names=[u"Peter",u"Paul",u"Mary"])

ofh = codecs.open("output1.cpp","w", encoding="utf-8")

ofh.write(rt)

ofh.close()



Let's comment this now:

  1. It's best to work with a template file that is separate from the Python code. We have achieved this by separating template1.jtpl and JinhaTest1.py

  2. In the Python code we first define a “template loader” which will allow us to tell Jinja2 where to find the templates which will be just refered to by a short file name. The FileSystemLoader call says get them by loading the file from d:\dev\py\templates

  3. We next create an “Environment” instance. Now an environment is a sort of global structure into which you can put all your options. For example you can create an environment where the “{{“ template token is replaced by another token. This might be useful if you were using Jinja2 as a preprocessor for some language where “{{“ can actually occur in a program. In our case we have indicated how to get the templates.

  4. Then we load the template by implicitly calling the file system loader via get_template. There are advanced features of templates like template inheritance where it's best to use get_template rather than calling Template(...). This is another reason for using the Environment instance.

  5. We then call the “render” which does the template processing and outputs a string. In the above code we are doing it with keyword arguments (in fact just one), but render is designed to allow passing a dictionnary to the same effect.

  6. The next line uses the codecs module instead of the usual open call because this can deal with unicode. The type of encoding is passed as a keyword argument.

  7. Everything about a codecs.open return value is treated like the return value of an open call.

  8. The effect of executing this program is to create a file whose content is
    printf(“%s”,”Peter”);
    printf(“%s”,”Paul”);
    printf(“%s”,”Mary”);

Other control structures

You can use conditional tags as in this example

{% if variable is defined %}

value of variable: {{ variable }}

{% else %}

variable is not defined

{% endif %}

In fact there are very many control structures and other instructions, we recommend you look at the Jinja2 website.

Many of the finer points (like many of the filters) of Jinja2 are explicitly aimed at Web programmers, but this is not a problem.



So what is this good for?

The author came across an application at work in which we had to deal with “messages” from several different sources, and each message had a number of fields, with their own data types. For each message type a subset of the fields were considered “interesting information” which should populate a database. The trouble was that there were lots of fields, and many places in which the database operations were used, so that if there was a change of specification of which fields to use this would cause a risk of forgetting which fields to add or remove from various parts of the program. Now of course the whole “meta-level” aspect of the fields could have been programmed in a class called field. There would also have been classes for sets of fields, and the operations would have been interpreted at runtime. This would have occurred a run-time overhead, which could have been avoided by preprocessing before the compilation.



Conclusion

Jinja is just one of the many template processing engines out there. Nevertheless it is free, open source, and rather well documented. It gives you a lot of power for very little effort.

The authors of these processing engines dont seem to talk much about code generation, but concentrate on web page generation.

We hope we have made the entry into the subject a little more easy with this article.