This is a simple template engine which allows you to replace macros within text. This engine allows for attributes and filters. The default implementation provides the entire string module as filters. Trying to use arguments will of course not work (since the framework supports no other arguments for the filter other than the filtered string itself).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | """
Name-Value Object: $obj.attr.attr2|filter|filter2|filterN
Numbered Argument: $[0].attr.attr2|filter|filter2|filterN
To create new filters, subclass and create do_ method.
do_ALL is the general method for any filters not found. It should raise FilterError if the
filter is still not available.
Use $$ to avoid replacement
"""
import re
from string import capwords
class FilterError(Exception): pass
r_macro = re.compile(r"""
\$ # must start with $, not $$
( # start group
\[\d+\][.|][a-zA-Z0-9]+[a-zA-Z0-9|.]* # opening [number] + attrs / filters
| # 2nd case
\[\d+\] # opening [number] + filters
| # 3rd case
[a-zA-Z][a-zA-Z0-9]+[.|][a-zA-Z0-9]+[a-zA-Z0-9|.]* # obj + attrs / filters
| # 4th case
[a-zA-Z][a-zA-Z0-9]+ # obj + attrs / filters
) # done
""", re.VERBOSE)
class Template:
def __init__(self, string=''):
self.buffer = string
self.filterregister = {'length': len}
def load(self, string):
self.buffer += string
def register_filter(self, name, func):
self.filterregister[name] = func
def remove_filter(self, name):
func = self.filterregister[name]
del self.filterregister[name]
return func
def do_capwords(self, string):
return capwords(string)
def do_capfirst(self, string):
return string[0].capitalize() + string[1:]
def do_ALL(self, string, filter_name):
"""General filter for all filter types not found"""
if hasattr(str, filter_name):
return getattr(str, filter_name)(string)
else:
raise FilterError('No such filter as %r'%filter_name)
def render(self, *args, **kwargs):
"""Render the template, replacing macros along the way"""
bufferstring = str(self.buffer)
for matchedstring in r_macro.findall(self.buffer):
# this list should contain the name of the object first, then the attrs needed
obj_and_attrs = matchedstring.split('.')
# remove the filters from the attr string
obj_and_attrs[-1] = obj_and_attrs[-1].split('|', 1)[0]
obj, attrs = obj_and_attrs[0], obj_and_attrs[1:]
if obj.startswith('['):
index = int(obj[1:-1])
if not index < len(args):
#raise ValueError("No object at index %s!"%index)
bufferstring = bufferstring.replace('$'+matchedstring, '')
continue
else:
obj = args[index]
else:
if obj not in kwargs:
#raise ValueError("No object with name %r"%obj)
bufferstring = bufferstring.replace('$'+matchedstring, '')
continue
obj = kwargs[obj]
value = obj
if len(attrs) > 0:
for attr in attrs:
if not attr:
raise ValueError("Cannot have empty attr!")
try:
value = getattr(value, attr)
except AttributeError:
value = value[attr]
filters = matchedstring.split('|')[1:]
for filtername in filters:
if not filtername:
raise ValueError("Cannot have empty filter!")
try:
value = getattr(self, "do_"+filtername)(value)
except AttributeError:
try:
value = self.filterregister[filtername](value)
except KeyError:
value = self.do_ALL(value, filtername)
bufferstring = bufferstring.replace('$'+matchedstring, str(value))
return bufferstring.replace('$$', '$').replace('\.', '.')
def render_from_string(templatestring, *args, **kwargs):
"""Shortcut function"""
t = Template(templatestring)
return t.render(*args, **kwargs)
if __name__ == "__main__":
tempstr = """Hello $fullname|capwords,
I am writing to inform you that your child, $firstname|capitalize $lastname|capitalize has recieved
a grade of $grade% in this $coursename course. I strongly believe that your child has much potential
and could do much better if he/she tried.
I require $$100.00 for the field trip next week. We will be going to $[0] on $[1]\."""
print render_from_string(tempstr, "Edworthy Park", "Tuesday", fullname="Bob joe", firstname="James", lastname="Clark maxwell", grade=75, coursename="Astronomy")
|
All of the below documentation is provided (in less words) within the source code. That way you can always refer to it.
This engine seeks out dollar signs ($) within the text. Use $objname
to access named objects. Use $[0]
to access numbered arguments. See examples below. To create new filters, subclass Template
and create a new do_ method. do_ALL
is the general filter method and should raise FilterError when it cannot complete the filter.
Use $$ to avoid replacement.
Ex: Name-Value Object:
$obj.attr.attr2|filter|filter2|filterN
Numbered Argument:
$[0].attr.attr2|filter|filter2|filterN
Here is a full example:
tempstr = """Hello $fullname|capwords, I am writing to inform you that your child, $firstname|capitalize $lastname|capitalize has recieved a grade of $grade% in this $coursename course. I strongly believe that your child has much potential and could do much better if he/she tried.\n\nI require $$100.00 for the field trip next week. We will be going to $[0] on $[1]\."""
print render_from_string(tempstr, "Edworthy Park", "Tuesday", fullname="Bob joe", firstname="James", lastname="Clark maxwell", grade=75, coursename="Astronomy")
Executing the example 1 million times takes 55.315 seconds. Which means that executing it 100 times will take about .55315 seconds.
>>> timeit('render_from_string(tempstr, "Edworthy Park", "Tuesday", fullname="Bob joe", firstname="James", lastname="Clark maxwell", grade=75, coursename="Astronomy")', "from template import render_from_string\n" r"tempstr = 'Hello $fullname|capwords,\nI am writing to inform you that your child, $firstname|capitalize $lastname|capitalize has recieved\na grade of $grade% in this $coursename course. I strongly believe that your child has much potential\nand could do much better if he/she tried.\n\nI require $$100.00 for the field trip next week. We will be going to $[0] on $[1].'")
55.315001132816704