"""
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")