Source code for lighty.templates.template

"""Module provides template two template classes:

- Template
- LazyTemplate
"""
from collections import deque
from functools import reduce
from decimal import Decimal
try:
    import cStringIO
    StringIO = cStringIO.StringIO
except:
    try:
        import StringIO as sio
        StringIO = sio.StringIO
    except:
        import io
        StringIO = io.StringIO

from .context import resolve
from .loaders import TemplateLoader
from .filter import filter_manager
from .tag import tag_manager, parse_token


[docs]class Template(object): """Class represents template. You can create template directrly in code:: template = Template('<b>Hello, {{ name }}!</b>') or load it using template loader:: template = loader.get_template('simple.html') Also you can create template and parse some text later but I do not recomend to do that: template = Template() template.parse({{ var }}) To render the template you can use execute method and pass render context as single arguments to this methods::: template.execute({'var': 'test'}) And you reciveve 'test' string as result of template execution. Or you can just call the template like a function to render template simpler way::: template({'var': 'test'}) You can also access more complex variable in you context from templates, as example dict subclasses or even object fields::: >>> template = Template('Hello, {{ user.name }} from {{ var }}') >>> template({'user': {'name': 'Peter', 'is_authenticated': True}, ... 'var': 'test'}) 'Hello, Peter from test' """ TEXT = 1 TOKEN = 2 ECHO = 3 FILTER = 4 TAG = 5 STRING = 6 CLOSE = 7 def __init__(self, text=None, loader=TemplateLoader(), name="unnamed"): """Create new template instance """ super(Template, self).__init__() self.loader = loader self.name = name self.commands = [] self.context = {} self.loader.register(name, self) if text is not None: self.parse(text) def __eq__(self, obj): return type(self) == type(obj) and self.name == obj.name @staticmethod def variable(name): def print_variable(context): return str(resolve(name, context)) return print_variable @staticmethod def constant(value): def print_constant(context): return value return print_constant @staticmethod
[docs] def filter(value): '''Parse the tamplte filter ''' parts = value.split('|') filters = [] variable = parts[0] for token in parts[1:]: if ':' in token: parsed = token.split(':') if len(parsed) > 1: filter, args_token = parsed args, types = parse_token(args_token) else: filter = parsed args, types = (), () else: filter = token args, types = (), () filters.append((filter, args, types)) def apply_filters(context): def apply_filter(value, pair): filter, args, types = pair return filter_manager.apply(filter, value, args, types, context) if variable[0] == '"' or variable[0] == "'": if variable[0] == variable[-1]: value_with_filters = [variable[1:-1]] + filters else: raise Exception('Template filter syntax error') else: try: value_with_filters = [Decimal(variable)] + filters except: value_with_filters = [resolve(variable, context)] + filters return str(reduce(apply_filter, value_with_filters)) return apply_filters
def tag(self, name, token, block): if tag_manager.is_lazy_tag(name): def execute_tag(context): return tag_manager.execute(name, token, context, block, self, self.loader) return execute_tag else: result = tag_manager.execute(name, token, self.context, block, self, self.loader) if callable(result): return result else: return lambda context: ''
[docs] def parse(self, text): """Parse template string and create appropriate command list into this template instance """ current = Template.TEXT token = '' cmds = self.commands cmd_stack = deque() tag_stack = deque() token_stack = deque() for char in text: if current == Template.TEXT: if char == '{': current = Template.TOKEN if len(token) > 0: cmds.append(Template.constant(token)) token = '' else: token += str(char) elif current == Template.TOKEN: if char == '{': current = Template.ECHO elif char == '%': current = Template.TAG else: current = Template.TEXT token = '{' + str(char) elif current == Template.ECHO or current == Template.FILTER: if char == '}': if len(token) > 0: token = token.strip() if current == Template.ECHO: cmd = Template.variable(token) else: cmd = Template.filter(token) cmds.append(cmd) token = '' current = Template.CLOSE elif char == '|': current = Template.FILTER token += str(char) else: token += str(char) elif current == Template.TAG: if char == '%': current = Template.CLOSE token = token.strip() name = token.split(' ', 1)[0] if name.startswith('end'): name = name[3:] tag = tag_stack.pop() # Close block if name == tag: block = cmds cmds = cmd_stack.pop() token = token_stack.pop() cmds.append(self.tag(name, token, block)) else: raise Exception( "Invalid closing tag: 'end%s' except 'end%s'" % (name, tag)) else: if ' ' in token: token = token.split(' ', 1)[1] else: token = '' if tag_manager.is_block_tag(name): cmd_stack.append(cmds) tag_stack.append(name) token_stack.append(token) cmds = [] else: cmds.append(self.tag(name, token, ())) token = '' else: token += str(char) elif current == Template.CLOSE: if char == '}': current = Template.TEXT else: raise Exception('Wrong template syntax') else: raise Exception('Wrong template syntax') # Check stack length - detect unclosed tags if len(cmd_stack) > 0: raise Exception('Unexpected end of input - not all tags closed') # Last value if len(token) > 0: cmds.append(Template.constant(token)) self.commands = cmds
[docs] def execute(self, context={}): """Execute all commands on a specified context Arguments: context: dict contains varibles Returns: string contains the whole result """ result = StringIO() for cmd in self.commands: result.write(cmd(context)) value = result.getvalue() result.close() return value
def __call__(self, context={}): """Alias for execute() """ return self.execute(context)
[docs] def partial(self, context, name='', key_args=()): """Execute all commands on a specified context and cache the result as another template ready for execution Arguments: context: dict contains variables Returns: another template contains the result """ result = Template(loader=self.loader, name=name) buffer = StringIO() for cmd in self.commands: try: buffer.write(cmd(context)) except Exception: value = buffer.getvalue() if len(value) > 0: result.commands.append(Template.constant(value)) result.commands.append(cmd) buffer.close() buffer = StringIO() value = buffer.getvalue() buffer.close() if len(value) > 0: result.commands.append(Template.constant(value)) return result
[docs]class LazyTemplate(Template): '''Lazy template class change the way how template loaded. :class: Template parses template context on template creation if template text provided:: >>> from lighty.templates.template import Template, LazyTemplate >>> template = Template('{{ var }}') # template already parsed >>> template.commands [<function print_variable at 0xdda0c8>] >>> lazy = LazyTemplate('{{ var }}') # not parsed >>> lazy.commands [] >>> lazy.execute({'var': 'test'}) # parse on demand and then execute 'test' >>> lazy.commands [<function print_variable at 0x1130140>] Lazy template class usefull for template loaders like a :class: lighty.templates.loader.FSLoader that requires to get the list of all the templates but does not require to parse all the templates on loading because it causes an error with templates loading order (when child template loaded before parent). Also it speed ups templates loading process because it does not require to parse all the templates when they even not used. '''
[docs] def prepare(self): '''Prepare to execution ''' self.parse = super(LazyTemplate, self).parse self.parse(self.text) del self.text execute = super(LazyTemplate, self).execute self.execute = execute return execute
[docs] def parse(self, text): '''Parse template later ''' self.text = text
[docs] def execute(self, context): '''Execute ''' return self.prepare()(context)