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):
return 'Context(%s)' % repr(tmp)
+class LoopContext(object):
+ """
+ Simple class that provides special loop variables.
+ Used by `Environment.iterate`.
+ """
+
+ def __init__(self, index, length):
+ self.index = 0
+ self.length = length
+ try:
+ self.length = len(seq)
+ except TypeError:
+ self.seq = list(seq)
+ self.length = len(self.seq)
+ else:
+ self.seq = seq
+
+ def revindex(self):
+ return self.length - self.index + 1
+ revindex = property(revindex)
+
+ def revindex0(self):
+ return self.length - self.index
+ revindex0 = property(revindex0)
+
+ def index0(self):
+ return self.index - 1
+ index0 = property(index0)
+
+ def even(self):
+ return self.index % 2 == 0
+ even = property(even)
+
+ def odd(self):
+ return self.index % 2 == 1
+ odd = property(odd)
+
+
class TokenStream(object):
"""
A token stream works like a normal generator just that
"""
from jinja.lexer import Lexer
from jinja.parser import Parser
-from jinja.exceptions import TagNotFound, FilterNotFound
+from jinja.datastructure import LoopContext, Undefined
+from jinja.exceptions import FilterNotFound
from jinja.defaults import DEFAULT_FILTERS
template_charset='utf-8',
charset='utf-8',
loader=None,
- tags=None,
filters=None):
# lexer / parser information
parser = Parser(self, source)
return parser.parse_page()
- 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.
return unicode(value)
except UnicodeError:
return str(value).decode(self.charset, 'ignore')
+
+ def iterate(self, seq):
+ """
+ Helper function used by the python translator runtime code to
+ iterate over a sequence.
+ """
+ try:
+ length = len(seq)
+ except TypeError:
+ seq = list(seq)
+ length = len(seq)
+ loop_data = LoopContext(0, length)
+ for item in seq:
+ loop_data.index += 1
+ yield loop_data, item
+
+ def prepare_filter(self, name, *args):
+ """
+ Prepare a filter.
+ """
+ try:
+ return self.filters[name](*args)
+ except KeyError:
+ raise FilterNotFound(name)
+
+ def apply_filters(self, value, context, filters):
+ """
+ Apply a list of filters on the variable.
+ """
+ for f in filters:
+ value = f(self, context, value)
+ return value
+
+ def get_attribute(self, obj, name):
+ """
+ Get the attribute name from obj.
+ """
+ try:
+ return getattr(obj, name)
+ except AttributeError:
+ return obj[name]
+ except:
+ return Undefined
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.
+ Raised if a filter does not exist.
"""
- def __init__(self, filtername):
- super(FilterNotFound, self).__init__('The filter %r does not exist.' % filtername)
- self.filtername = filtername
+ def __init__(self, message):
+ KeyError.__init__(self, message)
class TemplateSyntaxError(SyntaxError, TemplateError):
"""
def __init__(self, message, pos):
- super(TemplateSyntaxError, self).__init__(message)
+ SyntaxError.__init__(self, message)
self.pos = pos
"""
def __init__(self, message, pos):
- super(TemplateRuntimeError, self).__init__(message)
+ RuntimeError.__init__(self, message)
self.pos = pos
if count is None:
return s.replace(old, new)
return s.replace(old, new, count)
-do_replace = stringfilter(do_replace)
def do_upper(s):
Return a copy of s converted to uppercase.
"""
return s.upper()
-do_upper = stringfilter(do_upper)
def do_lower(s):
Return a copy of s converted to lowercase.
"""
return s.lower()
-do_lower = stringfilter(do_lower)
def do_escape(s, attribute=False):
if attribute:
s = s.replace('"', """)
return s
-do_escape = stringfilter(do_escape)
def do_addslashes(s):
Adds slashes to s.
"""
return s.encode('utf-8').encode('string-escape').decode('utf-8')
-do_addslashes = stringfilter(do_addslashes)
def do_capitalize(s):
capitalized.
"""
return s.capitalize()
-do_capitalize = stringfilter(do_capitalize)
def do_title(s):
characters, all remaining cased characters have lowercase.
"""
return s.title()
-do_title = stringfilter(do_title)
def do_default(default_value=u''):
# braces and parenthesis
'[', ']', '(', ')', '{', '}',
# attribute access and comparison / logical operators
- '.', ',', '|', '==', '<', '>', '<=', '>=', '!=',
+ '.', ':', ',', '|', '==', '<', '>', '<=', '>=', '!=', '=',
ur'or\b', ur'and\b', ur'not\b'
]))
"""
from compiler import ast
from jinja import nodes
+from jinja.exceptions import TemplateSyntaxError
class PythonTranslator(object):
self.indention = 0
self.last_pos = 0
+ self.constants = {
+ 'true': 'True',
+ 'false': 'False',
+ 'none': 'None',
+ 'undefined': 'Undefined'
+ }
+
self.handlers = {
# jinja nodes
nodes.Text: self.handle_template_text,
ast.Div: self.handle_div,
ast.Mul: self.handle_mul,
ast.Mod: self.handle_mod,
+ ast.UnaryAdd: self.handle_unary_add,
ast.UnarySub: self.handle_unary_sub,
ast.Power: self.handle_power,
ast.Dict: self.handle_dict,
ast.Tuple: self.handle_list,
ast.And: self.handle_and,
ast.Or: self.handle_or,
- ast.Not: self.handle_not
+ ast.Not: self.handle_not,
+ ast.Slice: self.handle_slice,
+ ast.Sliceobj: self.handle_sliceobj
+ }
+
+ self.unsupported = {
+ ast.ListComp: 'list comprehensions',
+ ast.From: 'imports',
+ ast.Import: 'imports',
}
+ if hasattr(ast, 'GenExpr'):
+ self.unsupported.update({
+ ast.GenExpr: 'generator expressions'
+ })
def indent(self, text):
"""
"""
if node.__class__ in self.handlers:
out = self.handlers[node.__class__](node)
+ elif node.__class__ in self.unsupported:
+ raise TemplateSyntaxError('unsupported syntax element %r found.'
+ % self.unsupported[node.__class__],
+ self.last_pos)
else:
raise AssertionError('unhandled node %r' % node.__class__)
return out
"""
Handle name assignments and name retreivement.
"""
+ if node.name in self.constants:
+ return self.constants[node.name]
return 'context[%r]' % node.name
def handle_compare(self, node):
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)' % (
+ if node.subs[0].__class__ is ast.Sliceobj:
+ return '%s[%s]' % (
+ self.handle_node(node.expr),
+ self.handle_node(node.subs[0])
+ )
+ return 'environment.get_attribute(%s, %s)' % (
self.handle_node(node.expr),
self.handle_node(node.subs[0])
)
"""
Handle hardcoded attribute access. foo.bar
"""
- return 'get_attribute(%s, %r)' % (
+ return 'environment.get_attribute(%s, %r)' % (
self.handle_node(node.expr),
node.attrname
)
"""
We use the pipe operator for filtering.
"""
- return 'environment.apply_filters(%s, [%s])' % (
+ filters = []
+ for n in node.nodes[1:]:
+ if n.__class__ is ast.CallFunc:
+ args = []
+ for arg in n.args:
+ if arg.__class__ is ast.Keyword:
+ raise TemplateSyntaxError('keyword arguments for '
+ 'filters are not supported.',
+ self.last_pos)
+ args.append(self.handle_node(arg))
+ if n.star_args is not None or n.dstar_args is not None:
+ raise TemplateSynaxError('*args / **kwargs is not supported '
+ 'for filters', self.last_pos)
+ args = ', '.join(args)
+ if args:
+ args = ', ' + args
+ filters.append('environment.prepare_filter(%s%s)' % (
+ self.handle_node(n.node),
+ args
+ ))
+ elif n.__class__ is ast.Name:
+ filters.append('environment.prepare_filter(%s)' %
+ self.handle_node(n))
+ else:
+ raise TemplateSyntaxError('invalid filter. filter must be a '
+ 'hardcoded function name from the '
+ 'filter namespace',
+ self.last_pos)
+ return 'environment.apply_filters(%s, context, [%s])' % (
self.handle_node(node.nodes[0]),
- ', '.join([self.handle_node(n) for n in node.nodes[1:]])
+ ', '.join(filters)
)
def handle_call_func(self, node):
self.handle_node(node.right)
)
+ def handle_unary_add(self, node):
+ """
+ One of the more or less unused nodes.
+ """
+ return '(+%s)' % self.handle_node(node.expr)
+
def handle_unary_sub(self, node):
"""
Make a number negative.
We don't know tuples, tuples are lists for jinja.
"""
return '[%s]' % ', '.join([
- self.handle_node(n) for n in nodes
+ self.handle_node(n) for n in node.nodes
])
def handle_and(self, node):
"""
return 'not %s' % self.handle_node(node.expr)
+ def handle_slice(self, node):
+ """
+ Slice access.
+ """
+ if node.lower is None:
+ lower = ''
+ else:
+ lower = self.handle_node(node.lower)
+ if node.upper is None:
+ upper = ''
+ else:
+ upper = self.handle_node(node.upper)
+ assert node.flags != 'OP_DELETE', 'wtf? shouldn\'t happen'
+ return '%s[%s:%s]' % (
+ self.handle_node(node.expr),
+ lower,
+ upper
+ )
+
+ def handle_sliceobj(self, node):
+ """
+ Extended Slice access.
+ """
+ args = []
+ for n in node.nodes:
+ args.append(self.handle_node(n))
+ return '[%s]' % ':'.join(args)
+
def translate(self):
self.indention = 1
+ self.last_pos = 0
lines = [
+ 'from jinja.datastructures import Undefined',
'def generate(environment, context, write, write_var=None):',
' """This function was automatically generated by',
' the jinja python translator. do not edit."""',