/projects/python_templating/
pythoncode_project
'''Super advanced template processor

templates:
 - single: <{identifier}>
 - list/dict: <[identifier]> content <[/identifier]>
the list/dict identifiers can be nested.
'''

import re

# pattern for identifiers
__pattern_id = r'[a-zA-Z_][a-zA-Z_0-9]*'
# single pattern: <{identifier}>
pattern_single = r'\<\{(?P<identifier>' + __pattern_id + r')\}\>'
# list/dict pattern: <[identifier]> some content <[/identifier]>
pattern_nested = r'\<\[(?P<identifier>' + __pattern_id + \
                 r')\]\>\n?(?P<content>.*?)\<\[/(?P=identifier)\]\>'

def process(template, replaces):
    '''Processes template string

    template: string to be processed.
    replaces: dictionary containing all the substitutions.
    Entries for single identifiers are converted to strings and entries for
    list/dict identifiers must be lists of dictionaries or again dictionaries.
    The replace dictionary must have the same nested structure as the template.
    '''
    # process match object for nested patterns, by recursively calling process
    def __replace_nested(m):
        sub_replaces = replaces[m.group('identifier')]

        if type(sub_replaces) is list:
            sub_templates = [process(m.group('content'), repl)
                                        for repl in sub_replaces]
            return ''.join(sub_templates).rstrip()

        elif type(sub_replaces) is dict:
            return process(m.group('content'), sub_replaces)

        else:
            raise TypeError(
                    'Expected a list or a dict as value for the id: '
                    + str(m.group('identifier'))
                    + ', but found: '
                    + str(type(sub_replaces)))

    # process match object for single patterns
    def __replace_single(m):
        return str(replaces[m.group('identifier')])

    # first process nested patterns
    template = re.sub(pattern_nested, __replace_nested, template, flags=re.DOTALL)
    # then process single patterns
    return re.sub(pattern_single, __replace_single, template, flags=re.DOTALL)