summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDerek Harland <derek.harland@finq.co.nz>2014-04-16 11:49:12 +1200
committerDerek Harland <derek.harland@finq.co.nz>2014-04-16 11:49:12 +1200
commit71a3ea74ba27e172aa98a0e7ddef35464a143fe4 (patch)
tree0afaff669595264b10c2b4646d7be4b8d2359763
parent68c28cb42c9e8b42a3425ddf863bbafa5c80d086 (diff)
parentc2373cede978a62eb3c142c5ca4bb1a02a32b3ad (diff)
downloadmako-71a3ea74ba27e172aa98a0e7ddef35464a143fe4.tar.gz
Merged zzzeek/mako into master
-rw-r--r--doc/build/changelog.rst16
-rw-r--r--doc/build/inheritance.rst107
-rw-r--r--mako/pyparser.py2
-rw-r--r--mako/util.py13
-rwxr-xr-xscripts/mako-render8
-rw-r--r--setup.cfg5
-rw-r--r--test/__init__.py6
-rw-r--r--test/test_ast.py25
8 files changed, 167 insertions, 15 deletions
diff --git a/doc/build/changelog.rst b/doc/build/changelog.rst
index 2a0e5f3..6b79b30 100644
--- a/doc/build/changelog.rst
+++ b/doc/build/changelog.rst
@@ -10,6 +10,22 @@ Changelog
:released:
.. change::
+ :tags: bug, py3k
+ :tickets: 227
+
+ Fixed bug in Python parsing logic which would fail on Python 3
+ when a "try/except" targeted a tuple of exception types, rather
+ than a single exception.
+
+ .. change::
+ :tags: feature
+ :pullreq: bitbucket:4
+
+ The mako-render script will now catch exceptions and run them
+ into the text error handler, and exit with a non-zero exit code.
+ Pull request courtesy Derek Harland.
+
+ .. change::
:tags: feature, py3k
:pullreq: github:7
diff --git a/doc/build/inheritance.rst b/doc/build/inheritance.rst
index 9eba053..5b29574 100644
--- a/doc/build/inheritance.rst
+++ b/doc/build/inheritance.rst
@@ -192,6 +192,8 @@ a section that is used more than once, such as the title of a page:
Where above an inheriting template can define ``<%block name="title">`` just once, and it will be
used in the base template both in the ``<title>`` section as well as the ``<h2>``.
+
+
But what about Defs?
====================
@@ -494,6 +496,111 @@ thing is now:
and you're now a template inheritance ninja!
+Using ``<%include>`` with Template Inheritance
+==============================================
+
+A common source of confusion is the behavior of the ``<%include>`` tag,
+often in conjunction with its interaction within template inheritance.
+Key to understanding the ``<%include>`` tag is that it is a *dynamic*, e.g.
+runtime, include, and not a static include. The ``<%include>`` is only processed
+as the template renders, and not at inheritance setup time. When encountered,
+the referenced template is run fully as an entirely separate template with no
+linkage to any current inheritance structure.
+
+If the tag were on the other hand a *static* include, this would allow source
+within the included template to interact within the same inheritance context
+as the calling template, but currently Mako has no static include facility.
+
+In practice, this means that ``<%block>`` elements defined in an ``<%include>``
+file will not interact with corresponding ``<%block>`` elements in the calling
+template.
+
+A common mistake is along these lines:
+
+.. sourcecode:: mako
+
+ ## partials.mako
+ <%block name="header">
+ Global Header
+ </%block>
+
+ ## parent.mako
+ <%include file="partials.mako">
+
+ ## child.mako
+ <%inherit file="parent.mako">
+ <%block name="header">
+ Custom Header
+ </%block>
+
+Above, one might expect that the ``"header"`` block declared in ``child.mako``
+might be invoked, as a result of it overriding the same block present in
+``parent.mako`` via the include for ``partials.mako``. But this is not the case.
+Instead, ``parent.mako`` will invoke ``partials.mako``, which then invokes
+``"header"`` in ``partials.mako``, and then is finished rendering. Nothing
+from ``child.mako`` will render; there is no interaction between the ``"header"``
+block in ``child.mako`` and the ``"header"`` block in ``partials.mako``.
+
+Instead, ``parent.mako`` must explicitly state the inheritance structure.
+In order to call upon specific elements of ``partials.mako``, we will call upon
+it as a namespace:
+
+.. sourcecode:: mako
+
+ ## partials.mako
+ <%block name="header">
+ Global Header
+ </%block>
+
+ ## parent.mako
+ <%namespace name="partials" file="partials.mako"/>
+ <%block name="header">
+ ${partials.header()}
+ </%block>
+
+ ## child.mako
+ <%inherit file="parent.mako">
+ <%block name="header">
+ Custom Header
+ </%block>
+
+Where above, ``parent.mako`` states the inheritance structure that ``child.mako``
+is to participate within. ``partials.mako`` only defines defs/blocks that can be
+used on a per-name basis.
+
+Another scenario is below, which results in both ``"SectionA"`` blocks being rendered for the ``child.mako`` document:
+
+.. sourcecode:: mako
+
+ ## base.mako
+ ${self.body()}
+ <%block name="SectionA">
+ base.mako
+ </%block>
+
+ ## parent.mako
+ <%inherit file="base.mako">
+ <%include file="child.mako">
+
+ ## child.mako
+ <%block name="SectionA">
+ child.mako
+ </%block>
+
+The resolution is similar; instead of using ``<%include>``, we call upon the blocks
+of ``child.mako`` using a namespace:
+
+.. sourcecode:: mako
+
+ ## parent.mako
+ <%inherit file="base.mako">
+ <%namespace name="child" file="child.mako">
+
+ <%block name="SectionA">
+ ${child.SectionA()}
+ </%block>
+
+
.. _inheritance_attr:
Inheritable Attributes
diff --git a/mako/pyparser.py b/mako/pyparser.py
index aa2d882..db5c295 100644
--- a/mako/pyparser.py
+++ b/mako/pyparser.py
@@ -102,7 +102,7 @@ if _ast:
if node.name is not None:
self._add_declared(node.name)
if node.type is not None:
- self.listener.undeclared_identifiers.add(node.type.id)
+ self.visit(node.type)
for statement in node.body:
self.visit(statement)
diff --git a/mako/util.py b/mako/util.py
index 0108109..48e3a23 100644
--- a/mako/util.py
+++ b/mako/util.py
@@ -11,15 +11,10 @@ import os
from mako import compat
import operator
-def function_named(fn, name):
- """Return a function with a given __name__.
-
- Will assign to __name__ and return the original function if possible on
- the Python implementation, otherwise a new function will be constructed.
-
- """
- fn.__name__ = name
- return fn
+def update_wrapper(decorated, fn):
+ decorated.__wrapped__ = fn
+ decorated.__name__ = fn.__name__
+ return decorated
class PluginLoader(object):
diff --git a/scripts/mako-render b/scripts/mako-render
index 122589f..2b951ab 100755
--- a/scripts/mako-render
+++ b/scripts/mako-render
@@ -48,7 +48,13 @@ def main(argv=None):
kw = dict([varsplit(var) for var in opts.var])
data = fo.read()
- print(render(data, kw, lookup_dirs=lookup_dirs))
+
+ try:
+ print(render(data, kw, lookup_dirs=lookup_dirs))
+ except:
+ from mako import exceptions
+ print(exceptions.text_error_template().render(), file=sys.stderr)
+ sys.exit(1)
if __name__ == "__main__":
main()
diff --git a/setup.cfg b/setup.cfg
index c933a4d..6a03440 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -5,6 +5,11 @@ tag_build = dev
[wheel]
universal = 1
+[pytest]
+addopts= --tb native -v -r fxX
+python_files=test/*test_*.py
+
+
[upload]
sign = 1
identity = C4DAFEE1
diff --git a/test/__init__.py b/test/__init__.py
index f114f64..64dde8e 100644
--- a/test/__init__.py
+++ b/test/__init__.py
@@ -2,7 +2,7 @@ from mako.template import Template
import unittest
import os
from mako.compat import py3k, py26, py25
-from mako.util import function_named
+from mako.util import update_wrapper
import re
from mako.cache import CacheImpl, register_plugin
from nose import SkipTest
@@ -93,7 +93,7 @@ def skip_if(predicate, reason=None):
raise SkipTest(msg)
else:
return fn(*args, **kw)
- return function_named(maybe, fn_name)
+ return update_wrapper(maybe, fn)
return decorate
def requires_python_3(fn):
@@ -124,7 +124,7 @@ def requires_no_pygments_exceptions(fn):
return fn(*arg, **kw)
finally:
exceptions._install_highlighting()
- return function_named(go, fn.__name__)
+ return update_wrapper(go, fn)
class PlainCacheImpl(CacheImpl):
"""Simple memory cache impl so that tests which
diff --git a/test/test_ast.py b/test/test_ast.py
index 9f9ec10..32a1d74 100644
--- a/test/test_ast.py
+++ b/test/test_ast.py
@@ -1,7 +1,8 @@
import unittest
from mako import ast, exceptions, pyparser, util, compat
-from test import eq_, requires_python_2, requires_python_3
+from test import eq_, requires_python_2, requires_python_3, \
+ requires_python_26_or_greater
exception_kwargs = {
'source': '',
@@ -222,6 +223,28 @@ t2 = lambda (x,y):(x+5, y+4)
eq_(parsed.declared_identifiers, set(['t1', 't2']))
eq_(parsed.undeclared_identifiers, set())
+ @requires_python_26_or_greater
+ def test_locate_identifiers_16(self):
+ code = """
+try:
+ print(x)
+except Exception as e:
+ print(y)
+"""
+ parsed = ast.PythonCode(code, **exception_kwargs)
+ eq_(parsed.undeclared_identifiers, set(['x', 'y', 'Exception']))
+
+ @requires_python_26_or_greater
+ def test_locate_identifiers_17(self):
+ code = """
+try:
+ print(x)
+except (Foo, Bar) as e:
+ print(y)
+"""
+ parsed = ast.PythonCode(code, **exception_kwargs)
+ eq_(parsed.undeclared_identifiers, set(['x', 'y', 'Foo', 'Bar']))
+
def test_no_global_imports(self):
code = """
from foo import *