From 4271d77e2455bf5661bfcbb833e091a0d9ee20b2 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 26 Feb 2007 23:52:30 +0100 Subject: [PATCH] [svn] final jinja changes for today: implemented better filter lookup --HG-- branch : trunk --- jinja/datastructure.py | 43 ++++++++++++-- jinja/environment.py | 56 +++++++++++++++---- jinja/exceptions.py | 22 ++------ jinja/filters.py | 7 --- jinja/lexer.py | 2 +- jinja/translators/python.py | 108 ++++++++++++++++++++++++++++++++++-- 6 files changed, 191 insertions(+), 47 deletions(-) diff --git a/jinja/datastructure.py b/jinja/datastructure.py index 868397c..f1db28d 100644 --- a/jinja/datastructure.py +++ b/jinja/datastructure.py @@ -79,11 +79,6 @@ class Context(object): 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): @@ -115,6 +110,44 @@ class Context(object): 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 diff --git a/jinja/environment.py b/jinja/environment.py index 32659c6..6b08675 100644 --- a/jinja/environment.py +++ b/jinja/environment.py @@ -10,7 +10,8 @@ """ 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 @@ -29,7 +30,6 @@ class Environment(object): template_charset='utf-8', charset='utf-8', loader=None, - tags=None, filters=None): # lexer / parser information @@ -54,15 +54,6 @@ class Environment(object): 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. @@ -74,3 +65,46 @@ class Environment(object): 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 diff --git a/jinja/exceptions.py b/jinja/exceptions.py index 6156041..265c2da 100644 --- a/jinja/exceptions.py +++ b/jinja/exceptions.py @@ -14,25 +14,13 @@ 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. + 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): @@ -41,7 +29,7 @@ class TemplateSyntaxError(SyntaxError, TemplateError): """ def __init__(self, message, pos): - super(TemplateSyntaxError, self).__init__(message) + SyntaxError.__init__(self, message) self.pos = pos @@ -52,5 +40,5 @@ class TemplateRuntimeError(TemplateError): """ def __init__(self, message, pos): - super(TemplateRuntimeError, self).__init__(message) + RuntimeError.__init__(self, message) self.pos = pos diff --git a/jinja/filters.py b/jinja/filters.py index 93ef90f..c495d32 100644 --- a/jinja/filters.py +++ b/jinja/filters.py @@ -36,7 +36,6 @@ def do_replace(s, old, new, count=None): if count is None: return s.replace(old, new) return s.replace(old, new, count) -do_replace = stringfilter(do_replace) def do_upper(s): @@ -46,7 +45,6 @@ def do_upper(s): Return a copy of s converted to uppercase. """ return s.upper() -do_upper = stringfilter(do_upper) def do_lower(s): @@ -56,7 +54,6 @@ 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): @@ -70,7 +67,6 @@ def do_escape(s, attribute=False): if attribute: s = s.replace('"', """) return s -do_escape = stringfilter(do_escape) def do_addslashes(s): @@ -80,7 +76,6 @@ 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): @@ -91,7 +86,6 @@ def do_capitalize(s): capitalized. """ return s.capitalize() -do_capitalize = stringfilter(do_capitalize) def do_title(s): @@ -102,7 +96,6 @@ 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''): diff --git a/jinja/lexer.py b/jinja/lexer.py index ce2541d..e8f4ffc 100644 --- a/jinja/lexer.py +++ b/jinja/lexer.py @@ -22,7 +22,7 @@ operator_re = re.compile('(%s)' % '|'.join( # braces and parenthesis '[', ']', '(', ')', '{', '}', # attribute access and comparison / logical operators - '.', ',', '|', '==', '<', '>', '<=', '>=', '!=', + '.', ':', ',', '|', '==', '<', '>', '<=', '>=', '!=', '=', ur'or\b', ur'and\b', ur'not\b' ])) diff --git a/jinja/translators/python.py b/jinja/translators/python.py index 57028d8..175dbd3 100644 --- a/jinja/translators/python.py +++ b/jinja/translators/python.py @@ -10,6 +10,7 @@ """ from compiler import ast from jinja import nodes +from jinja.exceptions import TemplateSyntaxError class PythonTranslator(object): @@ -23,6 +24,13 @@ 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, @@ -45,6 +53,7 @@ class PythonTranslator(object): 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, @@ -52,8 +61,20 @@ class PythonTranslator(object): 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): """ @@ -67,6 +88,10 @@ class PythonTranslator(object): """ 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 @@ -150,6 +175,8 @@ class PythonTranslator(object): """ 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): @@ -177,7 +204,12 @@ class PythonTranslator(object): 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]) ) @@ -186,7 +218,7 @@ class PythonTranslator(object): """ Handle hardcoded attribute access. foo.bar """ - return 'get_attribute(%s, %r)' % ( + return 'environment.get_attribute(%s, %r)' % ( self.handle_node(node.expr), node.attrname ) @@ -201,9 +233,37 @@ class PythonTranslator(object): """ 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): @@ -275,6 +335,12 @@ class PythonTranslator(object): 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. @@ -306,7 +372,7 @@ class PythonTranslator(object): 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): @@ -331,9 +397,39 @@ class PythonTranslator(object): """ 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."""', -- 2.26.2