From 34f3042b7fbdd150acb7f2c6e0dc5db37276bdd7 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 30 Mar 2007 21:58:15 +0200 Subject: [PATCH] [svn] fixed supering in jinja, documented it, added lipsum() --HG-- branch : trunk --- docs/src/designerdoc.txt | 41 +++++++++ jdebug.py | 4 +- jinja/datastructure.py | 38 +++++--- jinja/defaults.py | 5 +- jinja/environment.py | 5 +- jinja/translators/python.py | 170 ++++++++++++++++++------------------ jinja/utils.py | 107 ++++++++++++++++------- tests/runtime/exception.py | 8 +- 8 files changed, 242 insertions(+), 136 deletions(-) diff --git a/docs/src/designerdoc.txt b/docs/src/designerdoc.txt index d369eb8..6036a5c 100644 --- a/docs/src/designerdoc.txt +++ b/docs/src/designerdoc.txt @@ -136,6 +136,28 @@ available per default: Function that outputs the contents of the context. +`lipsum` + + Generate some paragraphs of random text: + + .. sourcecode:: jinja + + {{ lipsum(5) }} + five html paragraphs + + {{ lipsum(2, False) }} + two ascii only paragraphs + + Per default a paragraph is between 20 and 100 words long. You can + override this default with the third and fourth parameter: + + .. sourcecode:: jinja + + {{ lipsum(10, True, 50, 200) }} + generate 10 HTML paragraphs between 50 and 200 words. + + *new in Jinja 1.1* + Loops ===== @@ -475,6 +497,25 @@ value from the parent template is used instead. two similarly-named ``{% block %}`` tags in a template, that template's parent wouldn't know which one of the blocks' content to use. +Super Blocks +============ + +Starting with `Jinja 1.1` it's possible to render the contents of the parent +block. By calling it you get the results of the parent block back. If you want +to get the data of the parent you can give it an offset: + +.. sourcecode:: jinja + + {{ super() }} + return the parent data + + {{ super(1) }} + the same as above + + {{ super(2) }} + return the data of the second parent block + + Template Inclusion ================== diff --git a/jdebug.py b/jdebug.py index 109c5c6..257ddea 100644 --- a/jdebug.py +++ b/jdebug.py @@ -33,8 +33,8 @@ if os.environ.get('JDEBUG_SOURCEPRINT'): PythonTranslator.translate = debug_translate -def p(x): - print PythonTranslator(e, Parser(e, x).parse()).translate() +def p(x, f=None): + print PythonTranslator(e, Parser(e, x, f).parse()).translate() def l(x): for item in e.lexer.tokenize(x): diff --git a/jinja/datastructure.py b/jinja/datastructure.py index 732a938..abd2ce1 100644 --- a/jinja/datastructure.py +++ b/jinja/datastructure.py @@ -295,7 +295,7 @@ class CycleContext(object): """ def __init__(self, seq=None): - self.lineno = -1 + self.pos = -1 # bind the correct helper function based on the constructor signature if seq is not None: self.seq = seq @@ -306,33 +306,43 @@ class CycleContext(object): def cycle_static(self): """Helper function for static cycling.""" - self.lineno = (self.lineno + 1) % self.length - return self.seq[self.lineno] + self.pos = (self.pos + 1) % self.length + return self.seq[self.pos] def cycle_dynamic(self, seq): """Helper function for dynamic cycling.""" - self.lineno = (self.lineno + 1) % len(seq) - return seq[self.lineno] + self.pos = pos = (self.pos + 1) % len(seq) + return seq[pos] -class BlockContext(object): +class SuperBlock(object): """ Helper class for ``{{ super() }}``. """ jinja_allowed_attributes = ['name'] - def __init__(self, name, stack, level, context): + def __init__(self, name, blocks, level, context): self.name = name self.context = context - if len(stack) > level: - self.block = stack[level] + if name in blocks: + self.stack = blocks[name] + self.level = level else: - self.block = None + self.stack is None + if len(stack) > level: + self.block = stack[level] - def __call__(self): - if self.block is None: - raise TemplateRuntimeError('no super block for %r' % self.name) - return self.block(self.context) + def __call__(self, offset=1): + level = self.level + (offset - 1) + if level < len(self.stack): + return self.stack[level](self.context) + raise TemplateRuntimeError('no super block for %r' % self.name) + + def __repr__(self): + return '' % ( + self.name, + self.stack is None and 'im' or '' + ) class TokenStream(object): diff --git a/jinja/defaults.py b/jinja/defaults.py index b634dad..382ae0e 100644 --- a/jinja/defaults.py +++ b/jinja/defaults.py @@ -10,10 +10,11 @@ """ from jinja.filters import FILTERS as DEFAULT_FILTERS from jinja.tests import TESTS as DEFAULT_TESTS -from jinja.utils import debug_context, safe_range +from jinja.utils import debug_context, safe_range, generate_lorem_ipsum DEFAULT_NAMESPACE = { 'range': safe_range, - 'debug': debug_context + 'debug': debug_context, + 'lipsum': generate_lorem_ipsum } diff --git a/jinja/environment.py b/jinja/environment.py index 329ad81..92ed4ca 100644 --- a/jinja/environment.py +++ b/jinja/environment.py @@ -87,7 +87,10 @@ class Environment(object): """Load a template from a string.""" from jinja.parser import Parser from jinja.translators.python import PythonTranslator - return PythonTranslator.process(self, Parser(self, source).parse()) + rv = PythonTranslator.process(self, Parser(self, source).parse()) + # attach the source for debugging + rv._source = source + return rv def get_template(self, filename): """Load a template from a filename. Only works diff --git a/jinja/translators/python.py b/jinja/translators/python.py index e8e9a59..67ce6a4 100644 --- a/jinja/translators/python.py +++ b/jinja/translators/python.py @@ -8,6 +8,7 @@ :copyright: 2007 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ +import re import sys from compiler import ast from jinja import nodes @@ -15,7 +16,13 @@ from jinja.nodes import get_nodes from jinja.parser import Parser from jinja.exceptions import TemplateSyntaxError from jinja.translators import Translator -from jinja.utils import translate_exception, capture_generator +from jinja.utils import translate_exception, capture_generator, \ + RUNTIME_EXCEPTION_OFFSET + + +#: regular expression for the debug symbols +_debug_re = re.compile(r'^\s*\# DEBUG\(filename=(?P.*?), ' + r'lineno=(?P\d+)\)$') def _to_tuple(args): @@ -33,48 +40,60 @@ class Template(object): Represents a finished template. """ - def __init__(self, environment, code, translated_source=None): + def __init__(self, environment, code): self.environment = environment self.code = code - self.translated_source = translated_source self.generate_func = None + #: holds debug information + self._debug_info = None + #: unused in loader environments but used when a template + #: is loaded from a string in order to be able to debug those too + self._source = None def dump(self, stream=None): """Dump the template into python bytecode.""" if stream is not None: from marshal import dump - dump((self.code, self.translated_source), stream) + dump(self.code, stream) else: from marshal import dumps - return dumps((self.code, self.translated_source)) + return dumps(self.code) def load(environment, data): """Load the template from python bytecode.""" if isinstance(data, basestring): from marshal import loads - code, src = loads(data) + code = loads(data) else: from marshal import load - code, src = load(data) - return Template(environment, code, src) + code = load(data) + return Template(environment, code) load = staticmethod(load) def render(self, *args, **kwargs): """Render a template.""" + # if there is no generation function we execute the code + # in a new namespace and save the generation function and + # debug information. if self.generate_func is None: ns = {'environment': self.environment} exec self.code in ns self.generate_func = ns['generate'] + self._debug_info = ns['debug_info'] ctx = self.environment.context_class(self.environment, *args, **kwargs) try: return capture_generator(self.generate_func(ctx)) except: + # debugging system: + # on any exception we first get the current exception information + # and skip the internal frames (currently either one (python2.5) + # or two (python2.4 and lower)). After that we call a function + # that creates a new traceback that is easier to debug. exc_type, exc_value, traceback = sys.exc_info() - # translate the exception, We skip two frames. One - # frame that is the "capture_generator" frame, and another - # one which is the frame of this function + for _ in xrange(RUNTIME_EXCEPTION_OFFSET): + traceback = traceback.tb_next traceback = translate_exception(self, exc_type, exc_value, - traceback.tb_next.tb_next, ctx) + traceback, ctx) raise exc_type, exc_value, traceback @@ -156,8 +175,7 @@ class PythonTranslator(Translator): filename = node.filename or '