[svn] checked in today's jinja changes which i forgot to commit
authorArmin Ronacher <armin.ronacher@active-4.com>
Sat, 31 Mar 2007 18:40:38 +0000 (20:40 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Sat, 31 Mar 2007 18:40:38 +0000 (20:40 +0200)
--HG--
branch : trunk

docs/src/designerdoc.txt
jinja/defaults.py
jinja/environment.py
jinja/lexer.py
jinja/nodes.py
jinja/parser.py
jinja/translators/python.py
jinja/utils.py

index 6036a5cf4f35cfde5ccc7ed9c9c87dedc9401285..c59987ce5e00fdbc21979b0546e7d343ce21c3c7 100644 (file)
@@ -158,6 +158,56 @@ available per default:
 
     *new in Jinja 1.1*
 
+`watchchanges`
+
+    Jinja does not provide an django like ``{% ifchanged %}`` tag. As
+    replacement for this tag there is a special function in the namespace
+    called `watchchanges`.
+
+    You can use it like this:
+
+    .. sourcecode:: html+jinja
+
+        {% for changed, article in watchchanges(articles, 'date', 'day') %}
+            {% if changed %}<h3>{{ articles.date.day }}</h3>{% endif %}
+            <h4>{{ article.title|e }}</h4>
+            <p>{{ article.body|e }}</p>
+        {% endif %}
+
+    For each iteration `watchchanges` will check the given attribute. If it
+    changed to the former iteration the first yielded item (in this example
+    it's called `changed`) will be `true`, else `false`.
+
+    In this example `articles` is a list of articles for the template with
+    an attribute called `date.day` which represents the current day. To only
+    add a new day headline if the day has changed `watchchanges` is now told
+    to check `articles.date.days`.
+
+    If you want to observe more than one attribute you can provide pairs:
+
+    .. sourcecode:: html+jinja
+
+        {% for changed, item in watchchanges(foo, ('a', 'b'), ('a', 'c')) %}
+            ...
+        {% endfor %}
+
+    Note that if you want to watch two first level attributes you have to
+    either use the list syntax `[]` or add a colon:
+
+    .. sourcecode:: html+jinja
+
+        {% for changed, item in watchchanges(foo, ['a'], ('b',)) %}
+            ...
+        {% endfor %}
+
+    otherwise Jinja cannot differ between a pair of parentheses to group
+    expressions or the sequence syntax.
+
+    If you don't provide any arguments the value of the variable itself
+    is checked.
+
+    *new in Jinja 1.1*
+
 Loops
 =====
 
index 382ae0e33a72db46127cf20f137fc1ac4e781895..b0ca6ef4f7046d52fbe7492056df2f0131a76575 100644 (file)
 """
 from jinja.filters import FILTERS as DEFAULT_FILTERS
 from jinja.tests import TESTS as DEFAULT_TESTS
-from jinja.utils import debug_context, safe_range, generate_lorem_ipsum
+from jinja.utils import debug_context, safe_range, generate_lorem_ipsum, \
+     watch_changes
 
 
 DEFAULT_NAMESPACE = {
     'range':                safe_range,
     'debug':                debug_context,
-    'lipsum':               generate_lorem_ipsum
+    'lipsum':               generate_lorem_ipsum,
+    'watchchanges':         watch_changes
 }
index abbaa4b735451d281dff7a1e29153c2b8adf4b6b..6fdd369aa2a6ef98030833c761b30b847a284594 100644 (file)
@@ -182,6 +182,16 @@ class Environment(object):
                 pass
         return Undefined
 
+    def get_attributes(self, obj, attributes):
+        """
+        Get some attributes from an object. If attributes is an
+        empty sequence the object is returned as it.
+        """
+        get = self.get_attribute
+        for name in attributes:
+            obj = get(obj, name)
+        return obj
+
     def call_function(self, f, context, args, kwargs, dyn_args, dyn_kwargs):
         """
         Function call helper. Called for all functions that are passed
index 84e83f5a6b22bbbdd6bbd8f40bd3e5c80f85aad0..413bdc5a892075a8254ee94b8a22133c5625d319 100644 (file)
@@ -111,17 +111,19 @@ class Lexer(object):
             ],
             # comments
             'comment_begin': [
-                (c(r'(.*?)(\-%s\s*|%s)' % (
+                (c(r'(.*?)((?:\-%s\s*|%s)%s)' % (
                     e(environment.comment_end_string),
-                    e(environment.comment_end_string)
+                    e(environment.comment_end_string),
+                    block_suffix_re
                 )), ('comment', 'comment_end'), '#pop'),
                 (c('(.)'), (Failure('Missing end of comment tag'),), None)
             ],
             # directives
             'block_begin': [
-                (c('\-%s\s*|%s' % (
+                (c('(?:\-%s\s*|%s)%s' % (
                     e(environment.block_end_string),
-                    e(environment.block_end_string)
+                    e(environment.block_end_string),
+                    block_suffix_re
                 )), 'block_end', '#pop'),
             ] + tag_rules,
             # variables
index 85a92b47f481b6b30851dc750b14522d25c0457e..c06ab8d7e70d10edfb6035fdf84bfba2e3668561 100644 (file)
@@ -100,10 +100,10 @@ class Template(NodeList):
         return self.extends is not None and [self.extends] or []
 
     def __repr__(self):
-        return 'Template(%r, %r, %r)' % (
+        return 'Template(%r, %r, %s)' % (
             self.filename,
             self.extends,
-            NodeList.__repr__(self)
+            list.__repr__(self)
         )
 
 
index b6473980761f26eb2f876072f4f459d424f4bf32..8449cd83d19265b2a9e6963d1640070f9110251f 100644 (file)
@@ -475,9 +475,17 @@ class Parser(object):
         tokens = []
         for t_lineno, t_token, t_data in gen:
             if t_token == 'string':
-                tokens.append('u' + t_data)
-            else:
-                tokens.append(t_data)
+                # because some libraries have problems with unicode
+                # objects we do some lazy unicode processing here.
+                # if a string is ASCII only we yield it as string
+                # in other cases as unicode. This works around
+                # problems with datetimeobj.strftime()
+                try:
+                    str(t_data)
+                except UnicodeError:
+                    tokens.append('u' + t_data)
+                    continue
+            tokens.append(t_data)
         source = '\xef\xbb\xbf' + (template % (u' '.join(tokens)).
                                    encode('utf-8'))
         try:
index db9f52c5d4d269d3b7c1d43c13fba5bacf9bfa59..9d7b5c34d5b4fd72560c5d1b3e7db9c3c2f2a826 100644 (file)
@@ -446,7 +446,10 @@ class PythonTranslator(Translator):
         # handle real loop code
         self.indention += 1
         write(self.nodeinfo(node.body))
-        buf.append(self.handle_node(node.body) or self.indent('pass'))
+        if node.body:
+            buf.append(self.handle_node(node.body))
+        else:
+            write('pass')
         self.indention -= 1
 
         # else part of loop
index b430312f7bddeb4f52c323b985c7c3200cf97c8f..dec5348d3ff09155a07ea21dcc60c54c51346383 100644 (file)
@@ -29,8 +29,6 @@ except ImportError:
 #: number of maximal range items
 MAX_RANGE = 1000000
 
-_integer_re = re.compile('^(\d+)$')
-
 _word_split_re = re.compile(r'(\s+)')
 
 _punctuation_re = re.compile(
@@ -199,7 +197,43 @@ def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
     return u'\n'.join([u'<p>%s</p>' % escape(x) for x in result])
 
 
-# python2.4 and lower has a bug regarding joining of broken generators
+def watch_changes(env, context, iterable, *attributes):
+    """
+    Wise replacement for ``{% ifchanged %}``.
+    """
+    # find the attributes to watch
+    if attributes:
+        tests = []
+        tmp = []
+        for attribute in attributes:
+            if isinstance(attribute, (str, unicode, int, long, bool)):
+                tmp.append(attribute)
+            else:
+                tests.append(tuple(attribute))
+        if tmp:
+            tests.append(tuple(attribute))
+        last = tuple([object() for x in tests])
+    # or no attributes if we watch the object itself
+    else:
+        tests = None
+        last = object()
+
+    # iterate trough it and keep check the attributes or values
+    for item in iterable:
+        if tests is None:
+            cur = item
+        else:
+            cur = tuple([env.get_attributes(item, x) for x in tests])
+        if cur != last:
+            changed = True
+            last = cur
+        else:
+            changed = False
+        yield changed, item
+watch_changes.jinja_context_callable = True
+
+
+# python2.4 and lower has a bug regarding joining of broken generators.
 # because of the runtime debugging system we have to keep track of the
 # number of frames to skip. that's what RUNTIME_EXCEPTION_OFFSET is for.
 if sys.version_info < (2, 5):