Source

coverage.py / coverage / templite.py

"""A simple Python template renderer, for a nano-subset of Django syntax."""

# Started from http://blog.ianbicking.org/templating-via-dict-wrappers.html
# and http://jtauber.com/2006/05/templates.html
# and http://code.activestate.com/recipes/496730/

import re

class Templite(object):
    """A simple template renderer, for a nano-subset of Django syntax.
    
    """
    def __init__(self, text, *contexts):
        self.loops = []
        self.text = self._prepare(text)
        self.context = {}
        for context in contexts:
            self.context.update(context)

    def render(self, context=None):
        # Make the complete context we'll use.
        ctx = dict(self.context)
        if context:
            ctx.update(context)
            
        ctxaccess = ContextAccess(ctx)
        
        # Render the loops.
        for iloop, (loopvar, listvar, loopbody) in enumerate(self.loops):
            result = ""
            for listval in ctxaccess[listvar]:
                ctx[loopvar] = listval
                result += loopbody % ctxaccess
            ctx["loop:%d" % iloop] = result
            
        # Render the final template.
        return self.text % ctxaccess

    def _prepare(self, text):
        """Convert Django-style data references into Python-native ones."""
        # Pull out loops.
        text = re.sub(
            r"(?s){% for ([a-z0-9_]+) in ([a-z0-9_.|]+) %}(.*?){% endfor %}",
            self._loop_repl, text
            )
        # Protect actual percent signs in the text.
        text = text.replace("%", "%%")
        # Convert {{foo}} into %(foo)s
        text = re.sub(r"{{([^}]+)}}", r"%(\1)s", text)
        return text

    def _loop_repl(self, match):
        nloop = len(self.loops)
        # Append (loopvar, listvar, loopbody) to self.loops
        loopvar, listvar, loopbody = match.groups()
        loopbody = self._prepare(loopbody)
        self.loops.append((loopvar, listvar, loopbody))
        return "{{loop:%d}}" % nloop


class ContextAccess(object):
    
    def __init__(self, context):
        self.context = context

    def __getitem__(self, key):
        if "|" in key:
            pipes = key.split("|")
            value = self[pipes[0]]
            for func in pipes[1:]:
                value = self[func](value)
        elif "." in key:
            dots = key.split('.') 
            value = self[dots[0]]
            for dot in dots[1:]:
                try:
                    value = getattr(value, dot)
                except AttributeError:
                    value = value[dot]
                if callable(value):
                    value = value()
        else:
            value = self.context[key]
        return value