tempiny

Tiny template engine


License
LGPL-2.1+
Install
pip install tempiny==1.1

Documentation

Introduction

Tempiny is a tiny and really simple template engine.

The main feature is to be able to execute arbitrary python code inside the template, which makes it really powerful for code generation for example.

See the skbs project to have a sense of what it can achieve !

Install

Simply pip install it :

pip install tempiny

Template syntax

First an example to demonstrate all its features :

This text will be printed as it is

Lines starting with '##' (or a user-configured prefix) are python code.

## a=5 # this won't be printed
## # this is a comment in the python script. Won't be printed.

if/else/for/while/with/try/except etc blocks don't need indentation. Instead, a line containing only '## -' marks the block end.

## for i in range(a) :
##   b = a + 1 # you may indent
## c = a +2 # or not, still in the for block.
This text will be printed 5 times (a = {{a}}) Btw, between a double brace (2 '{'), you can put expression that will be converted to str, and printed instead.
To escape it, The prefered way is to declare variables containing the tokens (as done in skbs)
##   for j in range(3) :
You can also nest loops
##   -
## # ↑ end of inner loop
## -
## # end of outer loop

Expression can be as complex you want as long as they are valid python expression returning something that can be transformed to a string :
{{ ";".join( str(i) + f' - {a=},{b=},{c=}' for i in range(2)) }}

will be output as :

This text will be printed as it is

Lines starting with '##' (or a user-configured prefix) are python code.


if/else/for/while/with/try/except etc blocks don't need indentation. Instead, a line containing only '## -' marks the block end.

This text will be printed 5 times (a = 5) Btw, between a double brace (2 '{'), you can put expression that will be converted to str, and printed instead.
To escape it, The prefered way is to declare variables containing the tokens (as done in skbs)
You can also nest loops
You can also nest loops
You can also nest loops
This text will be printed 5 times (a = 5) Btw, between a double brace (2 '{'), you can put expression that will be converted to str, and printed instead.
To escape it, The prefered way is to declare variables containing the tokens (as done in skbs)
You can also nest loops
You can also nest loops
You can also nest loops
This text will be printed 5 times (a = 5) Btw, between a double brace (2 '{'), you can put expression that will be converted to str, and printed instead.
To escape it, The prefered way is to declare variables containing the tokens (as done in skbs)
You can also nest loops
You can also nest loops
You can also nest loops
This text will be printed 5 times (a = 5) Btw, between a double brace (2 '{'), you can put expression that will be converted to str, and printed instead.
To escape it, The prefered way is to declare variables containing the tokens (as done in skbs)
You can also nest loops
You can also nest loops
You can also nest loops
This text will be printed 5 times (a = 5) Btw, between a double brace (2 '{'), you can put expression that will be converted to str, and printed instead.
To escape it, The prefered way is to declare variables containing the tokens (as done in skbs)
You can also nest loops
You can also nest loops
You can also nest loops

Expression can be as complex you want as long as they are valid python expression returning something that can be transformed to a string : 0 - a=5,b=6,c=7;1 - a=5,b=6,c=7

Basically, there are 3 contexts :

Code context

Each line starting by the code prefix (specified in plugin.py, or '##' by default) is basically python code except for the block delimitation : in python, the indentation level delimits a block while with tempiny, for practical use, indentation doesn't matter, and a block is ended by a single dash ( "-" ).

Example :

## a = 5
## for i in range(a) :
##   b = 2 + i
##   # Do come stuff
## c=3 # this is still in the for
## -
## # end of the for

Any python code is allowed. This is the reason you should use templates only from trusted sources.

Text context

Any line that doesn't start with the code prefix is "text", and will be output as is each time the execution flow reaches it. Basically, you can imagine (by the way, this is actually how it is implemented...) each Text context is like a call to print

For example, the following :

This is a text
## for i in range(3):
To see
## -
how it works

Will output :

This is a test
To see
To see
To see
how it works

Expression context

Inside a Text context, you may want to print an expression (for example a variable value or the result of a python call etc.) You can do it by surrounding it with the expression delimiters (specified in plugin.py or '{{' and '}}' by default). It will be replaced by the expression value at the time of execution. Example:

## for i in range(3)
Item number {{i}}
## -

Will print :

Item number 0
Item number 1
Item number 2

Any python expression is allowed.

Once again, you should only execute trusted templates.

API

from tempiny import Tempiny # import tempiny

There are 3 built-in dialects:

Tempiny.C  = dict(stmt_line_start=r'//#', begin_expr='{{', end_expr='}}')
Tempiny.PY = dict(stmt_line_start=r'##', begin_expr='{{', end_expr='}}')
Tempiny.TEX = dict(stmt_line_start=r'%#', begin_expr='<<', end_expr='>>')

To configure a Tempiny instance :

T = Tempiny(
  stmt_line_start=PY['stmt_line_start'], # Code prefix. 
  begin_expr=PY['begin_expr'], # Expression delimiter (begin)
  end_expr=PY['end_expr'] # Expression delimiter (end)
)

Note that you can use any string for these tokens.

Tempiny.compile(self, f:Iterable[str], filename='<template>', default_globals={}, default_locals=None) -> Template: This method compiles a template. f should be an iterable of strings (an open file is ok). default_globals is a dict you can provide to have a default global scope when calling the template. You should probably leave default_locals to None except if you know what you are doing.

Example :

with open('test_template', 'r') as f:
  template = T.compile(f, 'test_template')

Template.__call__(self, out_stream, _globals={}, _locals=None) -> (dict, Exception) This method permits to call the template, outputting in out_stream (it should at least have a write(s:str) method. You can provide a global scope (it will be shallow-copied before entering the template) through _globals to pass values to the template. You can also provide a local scope through _locals, but it can lead to unexpected behaviour (not defined errors inside function or list-comprehension). Except if you know what to do, let it None, it will contain the same content as the globals. It returns the new locals (to retrieve names defined or modified from the template), and None if no exception occurred, or the exception if one raised.

Example:

with open('result', 'w') as f:
  template(f, {'author': 'Léo Flaventin Hauchecorne', 'date' : 2016})

License Notice

Tempiny is a tiny template engine Copyright © 2016-2020 Léo Flaventin Hauchecorne

This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA