--- /dev/null
+# -*- coding: utf-8 -*-
+"""
+ Jinja Sandboxed Template Engine
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ :copyright: 2006 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
--- /dev/null
+# -*- coding: utf-8 -*-
+"""
+ jinja.ast
+ ~~~~~~~~~
+
+ Advance Syntax Tree for jinja.
+
+ :copyright: 2006 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+
+class Node(object):
+ """
+ Baseclass of all nodes. For instance checking.
+ """
+ __slots__ = ()
+
+ def __init__(self):
+ raise TypeError('cannot create %r instances' %
+ self.__class__.__name__)
+
+
+class Expression(list, Node):
+ """
+ Node that helds childnodes. Normally just used temporary.
+ """
+ __slots__ = ()
+
+ def __init__(self, *args, **kwargs):
+ super(Expression, self).__init__(*args, **kwargs)
+
+ def __repr__(self):
+ return 'Expression(%s)' % list.__repr__(self)
+
+
+class Comment(Node):
+ """
+ Node that helds a comment. We keep comments in the data
+ if some translator wants to forward it into the generated
+ code (for example the python translator).
+ """
+ __slots__ = ('pos', 'comment',)
+
+ def __init__(self, pos, comment):
+ self.pos = pos
+ self.comment = comment
+
+ def __repr__(self):
+ return 'Comment(%r, %r)' % (self.pos, self.comment)
+
+
+class Page(Node):
+ """
+ Node that helds all root nodes.
+ """
+ __slots__ = ('filename', 'nodes')
+
+ def __init__(self, filename, nodes):
+ self.filename = filename
+ self.nodes = nodes
+
+ def __repr__(self):
+ return 'Page(%r, %r)' % (
+ self.filename,
+ self.nodes
+ )
+
+
+class Variable(Node):
+ """
+ Node for variables
+ """
+ __slots__ = ('pos', 'expression')
+
+ def __init__(self, pos, expression):
+ self.pos = pos
+ self.expression = expression
+
+ def __repr__(self):
+ return 'Variable(%r)' % self.expression
+
+
+class Data(Node):
+ """
+ Node for data outside of tags.
+ """
+ __slots__ = ('pos', 'data')
+
+ def __init__(self, pos, data):
+ self.pos = pos
+ self.data = data
+
+ def __repr__(self):
+ return 'Data(%d, %r)' % (self.pos, self.data)
+
+
+class Name(Node):
+ """
+ Node for names.
+ """
+ __slots__ = ('pos', 'data')
+
+ def __init__(self, pos, data):
+ self.pos = pos
+ self.data = data
+
+ def __repr__(self):
+ return 'Name(%d, %r)' % (self.pos, self.data)
--- /dev/null
+# -*- coding: utf-8 -*-
+"""
+ jinja.datastructure
+ ~~~~~~~~~~~~~~~~~~~
+
+ Module that helds several data types used in the template engine.
+
+ :copyright: 2006 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+
+# python2.3 compatibility. do not use this method for anything else
+# then context reversing.
+try:
+ _reversed = reversed
+except NameError:
+ def _reversed(c):
+ return c[::-1]
+
+
+class UndefinedType(object):
+ """
+ An object that does not exist.
+ """
+
+ def __init__(self):
+ try:
+ Undefined
+ except NameError:
+ pass
+ else:
+ raise TypeError('cannot create %r instances' %
+ self.__class__.__name__)
+
+ def __getitem__(self, arg):
+ return self
+
+ def __iter__(self):
+ return iter(int, 0)
+
+ def __getattr__(self, arg):
+ return self
+
+ def __nonzero__(self):
+ return False
+
+ def __len__(self):
+ return 0
+
+ def __str__(self):
+ return ''
+
+ def __unicode__(self):
+ return u''
+
+ def __int__(self):
+ return 0
+
+ def __float__(self):
+ return 1
+
+
+Undefined = UndefinedType()
+
+
+class Context(object):
+ """
+ Dict like object.
+ """
+
+ __slots__ = ('stack')
+
+ def __init__(*args, **kwargs):
+ try:
+ self = args[0]
+ self.environment = args[1]
+ initial = dict(*args[2:], **kwargs)
+ except:
+ raise TypeError('%r requires environment as first argument. '
+ 'The rest of the arguments are forwarded to '
+ 'the default dict constructor.')
+ initial.update(
+ false=False,
+ true=True,
+ none=None
+ )
+ self._stack = [initial, {}]
+
+ def pop(self):
+ if len(self._stack) <= 2:
+ raise ValueError('cannot pop initial layer')
+ return self._stack.pop()
+
+ def push(self, data=None):
+ self._stack.append(data or {})
+
+ def __getitem__(self, name):
+ for d in _reversed(self._stack):
+ if name in d:
+ return d[name]
+ return Undefined
+
+ def __setitem__(self, name, value):
+ self._stack[-1][name] = value
+
+ def __delitem__(self, name):
+ if name in self._stack[-1]:
+ del self._stack[-1][name]
+
+ def __repr__(self):
+ tmp = {}
+ for d in self._stack:
+ for key, value in d.iteritems():
+ tmp[key] = value
+ return 'Context(%s)' % repr(tmp)
+
+
+class TokenStream(object):
+ """
+ A token stream works like a normal generator just that
+ it supports pushing tokens back to the stream.
+ """
+
+ def __init__(self, generator):
+ self._generator = generator
+ self._pushed = []
+ self.last = (0, 'initial', '')
+
+ def __iter__(self):
+ return self
+
+ def __nonzero__(self):
+ """Are we at the end of the tokenstream?"""
+ if self._pushed:
+ return True
+ try:
+ self.push(self.next())
+ except StopIteration:
+ return False
+ return True
+
+ eos = property(lambda x: not x.__nonzero__(), doc=__nonzero__.__doc__)
+
+ def next(self):
+ """Return the next token from the stream."""
+ if self._pushed:
+ rv = self._pushed.pop()
+ else:
+ rv = self._generator.next()
+ self.last = rv
+ return rv
+
+ def look(self):
+ """Pop and push a token, return it."""
+ token = self.next()
+ self.push(*token)
+ return token
+
+ def fetch_until(self, test, drop_needle=False):
+ """Fetch tokens until a function matches."""
+ try:
+ while True:
+ token = self.next()
+ if test(*token):
+ if not drop_needle:
+ self.push(*token)
+ return
+ else:
+ yield token
+ except StopIteration:
+ raise IndexError('end of stream reached')
+
+ def push(self, pos, token, data):
+ """Push an yielded token back to the stream."""
+ self._pushed.append((pos, token, data))
--- /dev/null
+# -*- coding: utf-8 -*-
+"""
+ jinja.defaults
+ ~~~~~~~~~~~~~~
+
+ Jinja default filters and tags.
+
+ :copyright: 2006 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+
+
+DEFAULT_TAGS = {}
+
+DEFAULT_FILTERS = {}
--- /dev/null
+# -*- coding: utf-8 -*-
+"""
+ jinja.environment
+ ~~~~~~~~~~~~~~~~~
+
+ Provides a class that holds runtime and parsing time options.
+
+ :copyright: 2006 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+from jinja.lexer import Lexer
+from jinja.parser import Parser
+from jinja.exceptions import TagNotFound, FilterNotFound
+from jinja.defaults import DEFAULT_TAGS, DEFAULT_FILTERS
+
+
+class Environment(object):
+ """
+ The jinja environment.
+ """
+
+ def __init__(self,
+ block_start_string='{%',
+ block_end_string='%}',
+ variable_start_string='{{',
+ variable_end_string='}}',
+ comment_start_string='{#',
+ comment_end_string='#}',
+ template_charset='utf-8',
+ charset='utf-8',
+ loader=None,
+ tags=None,
+ filters=None):
+
+ # lexer / parser information
+ self.block_start_string = block_start_string
+ self.block_end_string = block_end_string
+ self.variable_start_string = variable_start_string
+ self.variable_end_string = variable_end_string
+ self.comment_start_string = comment_start_string
+ self.comment_end_string = comment_end_string
+
+ # other stuff
+ self.template_charset = template_charset
+ self.charset = charset
+ self.loader = loader
+ self.tags = tags or DEFAULT_TAGS.copy()
+ self.filters = filters or DEFAULT_FILTERS.copy()
+
+ # create lexer
+ self.lexer = Lexer(self)
+
+ def parse(self, source):
+ """Function that creates a new parser and parses the source."""
+ parser = Parser(self, source)
+ return parser.parse_page()
+
+ def get_tag(self, name):
+ """
+ Return the tag for a specific name. Raise a `TagNotFound` exception
+ if a tag with this name is not registered.
+ """
+ if name not in self._tags:
+ raise TagNotFound(name)
+ return self._tags[name]
+
+ def get_filter(self, name):
+ """
+ Return the filter for a given name. Raise a `FilterNotFound` exception
+ if the requested filter is not registered.
+ """
+ if name not in self._filters:
+ raise FilterNotFound(name)
+ return self._filters[name]
+
+ def to_unicode(self, value):
+ """
+ Convert a value to unicode with the rules defined on the environment.
+ """
+ if isinstance(value, unicode):
+ return value
+ else:
+ try:
+ return unicode(value)
+ except UnicodeError:
+ return str(value).decode(self.charset, 'ignore')
--- /dev/null
+# -*- coding: utf-8 -*-
+"""
+ jinja.exceptions
+ ~~~~~~~~~~~~~~~~
+
+ Jinja exceptions.
+
+ :copyright: 2006 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+
+
+class TemplateError(RuntimeError):
+ pass
+
+
+class TagNotFound(KeyError, TemplateError):
+ """
+ The parser looked for a specific tag in the tag library but was unable to find
+ one.
+ """
+
+ def __init__(self, tagname):
+ super(TagNotFound, self).__init__('The tag %r does not exist.' % tagname)
+ self.tagname = tagname
+
+
+class FilterNotFound(KeyError, TemplateError):
+ """
+ The template engine looked for a filter but was unable to find it.
+ """
+
+ def __init__(self, filtername):
+ super(FilterNotFound, self).__init__('The filter %r does not exist.' % filtername)
+ self.filtername = filtername
+
+
+class TemplateSyntaxError(SyntaxError, TemplateError):
+ """
+ Raised to tell the user that there is a problem with the template.
+ """
+
+ def __init__(self, message, pos):
+ super(TemplateSyntaxError, self).__init__(message)
+ self.pos = pos
+
+
+class TemplateRuntimeError(TemplateError):
+ """
+ Raised by the template engine if a tag encountered an error when
+ rendering.
+ """
+
+ def __init__(self, message, pos):
+ super(TemplateRuntimeError, self).__init__(message)
+ self.pos = pos
--- /dev/null
+# -*- coding: utf-8 -*-
+"""
+ jinja.filters
+ ~~~~~~~~~~~~~
+
+ Bundled jinja filters.
+
+ :copyright: 2006 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+
+
+def stringfilter(f):
+ """
+ Decorator for filters that just work on unicode objects.
+ """
+ def decorator(*args):
+ def wrapped(env, context, value):
+ args = list(args)
+ for idx, var in enumerate(args):
+ if isinstance(var, str):
+ args[idx] = env.to_unicode(var)
+ return f(env.to_unicode(value), *args)
+ return wrapped
+ return decorator
+
+
+def do_replace(s, old, new, count=None):
+ """
+ {{ s|replace(old, new, count=None) }}
+
+ Return a copy of s with all occurrences of substring
+ old replaced by new. If the optional argument count is
+ given, only the first count occurrences are replaced.
+ """
+ if count is None:
+ return s.replace(old, new)
+ return s.replace(old, new, count)
+do_replace = stringfilter(do_replace)
+
+
+def do_upper(s):
+ """
+ {{ s|upper }}
+
+ Return a copy of s converted to uppercase.
+ """
+ return s.upper()
+do_upper = stringfilter(do_upper)
+
+
+def do_lower(s):
+ """
+ {{ s|lower }}
+
+ Return a copy of s converted to lowercase.
+ """
+ return s.lower()
+do_lower = stringfilter(do_lower)
+
+
+def do_escape(s, attribute=False):
+ """
+ {{ s|escape(attribute) }}
+
+ XML escape &, <, and > in a string of data. If attribute is
+ True it also converts ``"`` to ``"``
+ """
+ s = s.replace("&", "&").replace("<", "<").replace(">", ">")
+ if attribute:
+ s = s.replace('"', """)
+ return s
+do_escape = stringfilter(do_escape)
+
+
+def do_addslashes(s):
+ """
+ {{ s|addslashes }}
+
+ Adds slashes to s.
+ """
+ return s.encode('utf-8').encode('string-escape').decode('utf-8')
+do_addslashes = stringfilter(do_addslashes)
+
+
+def do_capitalize(s):
+ """
+ {{ s|capitalize }}
+
+ Return a copy of the string s with only its first character
+ capitalized.
+ """
+ return s.capitalize()
+do_capitalize = stringfilter(do_capitalize)
+
+
+def do_title(s):
+ """
+ {{ s|title }}
+
+ Return a titlecased version of s, i.e. words start with uppercase
+ characters, all remaining cased characters have lowercase.
+ """
+ return s.title()
+do_title = stringfilter(do_title)
+
+
+def do_default(default_value=u''):
+ """
+ {{ s|default(default_value='') }}
+
+ In case of s isn't set or True default will return default_value
+ which is '' per default.
+ """
+ return lambda e, c, v: v or default_value
+
+
+def do_join(d=u''):
+ """
+ {{ sequence|join(d='') }}
+
+ Return a string which is the concatenation of the strings in the
+ sequence. The separator between elements is d which is an empty
+ string per default.
+ """
+ def wrapped(env, context, value):
+ d = env.to_unicode(d)
+ return d.join([env.to_unicode(x) for x in value])
+ return wrapped
+
+
+def do_count():
+ """
+ {{ var|count }}
+
+ Return the length of var. In case if getting an integer or float
+ it will convert it into a string an return the length of the new
+ string.
+ If the object doesn't provide a __len__ function it will return
+ zero.st(value)
+ l.reverse()
+ return
+ """
+ def wrapped(env, context, value):
+ try:
+ if type(value) in (int, float, long):
+ return len(str(var))
+ return len(var)
+ except TypeError:
+ return 0
+ return wrapped
+
+
+def do_odd():
+ """
+ {{ var|odd }}
+
+ Return true if the variable is odd.
+ """
+ return lambda e, c, v: v % 2 == 1
+
+
+def do_even():
+ """
+ {{ var|even }}
+
+ Return true of the variable is even.
+ """
+ return lambda e, c, v: v % 2 == 0
+
+
+def do_reversed():
+ """
+ {{ var|reversed }}
+
+ Return a reversed list of the iterable filtered.
+ """
+ def wrapped(env, context, value):
+ try:
+ return value[::-1]
+ except:
+ l = list(value)
+ l.reverse()
+ return l
+ return wrapped
+
+
+FILTERS = {
+ 'replace': do_replace,
+ 'upper': do_upper,
+ 'lower': do_lower,
+ 'escape': do_escape,
+ 'e': do_escape,
+ 'addslashes': do_addslashes,
+ 'capitalize': do_capitalize,
+ 'title': do_title,
+ 'default': do_default,
+ 'join': do_join,
+ 'count': do_count,
+ 'odd': do_odd,
+ 'even': do_even,
+ 'reversed': do_reversed
+}
--- /dev/null
+# -*- coding: utf-8 -*-
+"""
+ jinja.lexer
+ ~~~~~~~~~~~
+"""
+import re
+from jinja.datastructure import TokenStream
+from jinja.exceptions import TemplateSyntaxError
+
+
+# static regular expressions
+whitespace_re = re.compile(r'\s+(?m)')
+name_re = re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*[!?]?')
+string_re = re.compile(r"('([^'\\]*(?:\\.[^'\\]*)*)'"
+ r'|"([^"\\]*(?:\\.[^"\\]*)*)")(?ms)')
+number_re = re.compile(r'\d+(\.\d+)*')
+
+operator_re = re.compile('(%s)' % '|'.join(
+ isinstance(x, unicode) and str(x) or re.escape(x) for x in [
+ # math operators
+ '+', '-', '*', '/', '%',
+ # braces and parenthesis
+ '[', ']', '(', ')', '{', '}',
+ # attribute access and comparison / logical operators
+ '.', ',', '|', '==', '<', '>', '<=', '>=', '!=',
+ ur'or\b', ur'and\b', ur'not\b'
+]))
+
+
+class Failure(object):
+ """
+ Class that raises a `TemplateSyntaxError` if called.
+ Used by the `Lexer` to specify known errors.
+ """
+
+ def __init__(self, message, cls=TemplateSyntaxError):
+ self.message = message
+ self.error_class = cls
+
+ def __call__(self, position):
+ raise self.error_class(self.message, position)
+
+
+class Lexer(object):
+ """
+ Class that implements a lexer for a given environment. Automatically
+ created by the environment class, usually you don't have to do that.
+ """
+
+ def __init__(self, environment):
+ # shortcuts
+ c = lambda x: re.compile(x, re.M | re.S)
+ e = re.escape
+
+ # parsing rules for tags
+ tag_rules = [
+ (whitespace_re, None, None),
+ (number_re, 'number', None),
+ (operator_re, 'operator', None),
+ (name_re, 'name', None),
+ (string_re, 'string', None)
+ ]
+
+ # global parsing rules
+ self.rules = {
+ 'root': [
+ (c('(.*?)(?:(?P<comment_begin>' +
+ e(environment.comment_start_string) +
+ ')|(?P<block_begin>' +
+ e(environment.block_start_string) +
+ ')|(?P<variable_begin>' +
+ e(environment.variable_start_string) +
+ '))'), ('data', '#bygroup'), '#bygroup'),
+ (c('.+'), 'data', None)
+ ],
+ 'comment_begin': [
+ (c(r'(.*?)(%s)' % e(environment.comment_end_string)),
+ ('comment', 'comment_end'), '#pop'),
+ (c('(.)'), (Failure('Missing end of comment tag'),), None)
+ ],
+ 'block_begin': [
+ (c(e(environment.block_end_string)), 'block_end', '#pop')
+ ] + tag_rules,
+ 'variable_begin': [
+ (c(e(environment.variable_end_string)), 'variable_end',
+ '#pop')
+ ] + tag_rules
+ }
+
+ def tokenize(self, source):
+ """
+ Simple tokenize function that yields ``(position, type, contents)``
+ tuples. Wrap the generator returned by this function in a
+ `TokenStream` to get real token instances and be able to push tokens
+ back to the stream. That's for example done by the parser.
+ """
+ return TokenStream(self.tokeniter(source))
+
+ def tokeniter(self, source):
+ """
+ This method tokenizes the text and returns the tokens in a generator.
+ Normally it's a better idea to use the `tokenize` function which
+ returns a `TokenStream` but in some situations it can be useful
+ to use this function since it can be marginally faster.
+ """
+ pos = 0
+ stack = ['root']
+ statetokens = self.rules['root']
+ source_length = len(source)
+
+ while True:
+ # tokenizer loop
+ for regex, tokens, new_state in statetokens:
+ m = regex.match(source, pos)
+ if m:
+ # tuples support more options
+ if isinstance(tokens, tuple):
+ for idx, token in enumerate(tokens):
+ # hidden group
+ if token is None:
+ continue
+ # failure group
+ elif isinstance(token, Failure):
+ raise token(m.start(idx + 1))
+ # bygroup is a bit more complex, in that case we
+ # yield for the current token the first named
+ # group that matched
+ elif token == '#bygroup':
+ for key, value in m.groupdict().iteritems():
+ if value is not None:
+ yield m.start(key), key, value
+ break
+ else:
+ raise RuntimeError('%r wanted to resolve '
+ 'the token dynamically'
+ ' but no group matched'
+ % regex)
+ # normal group
+ else:
+ data = m.group(idx + 1)
+ if data:
+ yield m.start(idx + 1), token, data
+ # strings as token just are yielded as it, but just
+ # if the data is not empty
+ else:
+ data = m.group()
+ if tokens is not None:
+ if data:
+ yield pos, tokens, data
+ # fetch new position into new variable so that we can check
+ # if there is a internal parsing error which would result
+ # in an infinite loop
+ pos2 = m.end()
+ # handle state changes
+ if new_state is not None:
+ # remove the uppermost state
+ if new_state == '#pop':
+ stack.pop()
+ # resolve the new state by group checking
+ elif new_state == '#bygroup':
+ for key, value in m.groupdict().iteritems():
+ if value is not None:
+ stack.append(key)
+ break
+ else:
+ raise RuntimeError('%r wanted to resolve the '
+ 'new state dynamically but'
+ ' no group matched' %
+ regex)
+ # direct state name given
+ else:
+ stack.append(new_state)
+ statetokens = self.rules[stack[-1]]
+ # we are still at the same position and no stack change.
+ # this means a loop without break condition, avoid that and
+ # raise error
+ elif pos2 == pos:
+ raise RuntimeError('%r yielded empty string without '
+ 'stack change' % regex)
+ # publish new function and start again
+ pos = pos2
+ break
+ # if loop terminated without break we havn't found a single match
+ # either we are at the end of the file or we have a problem
+ else:
+ # end of text
+ if pos >= source_length:
+ return
+ # something went wrong
+ raise TemplateSyntaxError('unexpected char %r at %d' %
+ (source[pos], pos), pos)
--- /dev/null
+# -*- coding: utf-8 -*-
+"""
+ jinja.nodes
+ ~~~~~~~~~~~
+
+ Additional nodes for jinja. Look like nodes from the ast.
+
+ :copyright: 2006 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+from compiler.ast import Node
+
+
+class Text(Node):
+ """
+ Node that represents normal text.
+ """
+
+ def __init__(self, pos, text):
+ self.pos = pos
+ self.text = text
+
+ def __repr__(self):
+ return 'Text(%r)' % (self.text,)
+
+
+class NodeList(list, Node):
+ """
+ A node that stores multiple childnodes.
+ """
+
+ def __init__(self, pos, data=None):
+ self.pos = pos
+ list.__init__(self, data or ())
+
+ def __repr__(self):
+ return 'NodeList(%s)' % list.__repr__(self)
+
+
+class ForLoop(Node):
+ """
+ A node that represents a for loop
+ """
+
+ def __init__(self, pos, item, seq, body, else_):
+ self.pos = pos
+ self.item = item
+ self.seq = seq
+ self.body = body
+ self.else_ = else_
+
+ def __repr__(self):
+ return 'ForLoop(%r, %r, %r, %r)' % (
+ self.item,
+ self.seq,
+ self.body,
+ self.else_
+ )
+
+
+class IfCondition(Node):
+ """
+ A node that represents an if condition.
+ """
+
+ def __init__(self, pos, test, body, else_):
+ self.pos = pos
+ self.test = test
+ self.body = body
+ self.else_ = else_
+
+ def __repr__(self):
+ return 'IfCondition(%r, %r, %r)' % (
+ self.test,
+ self.body,
+ self.else_
+ )
+
+
+class Print(Node):
+ """
+ A node that represents variable tags and print calls
+ """
+
+ def __init__(self, pos, variable):
+ self.pos = pos
+ self.variable = variable
+
+ def __repr__(self):
+ return 'Print(%r)' % (self.variable,)
--- /dev/null
+# -*- coding: utf-8 -*-
+"""
+ jinja.parser
+ ~~~~~~~~~~~~
+
+ Implements the template parser.
+
+ :copyright: 2006 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+import re
+from compiler import ast, parse
+from jinja import nodes
+from jinja.datastructure import TokenStream
+from jinja.exceptions import TemplateSyntaxError
+
+
+# callback functions for the subparse method
+end_of_block = lambda p, t, d: t == 'block_end'
+end_of_variable = lambda p, t, d: t == 'variable_end'
+switch_for = lambda p, t, d: t == 'name' and d in ('else', 'endfor')
+end_of_for = lambda p, t, d: t == 'name' and d == 'endfor'
+switch_if = lambda p, t, d: t == 'name' and d in ('else', 'endif')
+end_of_if = lambda p, t, d: t == 'name' and d == 'endif'
+
+
+class Parser(object):
+ """
+ The template parser class.
+
+ Transforms sourcecode into an abstract syntax tree::
+
+ >>> parse("{% for item in seq|reversed %}{{ item }}{% endfor %}")
+ Document(ForLoop(AssignName('item'), Filter(Name('seq'), Name('reversed')),
+ Print('item'), None))
+ >>> parse("{% if true %}foo{% else %}bar{% endif %}")
+ Document(IfCondition(Name('true'), Data('foo'), Data('bar')))
+ >>> parse("{% if false %}...{% elif 0 > 1 %}...{% else %}...{% endif %}")
+ Document(IfCondition(Name('false'), Data('...'),
+ IfCondition(Compare('>', Const(0), Const(1)),
+ Data('...'), Data('...'))))
+ """
+
+ def __init__(self, environment, source, filename=None):
+ self.environment = environment
+ if isinstance(source, str):
+ source = source.decode(environment.template_charset, 'ignore')
+ self.source = source
+ self.filename = filename
+ self.tokenstream = environment.lexer.tokenize(source)
+ self._parsed = False
+
+ self.directives = {
+ 'for': self.handle_for_directive,
+ 'if': self.handle_if_directive,
+ 'print': self.handle_print_directive
+ }
+
+ def handle_for_directive(self, pos, gen):
+ """
+ Handle a for directive and return a ForLoop node
+ """
+ ast = self.parse_python(pos, gen, 'for %s:pass\nelse:pass')
+ body = self.subparse(switch_for)
+
+ # do we have an else section?
+ if self.tokenstream.next()[2] == 'else':
+ self.close_remaining_block()
+ else_ = self.subparse(end_of_for, True)
+ else:
+ else_ = None
+ self.close_remaining_block()
+
+ return nodes.ForLoop(pos, ast.assign, ast.list, body, else_)
+
+ def handle_if_directive(self, pos, gen):
+ """
+ Handle if/else blocks. elif is not supported by now.
+ """
+ ast = self.parse_python(pos, gen, 'if %s:pass\nelse:pass')
+ body = self.subparse(switch_if)
+
+ # do we have an else section?
+ if self.tokenstream.next()[2] == 'else':
+ self.close_remaining_block()
+ else_ = self.subparse(end_of_if, True)
+ else:
+ else_ = None
+ self.close_remaining_block()
+
+ return nodes.IfCondition(pos, ast.tests[0][0], body, else_)
+
+ def handle_print_directive(self, pos, gen):
+ """
+ Handle {{ foo }} and {% print foo %}.
+ """
+ ast = self.parse_python(pos, gen, 'print_(%s)')
+ # ast is something like Discard(CallFunc(Name('print_'), ...))
+ # so just use the args
+ arguments = ast.expr.args
+ # we only accept one argument
+ if len(arguments) != 1:
+ raise TemplateSyntaxError('invalid argument count for print; '
+ 'print requires exactly one argument, '
+ 'got %d.' % len(arguments), pos)
+ return nodes.Print(pos, arguments[0])
+
+ def parse_python(self, pos, gen, template='%s'):
+ """
+ Convert the passed generator into a flat string representing
+ python sourcecode and return an ast node or raise a
+ TemplateSyntaxError.
+ """
+ tokens = []
+ for t_pos, t_token, t_data in gen:
+ if t_token == 'string':
+ tokens.append('u' + t_data)
+ else:
+ tokens.append(t_data)
+ source = '\xef\xbb\xbf' + (template % (u' '.join(tokens)).encode('utf-8'))
+ try:
+ ast = parse(source, 'exec')
+ except SyntaxError, e:
+ raise TemplateSyntaxError(str(e), pos + e.offset - 1)
+ assert len(ast.node.nodes) == 1, 'get %d nodes, 1 expected' % len(ast.node.nodes)
+ return ast.node.nodes[0]
+
+ def parse(self):
+ """
+ Parse the template and return a nodelist.
+ """
+ return self.subparse(None)
+
+ def subparse(self, test, drop_needle=False):
+ """
+ Helper function used to parse the sourcecode until the test
+ function which is passed a tuple in the form (pos, token, data)
+ returns True. In that case the current token is pushed back to
+ the tokenstream and the generator ends.
+
+ The test function is only called for the first token after a
+ block tag. Variable tags are *not* aliases for {% print %} in
+ that case.
+
+ If drop_needle is True the needle_token is removed from the tokenstream.
+ """
+ def finish():
+ """Helper function to remove unused nodelists."""
+ if len(result) == 1:
+ return result[0]
+ return result
+
+ pos = self.tokenstream.last[0]
+ result = nodes.NodeList(pos)
+ for pos, token, data in self.tokenstream:
+ # this token marks the begin or a variable section.
+ # parse everything till the end of it.
+ if token == 'variable_begin':
+ gen = self.tokenstream.fetch_until(end_of_variable, True)
+ result.append(self.directives['print'](pos, gen))
+
+ # this token marks the start of a block. like for variables
+ # just parse everything until the end of the block
+ elif token == 'block_begin':
+ gen = self.tokenstream.fetch_until(end_of_block, True)
+ try:
+ pos, token, data = gen.next()
+ except StopIteration:
+ raise TemplateSyntaxError('unexpected end of block', pos)
+
+ # first token *must* be a name token
+ if token != 'name':
+ raise TemplateSyntaxError('unexpected %r token' % token, pos)
+
+ # if a test function is passed to subparse we check if we
+ # reached the end of such a requested block.
+ if test is not None and test(pos, token, data):
+ if not drop_needle:
+ self.tokenstream.push(pos, token, data)
+ return finish()
+
+ # the first token tells us which directive we want to call.
+ # if if doesn't match any existing directive it's like a
+ # template syntax error.
+ if data in self.directives:
+ node = self.directives[data](pos, gen)
+ else:
+ raise TemplateSyntaxError('unknown directive %r' % data, pos)
+ result.append(node)
+
+ # here the only token we should get is "data". all other
+ # tokens just exist in block or variable sections. (if the
+ # tokenizer is not brocken)
+ elif token == 'data':
+ result.append(nodes.Text(pos, data))
+
+ # so this should be unreachable code
+ else:
+ raise AssertionError('unexpected token %r' % token)
+
+ # still here and a test function is provided? raise and error
+ if test is not None:
+ raise TemplateSyntaxError('unexpected end of template', pos)
+ return finish()
+
+ def close_remaining_block(self):
+ """
+ If we opened a block tag because one of our tags requires an end
+ tag we can use this method to drop the rest of the block from
+ the stream. If the next token isn't the block end we throw an
+ error.
+ """
+ pos = self.tokenstream.last[0]
+ try:
+ pos, token, data = self.tokenstream.next()
+ except StopIteration:
+ raise TemplateSyntaxError('missing closing tag', pos)
+ if token != 'block_end':
+ raise TemplateSyntaxError('expected close tag, found %r' % token, pos)
--- /dev/null
+# -*- coding: utf-8 -*-
+"""
+ jinja.translators
+ ~~~~~~~~~~~~~~~~~
+
+ The submodules of this module provide translators for the jinja ast
+ which basically just is the python ast with a few more nodes.
+
+ :copyright: 2006 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
--- /dev/null
+# -*- coding: utf-8 -*-
+"""
+ jinja.translators.python
+ ~~~~~~~~~~~~~~~~~~~~~~~~
+
+ This module translates a jinja ast into python code.
+
+ :copyright: 2006 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+from compiler import ast
+from jinja import nodes
+
+
+class PythonTranslator(object):
+ """
+ Pass this translator a ast tree to get valid python code.
+ """
+
+ def __init__(self, environment, node):
+ self.environment = environment
+ self.node = node
+ self.indention = 0
+ self.last_pos = 0
+
+ self.handlers = {
+ # jinja nodes
+ nodes.Text: self.handle_template_text,
+ nodes.NodeList: self.handle_node_list,
+ nodes.ForLoop: self.handle_for_loop,
+ nodes.IfCondition: self.handle_if_condition,
+ nodes.Print: self.handle_print,
+ # used python nodes
+ ast.Name: self.handle_name,
+ ast.AssName: self.handle_name,
+ ast.Compare: self.handle_compare,
+ ast.Const: self.handle_const,
+ ast.Subscript: self.handle_subscript,
+ ast.Getattr: self.handle_getattr,
+ ast.AssTuple: self.handle_ass_tuple,
+ ast.Bitor: self.handle_bitor,
+ ast.CallFunc: self.handle_call_func,
+ ast.Add: self.handle_add,
+ ast.Sub: self.handle_sub,
+ ast.Div: self.handle_div,
+ ast.Mul: self.handle_mul,
+ ast.Mod: self.handle_mod,
+ ast.UnarySub: self.handle_unary_sub,
+ ast.Power: self.handle_power,
+ ast.Dict: self.handle_dict,
+ ast.List: self.handle_list,
+ ast.Tuple: self.handle_list,
+ ast.And: self.handle_and,
+ ast.Or: self.handle_or,
+ ast.Not: self.handle_not
+ }
+
+ def indent(self, text):
+ """
+ Indent the current text.
+ """
+ return (' ' * (self.indention * 4)) + text
+
+ def handle_node(self, node):
+ """
+ Handle one node
+ """
+ if node.__class__ in self.handlers:
+ out = self.handlers[node.__class__](node)
+ else:
+ raise AssertionError('unhandled node %r' % node.__class__)
+ return out
+
+ # -- jinja nodes
+
+ def handle_node_list(self, node):
+ """
+ In some situations we might have a node list. It's just
+ a collection of multiple statements.
+ """
+ self.last_pos = node.pos
+ buf = []
+ for n in node:
+ buf.append(self.handle_node(n))
+ return '\n'.join(buf)
+
+ def handle_for_loop(self, node):
+ """
+ Handle a for loop. Pretty basic, just that we give the else
+ clause a different behavior.
+ """
+ self.last_pos = node.pos
+ buf = []
+ write = lambda x: buf.append(self.indent(x))
+ write('context.push()')
+ write('parent_loop = context[\'loop\']')
+ write('loop_data = None')
+ write('for (loop_data, %s) in environment.iterate(%s):' % (
+ self.handle_node(node.item),
+ self.handle_node(node.seq)
+ ))
+ self.indention += 1
+ write('loop_data.parent = parent_loop')
+ write('context[\'loop\'] = loop_data')
+ buf.append(self.handle_node(node.body))
+ self.indention -= 1
+ if node.else_ is not None:
+ write('if loop_data is None:')
+ self.indention += 1
+ buf.append(self.handle_node(node.else_))
+ self.indention -= 1
+ write('context.pop()')
+ return '\n'.join(buf)
+
+ def handle_if_condition(self, node):
+ """
+ Handle an if condition node.
+ """
+ self.last_pos = node.pos
+ buf = []
+ write = lambda x: buf.append(self.indent(x))
+ write('if %s:' % self.handle_node(node.test))
+ self.indention += 1
+ buf.append(self.handle_node(node.body))
+ self.indention -= 1
+ if node.else_ is not None:
+ write('else:')
+ self.indention += 1
+ buf.append(self.handle_node(node.else_))
+ self.indention -= 1
+ return '\n'.join(buf)
+
+ def handle_print(self, node):
+ """
+ Handle a print statement.
+ """
+ self.last_pos = node.pos
+ return self.indent('write_var(%s)' % self.handle_node(node.variable))
+
+ def handle_template_text(self, node):
+ """
+ Handle data around nodes.
+ """
+ self.last_pos = node.pos
+ return self.indent('write(%r)' % node.text)
+
+ # -- python nodes
+
+ def handle_name(self, node):
+ """
+ Handle name assignments and name retreivement.
+ """
+ return 'context[%r]' % node.name
+
+ def handle_compare(self, node):
+ """
+ Any sort of comparison
+ """
+ buf = []
+ buf.append(self.handle_node(node.expr))
+ for op, n in node.ops:
+ buf.append(op)
+ buf.append(self.handle_node(n))
+ return ' '.join(buf)
+
+ def handle_const(self, node):
+ """
+ Constant values in expressions.
+ """
+ return repr(node.value)
+
+ def handle_subscript(self, node):
+ """
+ Handle variable based attribute access foo['bar'].
+ """
+ if len(node.subs) != 1:
+ raise TemplateSyntaxError('attribute access requires one argument',
+ self.last_pos)
+ assert node.flags != 'OP_DELETE', 'wtf? do we support that?'
+ return 'get_attribute(%s, %s)' % (
+ self.handle_node(node.expr),
+ self.handle_node(node.subs[0])
+ )
+
+ def handle_getattr(self, node):
+ """
+ Handle hardcoded attribute access. foo.bar
+ """
+ return 'get_attribute(%s, %r)' % (
+ self.handle_node(node.expr),
+ node.attrname
+ )
+
+ def handle_ass_tuple(self, node):
+ """
+ Tuple unpacking loops.
+ """
+ return '(%s)' % ', '.join([self.handle_node(n) for n in node.nodes])
+
+ def handle_bitor(self, node):
+ """
+ We use the pipe operator for filtering.
+ """
+ return 'environment.apply_filters(%s, %r)' % (
+ self.handle_node(node.nodes[0]),
+ [self.handle_node(n) for n in node.nodes[1:]]
+ )
+
+ def handle_call_func(self, node):
+ """
+ Handle function calls.
+ """
+ args = []
+ kwargs = {}
+ star_args = dstar_args = None
+ if node.star_args is not None:
+ star_args = self.handle_node(node.star_args)
+ if node.dstar_args is not None:
+ dstar_args = self.handle_node(node.dstar_args)
+ for arg in node.args:
+ if arg.__class__ is ast.Keyword:
+ kwargs[arg.name] = self.handle_node(arg.expr)
+ else:
+ args.append(self.handle_node(arg))
+ return 'environment.call_function(%s, [%s], {%s}, %s, %s)' % (
+ self.handle_node(node.node),
+ ', '.join(args),
+ ', '.join(['%r: %s' % i for i in kwargs.iteritems()]),
+ star_args,
+ dstar_args
+ )
+
+ def handle_add(self, node):
+ """
+ Add two items.
+ """
+ return '(%s + %s)' % (
+ self.handle_node(node.left),
+ self.handle_node(node.right)
+ )
+
+ def handle_sub(self, node):
+ """
+ Sub two items.
+ """
+ return '(%s - %s)' % (
+ self.handle_node(node.left),
+ self.handle_node(node.right)
+ )
+
+ def handle_div(self, node):
+ """
+ Divide two items.
+ """
+ return '(%s / %s)' % (
+ self.handle_node(node.left),
+ self.handle_node(node.right)
+ )
+
+ def handle_mul(self, node):
+ """
+ Multiply two items.
+ """
+ return '(%s * %s)' % (
+ self.handle_node(node.left),
+ self.handle_node(node.right)
+ )
+
+ def handle_mod(self, node):
+ """
+ Apply modulo.
+ """
+ return '(%s %% %s)' % (
+ self.handle_node(node.left),
+ self.handle_node(node.right)
+ )
+
+ def handle_unary_sub(self, node):
+ """
+ Make a number negative.
+ """
+ return '(-%s)' % self.handle_node(node.expr)
+
+ def handle_power(self, node):
+ """
+ handle foo**bar
+ """
+ return '(%s**%s)' % (
+ self.handle_node(node.left),
+ self.handle_node(node.right)
+ )
+
+ def handle_dict(self, node):
+ """
+ Dict constructor syntax.
+ """
+ return '{%s}' % ', '.join([
+ '%s: %s' % (
+ self.handle_node(key),
+ self.handle_node(value)
+ ) for key, value in node.items
+ ])
+
+ def handle_list(self, node):
+ """
+ We don't know tuples, tuples are lists for jinja.
+ """
+ return '[%s]' % ', '.join([
+ self.handle_node(n) for n in nodes
+ ])
+
+ def handle_and(self, node):
+ """
+ Handle foo and bar.
+ """
+ return ' and '.join([
+ self.handle_node(n) for n in node.nodes
+ ])
+
+ def handle_or(self, node):
+ """
+ handle foo or bar.
+ """
+ return ' or '.join([
+ self.handle_node(n) for n in self.nodse
+ ])
+
+ def handle_not(self, node):
+ """
+ handle not operator.
+ """
+ return 'not %s' % self.handle_node(node.expr)
+
+ def translate(self):
+ self.indention = 1
+ lines = [
+ 'def generate(environment, context, write, write_var=None):',
+ ' """This function was automatically generated by',
+ ' the jinja python translator. do not edit."""',
+ ' if write_var is None:',
+ ' write_var = write'
+ ]
+ lines.append(self.handle_node(self.node))
+ return '\n'.join(lines)
+
+
+def translate(environment, node):
+ """
+ Do the translation to python.
+ """
+ return PythonTranslator(environment, node).translate()
--- /dev/null
+Jinja 1.0 Syntax
+================
+
+<ul>
+{% for char in my_string|upper|replace(" ", "") %}
+ <li>{{ loop.index }} - {{ char|e }}</li>
+{% endfor %}
+</ul>
+
+{{ variable|strip(" ")|escape|replace("a", "b") }}
+
+{% if item == 42 %}
+ ...
+{% endif %}
+
+{% if item|odd? %}
+ applies the odd? filter which returns true if the
+ item in is odd.
+{% endif %}
+
+{{ item|e(true) }} -- escape the variable for attributes
+
+<ul>
+{% for item in seq %}
+ <li>{{ item.current|e }}
+ {% if item.items %}<ul>{% recurse item.items %}</ul>{% endif %}
+ </li>
+{% endfor %}
+</ul>
+
+How a Filter Looks Like
+=======================
+
+def replace(search, repl):
+ def wrapped(env, value):
+ return env.to_unicode(value).replace(search, repl)
+ return wrapped
+
+
+def escape(attr=False):
+ def wrapped(env, value):
+ return cgi.escape(env.to_unicode(value), attr)
+ return wrapped
+
+
+def odd():
+ return lambda env, value: value % 2 == 1
+odd.__name__ = 'odd?'
+
+
+@stringfilter
+def replace(value, search, repl):
+ return value.replace(search, repl)
+
+
+def stringfilter(f):
+ def decorator(*args):
+ def wrapped(env, value):
+ return f(env.to_unicode(value), *args)
+ return wrapped
+ return decorator
--- /dev/null
+For Loops
+---------
+
+Simple Syntax::
+
+ {% for <item> in <seq> %}
+ <data>
+ {% endfor %}
+
+Item Unpacking::
+
+ {% for <item1>, <item2> in <seq> %}
+ <data>
+ {% endfor %}
+
+With else block::
+
+ {% for <item> in <seq> %}
+ <data>
+ {% else %}
+ <data>
+ {% endfor %}
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Frameset//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>{% block "page_title" %}Untitled{% endblock %} | My Webpage</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <link rel="stylesheet" href="screen.css" type="text/css" media="screen" />
+ <link rel="stylesheet" href="print.css" type="text/css" media="print" />
+ {% block html_head %}{% endblock %}
+ </head>
+ <body>
+ <div id="header">
+ <h1>My Webpage</h1>
+ <ul id="navigation">
+ {% for item in page_navigation %}
+ <li><a href="{{ item.url|e }}"{% if item.active %}
+ class="active"{% endif %}>{{ item.caption|e }}</a></li>
+ {% endfor %}
+ </ul>
+ </div>
+ <div id="body">
+ {% block body %}
+ content goes here.
+ {% endblock %}
+ </div>
+ <div id="sidebar">
+ <h2>Who is online?</h2>
+ <ul>
+ {% for user in online_users %}
+ <li class="row_{% cycle 'even', 'odd' %}"><a href="{{ user.url|e }}">{{ user.username|e }}</a></li>
+ {% endfor %}
+ </ul>
+ <h2>Sitemap</h2>
+ {% macro draw_sitemap(items) %}
+ <ul>
+ {% for item in items %}
+ <li>{{ item.name|e }}{% if item.items %}{{ draw_sitemap(item.items) }}{% endif %}</li>
+ {% endfor %}
+ </ul>
+ {% endmacro %}
+ {{ draw_sitemap(sitemap) }}
+ <!-- alternative solution -->
+ <ul>
+ {% for item in items %}
+ <li>{{ item.name|e }}{% if item.items %}<ul>{% recurse item.items %}</ul>{% endif %}</li>
+ {% endfor %}
+ </ul>
+ </div>
+ <div id="footer">
+ © Copyrigh by yourself.
+ </div>
+ </body>
+</html>
--- /dev/null
+from jinja.environment import Environment
+
+e = Environment()
+
+def test(x):
+ for pos, token, data in e.lexer.tokenize(x):
+ print '%-8d%-30r%-40r' % (pos, token, data)
--- /dev/null
+source index.html::
+
+ {% page extends='layout.html', defaultfilter='autoescape',
+ charset='utf-8' %}
+
+ {% def title %}Index | {{ super.title }}{% enddef %}
+ {% def body %}
+ <h1>Index</h1>
+ <p>
+ This is just a mockup template so that you can see how the new
+ jinja syntax looks like. It also shows the python output of this
+ template when compiled.
+ </p>
+ <ul>
+ {% for user in users %}
+ <li class="{% cycle 'row1', 'row2' %}">{{ user.username }}</li>
+ {% endfor %}
+ </ul>
+ {% enddef %}
+
+source layout.html::
+
+ {% page defaultfilter='autoescape', charset='utf-8' %}
+ <html>
+ <head>
+ <title>{% block title %}My Webpage{% endblock %}</title>
+ </head>
+ <body>
+ <div class="header">
+ <h1>My Webpage</h1>
+ <h2>{{ self.title }}</h2>
+ </div>
+ <div class="body">
+ {{ self.body }}
+ </div>
+ <div class="footer">
+ {% block footer %}
+ Copyright 2007 by the Pocoo Team.
+ {% endblock %}
+ </div>
+ </body>
+ </html>
+
+generated python code::
+
+ from jinja.runtime import Template, Markup
+
+ class Layer1Template(Template):
+
+ defaultfilter = 'autoescape'
+
+ def p_title(self):
+ yield Markup(u'My Webpage')
+
+ def p_footer(self):
+ yield Markup(u'\n Copyright 2007 by the Pocoo Team.\n ')
+
+ def p_outer_body(self):
+ yield Markup(u'<html>\n <head>\n <title>\n')
+ for i in self.resolve('self.title'):
+ yield i
+ yield Markup(u'</title>\n </head>\n <body>\n <div class="header">\n'
+ u' <h1>My Webpage</h1>\n <h2>')
+ for i in self.resolve('self.title'):
+ yield i
+ yield Markup(u'</h2>\n </div>\n <div class="body">\n ')
+ for i in self.resolve('self.body'):
+ yield i
+ yield Markup(u'\n </div>\n <div class="footer">\n ')
+ for i in self.resolve('self.footer'):
+ yield i
+ yield Markup(u'\n </div>\n </body>\n</html')
+
+ class Layer2Template(Layer1Template):
+
+ defaultfilter = 'autoescape'
+
+ def p_title(self):
+ yield Markup(u'Index | ')
+ for i in self.resolve('super.title'):
+ yield i
+
+ def p_body(self):
+ yield Markup(u'\n <h1>Index</h1>\n <p>\n This is just a mockup '
+ u'template so that you can see how the new jinja syntax '
+ u'looks like. It also shows the python output of this '
+ u'template when compiled.\n </p>\n <ul>\n ')
+ iteration_1 = self.start_iteration(['user'], 'users')
+ for _ in iteration_1:
+ yield Markup(u'\n <li class="')
+ for i in self.cycle(u'row1', u'row2'):
+ yield i
+ yield Markup(u'">')
+ for i in self.resolve('user.username'):
+ yield i
+ yield Markup(u'</li>\n ')
+ iteration_1.close()
+
+ def get_template(context):
+ return Layer2Template(context)
+
+generated javascript code::
+
+ (function(c) {
+ var m = function(x) { return new Jinja.Markup(x) };
+
+ var l1 = function() {};
+ l1.defaultfilter = 'autoescape';
+ l1.prototype.p_title = function() {
+ this.yield(m('My Webpage'));
+ };
+ l1.prototype.p_footer = function() {
+ this.yield(m('\n Copyright 2007 by the Pocoo Team.\n '));
+ };
+ l1.prototype.p_outer_body = function() {
+ this.yield(m('<html>\n <head>\n <title>\n'));
+ this.resolve('self.title');
+ this.yield(m('</title>\n </head>\n <body>\n <div class="header">\n'
+ ' <h1>My Webpage</h1>\n <h2>'));
+ this.resolve('self.title');
+ this.yield(m('</h2>\n </div>\n <div class="body">\n '));
+ this.resolve('self.body');
+ this.yield(m('\n </div>\n <div class="footer">\n '));
+ this.resolve('self.footer');
+ this.yield(m('\n </div>\n </body>\n</html'));
+ };
+
+ var l2 = function() {};
+ l2.defaultfilter = 'autoescape';
+ l2.prototype.p_title = function() {
+ this.yield(m('Index | '));
+ this.resolve('super.title');
+ };
+ l2.prototype.p_body = function() {
+ this.yield(m('\n <h1>Index</h1>\n <p>\n This is just a mockup '
+ 'template so that you can see how the new jinja syntax '
+ 'looks like. It also shows the python output of this '
+ 'template when compiled.\n </p>\n <ul>\n '));
+ var iteration_1 = this.startIteration(['user'], 'users');
+ while (iteration_1.next()) {
+ this.yield(m('\n <li class="'));
+ this.cycle('row1', 'row2');
+ this.yield(m('">'));
+ this.resolve('user.username');
+ this.yield('</li>\n ');
+ };
+ iteration_1.close();
+ };
+
+ return function(context) {
+ return new Jinja.Template(context, l1, l2);
+ };
+ })();
--- /dev/null
+from jinja import Environment, FileSystemLoader
+
+env = Environment(
+ loader=FileSystemLoader(['.'], suffix='')
+)
+env.filters['odd?'] = lambda c, x: x % 2 == 1
+env.filters['even?'] = lambda x, x: x % 2 == 0
+
+t = env.get_template('index.html')
+print t.render(
+ users=[
+ dict(username='foo', user_id=1),
+ dict(username='bar', user_id=2)
+ ]
+)