[svn] final jinja changes for today: implemented better filter lookup
authorArmin Ronacher <armin.ronacher@active-4.com>
Mon, 26 Feb 2007 22:52:30 +0000 (23:52 +0100)
committerArmin Ronacher <armin.ronacher@active-4.com>
Mon, 26 Feb 2007 22:52:30 +0000 (23:52 +0100)
--HG--
branch : trunk

jinja/datastructure.py
jinja/environment.py
jinja/exceptions.py
jinja/filters.py
jinja/lexer.py
jinja/translators/python.py

index 868397cc20fd5d3f42fc561b5a5113a4142ad83d..f1db28d6029acc0da0c25dff28d17c4735ea5a19 100644 (file)
@@ -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
index 32659c6b3803a854c14d05c3fe7ff6cca8c1a103..6b0867508425b7a7ae8f038ea0203ef5090573a3 100644 (file)
@@ -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
index 61560417cfae6e72900a1a2e9f0994c18598dbd6..265c2da0532ebadfa76f59c1d1d8d9f7c3cdbbd5 100644 (file)
@@ -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
index 93ef90f83bc6d182f8cc30e38016893cda4495a3..c495d32500052bf53acc0ed33acc2c47fea2c026 100644 (file)
@@ -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('"', "&quot;")
     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''):
index ce2541d39d1feeb3095df0f80809c2b06f945f2c..e8f4ffc9f8fefc100467a23c45f1bb922093c027 100644 (file)
@@ -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'
 ]))
 
index 57028d8b4d18308f3350b36871b0f44536a9c1cd..175dbd35596db03da5d2ceb1f32e9f890adcc28b 100644 (file)
@@ -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."""',