Skip to content

Commit

Permalink
Merge 4b860dd into f304653
Browse files Browse the repository at this point in the history
  • Loading branch information
Ilya Orlov committed Sep 5, 2014
2 parents f304653 + 4b860dd commit 2898f01
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 16 deletions.
66 changes: 50 additions & 16 deletions cyclone/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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" %
Expand All @@ -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":
Expand Down Expand Up @@ -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

Expand Down
69 changes: 69 additions & 0 deletions cyclone/tests/test_template.py
Original file line number Diff line number Diff line change
@@ -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")

0 comments on commit 2898f01

Please sign in to comment.