From a5c8d580c31ae7db5ddb48acbb871beeb63f8d6c Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 31 Mar 2007 20:40:38 +0200 Subject: [PATCH] [svn] checked in today's jinja changes which i forgot to commit --HG-- branch : trunk --- docs/src/designerdoc.txt | 50 +++++++++++++++++++++++++++++++++++++ jinja/defaults.py | 6 +++-- jinja/environment.py | 10 ++++++++ jinja/lexer.py | 10 +++++--- jinja/nodes.py | 4 +-- jinja/parser.py | 14 ++++++++--- jinja/translators/python.py | 5 +++- jinja/utils.py | 40 ++++++++++++++++++++++++++--- 8 files changed, 124 insertions(+), 15 deletions(-) diff --git a/docs/src/designerdoc.txt b/docs/src/designerdoc.txt index 6036a5c..c59987c 100644 --- a/docs/src/designerdoc.txt +++ b/docs/src/designerdoc.txt @@ -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 %}

{{ articles.date.day }}

{% endif %} +

{{ article.title|e }}

+

{{ article.body|e }}

+ {% 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 ===== diff --git a/jinja/defaults.py b/jinja/defaults.py index 382ae0e..b0ca6ef 100644 --- a/jinja/defaults.py +++ b/jinja/defaults.py @@ -10,11 +10,13 @@ """ 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 } diff --git a/jinja/environment.py b/jinja/environment.py index abbaa4b..6fdd369 100644 --- a/jinja/environment.py +++ b/jinja/environment.py @@ -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 diff --git a/jinja/lexer.py b/jinja/lexer.py index 84e83f5..413bdc5 100644 --- a/jinja/lexer.py +++ b/jinja/lexer.py @@ -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 diff --git a/jinja/nodes.py b/jinja/nodes.py index 85a92b4..c06ab8d 100644 --- a/jinja/nodes.py +++ b/jinja/nodes.py @@ -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) ) diff --git a/jinja/parser.py b/jinja/parser.py index b647398..8449cd8 100644 --- a/jinja/parser.py +++ b/jinja/parser.py @@ -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: diff --git a/jinja/translators/python.py b/jinja/translators/python.py index db9f52c..9d7b5c3 100644 --- a/jinja/translators/python.py +++ b/jinja/translators/python.py @@ -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 diff --git a/jinja/utils.py b/jinja/utils.py index b430312..dec5348 100644 --- a/jinja/utils.py +++ b/jinja/utils.py @@ -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'

%s

' % 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): -- 2.26.2