From 4b860dd75164386e86537119b372d9be99c7939e Mon Sep 17 00:00:00 2001 From: Ilja Orlovs Date: Fri, 5 Sep 2014 10:40:23 +0100 Subject: [PATCH] Added statement. --- cyclone/template.py | 66 ++++++++++++++++++++++++-------- cyclone/tests/test_template.py | 69 ++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 16 deletions(-) create mode 100644 cyclone/tests/test_template.py diff --git a/cyclone/template.py b/cyclone/template.py index 40d38b8a18..3fcbf75819 100644 --- a/cyclone/template.py +++ b/cyclone/template.py @@ -139,6 +139,10 @@ def add(x, y): template. Anything in the child template not contained in a ``block`` tag will be ignored. For an example, see the ``{% block %}`` tag. +``{% super %}`` + This statement is allowed only inside ``{% block %}`` statement. + Inserts value of the appropriate block from the parent template. + ``{% for *var* in *expr* %}...{% end %}`` Same as the python ``for`` statement. ``{% break %}`` and ``{% continue %}`` may be used inside the loop. @@ -182,6 +186,8 @@ def add(x, y): from __future__ import absolute_import, division, with_statement +import collections +import contextlib import datetime import linecache import os.path @@ -276,11 +282,12 @@ def _generate_python(self, loader, compress_whitespace): buffer = StringIO() try: # named_blocks maps from names to _NamedBlock objects - named_blocks = {} + named_blocks = collections.defaultdict(list) ancestors = self._get_ancestors(loader) ancestors.reverse() for ancestor in ancestors: ancestor.find_named_blocks(loader, named_blocks) + self.file.find_named_blocks(loader, named_blocks) writer = _CodeWriter(buffer, named_blocks, loader, ancestors[0].template, @@ -440,24 +447,51 @@ def __init__(self, name, body, template, line): self.body = body self.template = template self.line = line + self.parent = None + for el in body.each_child(): + if isinstance(el, _Super): + el.set_parent_block(self) def each_child(self): return (self.body,) - def generate(self, writer): - block = writer.named_blocks[self.name] + def generate(self, writer, force_self=False): + if force_self: + block = self + else: + block = writer.named_blocks[self.name][-1] with writer.include(block.template, self.line): block.body.generate(writer) def find_named_blocks(self, loader, named_blocks): - named_blocks[self.name] = self + named_blocks[self.name].append(self) _Node.find_named_blocks(self, loader, named_blocks) - class _ExtendsBlock(_Node): def __init__(self, name): self.name = name +class _Super(_Node): + + parent = None + + def __init__(self, template, suffix, line): + self.template = template + self.suffix = suffix + self.line = line + + def set_parent_block(self, parent): + self.parent = parent + + def generate(self, writer): + parent = self.parent + blocks = writer.named_blocks[parent.name] + assert self.parent in blocks + idx = blocks.index(self.parent) + preParent = blocks[:idx] + if preParent: + preParent = preParent[-1] + preParent.generate(writer, force_self=True) class _IncludeBlock(_Node): def __init__(self, name, reader, line): @@ -525,7 +559,6 @@ def generate(self, writer): writer.write_line("%s:" % self.statement, self.line, writer.indent_size() - 1) - class _Statement(_Node): def __init__(self, statement, line): self.statement = statement @@ -615,18 +648,14 @@ def __exit__(_, *args): return Indenter() + @contextlib.contextmanager def include(self, template, line): self.include_stack.append((self.current_template, line)) self.current_template = template - - class IncludeTemplate(object): - def __enter__(_): - return self - - def __exit__(_, *args): - self.current_template = self.include_stack.pop()[0] - - return IncludeTemplate() + try: + yield self + finally: + self.current_template = self.include_stack.pop()[0] def write_line(self, line, line_number, indent=None): if indent is None: @@ -788,6 +817,7 @@ def _parse(reader, template, in_block=None, in_loop=None): "finally": set(["try"]), } allowed_parents = intermediate_blocks.get(operator) + if allowed_parents is not None: if not in_block: raise ParseError("%s outside %s block" % @@ -805,7 +835,7 @@ def _parse(reader, template, in_block=None, in_loop=None): return body elif operator in ("extends", "include", "set", "import", "from", - "comment", "autoescape", "raw", "module"): + "comment", "autoescape", "raw", "module", "super"): if operator == "comment": continue if operator == "extends": @@ -839,6 +869,10 @@ def _parse(reader, template, in_block=None, in_loop=None): block = _Expression(suffix, line, raw=True) elif operator == "module": block = _Module(suffix, line) + elif operator == "super": + if in_block != "block": + raise ParseError("'super' block cannot be attached to 'block' block") + block = _Super(template, suffix, line) body.chunks.append(block) continue diff --git a/cyclone/tests/test_template.py b/cyclone/tests/test_template.py new file mode 100644 index 0000000000..bc60567ab7 --- /dev/null +++ b/cyclone/tests/test_template.py @@ -0,0 +1,69 @@ +# +# Copyright 2014 David Novakovic +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from twisted.trial import unittest +from mock import Mock + +from cyclone import template + +class TestTemplates(unittest.TestCase): + + def test_simple_var(self): + t = template.Template(r"My name is: {{ name }}") + self.assertEqual(t.generate(name="Alice"), "My name is: Alice") + self.assertEqual(t.generate(name="Bob"), "My name is: Bob") + + def test_blocks(self): + loader = template.DictLoader({ + "base.html": r"value: {% block value %}original{% end %}", + "ext1.html": r'{% extends "base.html" %}{% block value %}new1{% end %}', + "ext2.html": r'{% extends "base.html" %}{% block value %}{% super %}, new2{% end %}', + "ext3.html": r'{% extends "ext2.html" %}{% block value %}{% super %}, new3{% end %}', + "ext2_1.html": r'{% extends "base.html" %}{% block value %}{% super %}, a={{a}}{% end %}', + "ext3_1.html": r'{% extends "ext2_1.html" %}{% block value %}{% super %}, a={{a}}{% end %}', + "ext3_2.html": r'{% extends "ext2_1.html" %}{% block value %}{% super %}, a={{a}}:b={{b}}{% end %}', + "ext3_3.html": r'{% extends "ext2_1.html" %}{% block value %}{% super %}, b={{b}}{% end %}', + }) + self.assertEqual(loader.load("base.html").generate(), "value: original") + self.assertEqual(loader.load("ext1.html").generate(), "value: new1") + self.assertEqual(loader.load("ext2.html").generate(), "value: original, new2") + self.assertEqual(loader.load("ext3.html").generate(), "value: original, new2, new3") + + self.assertEqual(loader.load("ext2_1.html").generate(a=-5), "value: original, a=-5") + self.assertEqual(loader.load("ext2_1.html").generate(a=42), "value: original, a=42") + + self.assertEqual(loader.load("ext3_1.html").generate(a=-5), "value: original, a=-5, a=-5") + self.assertEqual(loader.load("ext3_1.html").generate(a=42), "value: original, a=42, a=42") + + self.assertEqual(loader.load("ext3_2.html").generate(a=42, b=11), "value: original, a=42, a=42:b=11") + self.assertEqual(loader.load("ext3_3.html").generate(a=42, b=7), "value: original, a=42, b=7") + + loader = template.DictLoader({ + "base.html": r"{% block v1 %}1{% end %} {% block v2 %}2{% end %}", + "ext1.html": r'{% extends "base.html" %}{% block v1 %}Hello{% end %}{% block v2 %}World{% end %}', + "ext2.html": r'{% extends "base.html" %}{% block v1 %}{% super %}+10{% end %}{% block v2 %}= 9 + {% super %}{% end %}', + }) + self.assertEqual(loader.load("ext1.html").generate(), "Hello World") + self.assertEqual(loader.load("ext2.html").generate(), "1+10 = 9 + 2") + + def test_if(self): + t = template.Template(r"{% if a == 1 %}One{% elif a < 0 %}Negative{% elif isinstance(a, basestring) %}String{% else %}Unknown{% end %}") + self.assertEqual(t.generate(a=1), "One") + self.assertEqual(t.generate(a=-1), "Negative") + self.assertEqual(t.generate(a=-1.67), "Negative") + self.assertEqual(t.generate(a=-100), "Negative") + self.assertEqual(t.generate(a=42), "Unknown") + self.assertEqual(t.generate(a=42.5), "Unknown") + self.assertEqual(t.generate(a="meow"), "String") \ No newline at end of file