summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--.travis.yml11
-rw-r--r--doc/build/changelog.rst62
-rw-r--r--doc/build/defs.rst2
-rw-r--r--doc/build/syntax.rst31
-rw-r--r--doc/build/templates/base.mako8
-rw-r--r--mako/__init__.py2
-rw-r--r--mako/_ast_util.py16
-rw-r--r--mako/ast.py37
-rw-r--r--mako/cache.py2
-rwxr-xr-xmako/cmd.py21
-rw-r--r--mako/codegen.py454
-rw-r--r--mako/compat.py46
-rw-r--r--mako/exceptions.py45
-rw-r--r--mako/ext/autohandler.py15
-rw-r--r--mako/ext/babelplugin.py11
-rw-r--r--mako/ext/beaker_cache.py1
-rw-r--r--mako/ext/extract.py13
-rw-r--r--mako/ext/linguaplugin.py11
-rw-r--r--mako/ext/preprocessors.py8
-rw-r--r--mako/ext/pygmentplugin.py29
-rw-r--r--mako/ext/turbogears.py8
-rw-r--r--mako/filters.py14
-rw-r--r--mako/lexer.py124
-rw-r--r--mako/lookup.py142
-rw-r--r--mako/parsetree.py262
-rw-r--r--mako/pygen.py24
-rw-r--r--mako/pyparser.py15
-rw-r--r--mako/runtime.py107
-rw-r--r--mako/template.py253
-rw-r--r--mako/util.py40
-rw-r--r--setup.cfg2
-rw-r--r--test/__init__.py4
-rw-r--r--test/ext/test_babelplugin.py20
-rw-r--r--test/templates/gettext.mako31
-rw-r--r--test/templates/gettext_cp1251.mako1
-rw-r--r--test/templates/gettext_utf8.mako1
-rw-r--r--test/test_template.py41
-rw-r--r--tox.ini16
39 files changed, 1159 insertions, 774 deletions
diff --git a/.gitignore b/.gitignore
index d518bf8..c6d828e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,6 @@
/include
/lib
/man
+
+.tox/
+.cache/
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..69a61b8
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,11 @@
+language: python
+python: 2.7
+env:
+ - TOX_ENV=py34
+ - TOX_ENV=py33
+ - TOX_ENV=py27
+ - TOX_ENV=py26
+install:
+ - python -m pip install -U tox
+script:
+ - python -m tox -e $TOX_ENV
diff --git a/doc/build/changelog.rst b/doc/build/changelog.rst
index 03aaec7..25d63b8 100644
--- a/doc/build/changelog.rst
+++ b/doc/build/changelog.rst
@@ -6,6 +6,68 @@ Changelog
===
.. changelog::
+ :version: 1.0.4
+
+.. changelog::
+ :version: 1.0.3
+ :released: Tue Oct 27 2015
+
+ .. change::
+ :tags: bug, babel
+ :pullreq: bitbucket:21
+
+ Fixed an issue where the Babel plugin would not handle a translation
+ symbol that contained non-ascii characters. Pull request courtesy
+ Roman Imankulov.
+
+.. changelog::
+ :version: 1.0.2
+ :released: Wed Aug 26 2015
+
+ .. change::
+ :tags: bug, installation
+ :tickets: 249
+
+ The "universal wheel" marker is removed from setup.cfg, because
+ our setup.py currently makes use of conditional dependencies.
+ In :ticket:`249`, the discussion is ongoing on how to correct our
+ setup.cfg / setup.py fully so that we can handle the per-version
+ dependency changes while still maintaining optimal wheel settings,
+ so this issue is not yet fully resolved.
+
+ .. change::
+ :tags: bug, py3k
+ :tickets: 250
+
+ Repair some calls within the ast module that no longer work on Python3.5;
+ additionally replace the use of ``inspect.getargspec()`` under
+ Python 3 (seems to be called from the TG plugin) to avoid deprecation
+ warnings.
+
+ .. change::
+ :tags: bug
+ :pullreq: bitbucket:18
+
+ Update the Lingua translation extraction plugin to correctly
+ handle templates mixing Python control statements (such as if,
+ for and while) with template fragments. Pull request courtesy
+ Laurent Daverio.
+
+ .. change::
+ :tags: feature
+ :tickets: 236
+
+ Added ``STOP_RENDERING`` keyword for returning/exiting from a
+ template early, which is a synonym for an empty string ``""``.
+ Previously, the docs suggested a bare
+ ``return``, but this could cause ``None`` to appear in the
+ rendered template result.
+
+ .. seealso::
+
+ :ref:`syntax_exiting_early`
+
+.. changelog::
:version: 1.0.1
:released: Thu Jan 22 2015
diff --git a/doc/build/defs.rst b/doc/build/defs.rst
index 3c06840..314e9b9 100644
--- a/doc/build/defs.rst
+++ b/doc/build/defs.rst
@@ -216,7 +216,7 @@ modules, you can define arbitrarily nestable tags right in your
templates.
To achieve this, the target def is invoked using the form
-``<%namepacename:defname>`` instead of the normal ``${}``
+``<%namespacename:defname>`` instead of the normal ``${}``
syntax. This syntax, introduced in Mako 0.2.3, is functionally
equivalent to another tag known as ``%call``, which takes the form
``<%call expr='namespacename.defname(args)'>``. While ``%call``
diff --git a/doc/build/syntax.rst b/doc/build/syntax.rst
index fe4a860..e3dd7db 100644
--- a/doc/build/syntax.rst
+++ b/doc/build/syntax.rst
@@ -212,7 +212,7 @@ pure-Python functions you might want to declare:
%>
Any number of ``<%! %>`` blocks can be declared anywhere in a
-template; they will be rendered in the resulting module
+template; they will be rendered in the resulting module
in a single contiguous block above all render callables,
in the order in which they appear in the source template.
@@ -443,19 +443,25 @@ Mako:
<%def name="x()">${x}</%def>
</%text>
-Returning Early from a Template
-===============================
+.. _syntax_exiting_early:
+
+Exiting Early from a Template
+=============================
Sometimes you want to stop processing a template or ``<%def>``
method in the middle and just use the text you've accumulated so
-far. You can use a ``return`` statement inside a Python
-block to do that.
+far. This is accomplished by using ``return`` statement inside
+a Python block. It's a good idea for the ``return`` statement
+to return an empty string, which prevents the Python default return
+value of ``None`` from being rendered by the template. This
+return value is for semantic purposes provided in templates via
+the ``STOP_RENDERING`` symbol:
.. sourcecode:: mako
% if not len(records):
No records found.
- <% return %>
+ <% return STOP_RENDERING %>
% endif
Or perhaps:
@@ -464,6 +470,17 @@ Or perhaps:
<%
if not len(records):
- return
+ return STOP_RENDERING
%>
+In older versions of Mako, an empty string can be substituted for
+the ``STOP_RENDERING`` symbol:
+
+.. sourcecode:: mako
+
+ <% return '' %>
+
+.. versionadded:: 1.0.2 - added the ``STOP_RENDERING`` symbol which serves
+ as a semantic identifier for the empty string ``""`` used by a
+ Python ``return`` statement.
+
diff --git a/doc/build/templates/base.mako b/doc/build/templates/base.mako
index 32c49d8..b23be0f 100644
--- a/doc/build/templates/base.mako
+++ b/doc/build/templates/base.mako
@@ -19,14 +19,6 @@
<div id="wrap">
<div class="rightbar">
- % if toolbar:
- <div id="gittip_nav">
- <iframe style="border: 0; margin: 0; padding: 0;"
- src="https://www.gittip.com/zzzeek/widget.html"
- width="48pt" height="20pt"></iframe>
- </div>
- % endif
-
<div class="slogan">
Hyperfast and lightweight templating for the Python platform.
</div>
diff --git a/mako/__init__.py b/mako/__init__.py
index d963848..850f2a6 100644
--- a/mako/__init__.py
+++ b/mako/__init__.py
@@ -5,4 +5,4 @@
# the MIT License: http://www.opensource.org/licenses/mit-license.php
-__version__ = '1.0.1'
+__version__ = '1.0.4'
diff --git a/mako/_ast_util.py b/mako/_ast_util.py
index efbc4fc..cc298d5 100644
--- a/mako/_ast_util.py
+++ b/mako/_ast_util.py
@@ -30,7 +30,7 @@
:copyright: Copyright 2008 by Armin Ronacher.
:license: Python License.
"""
-from _ast import *
+from _ast import * # noqa
from mako.compat import arg_stringname
BOOLOP_SYMBOLS = {
@@ -246,6 +246,7 @@ def walk(node):
class NodeVisitor(object):
+
"""
Walks the abstract syntax tree and call visitor functions for every node
found. The visitor functions may return values which will be forwarded
@@ -290,6 +291,7 @@ class NodeVisitor(object):
class NodeTransformer(NodeVisitor):
+
"""
Walks the abstract syntax tree and allows modifications of nodes.
@@ -349,6 +351,7 @@ class NodeTransformer(NodeVisitor):
class SourceGenerator(NodeVisitor):
+
"""
This visitor is able to transform a well formed syntax tree into python
sourcecode. For more details have a look at the docstring of the
@@ -388,6 +391,7 @@ class SourceGenerator(NodeVisitor):
def signature(self, node):
want_comma = []
+
def write_comma():
if want_comma:
self.write(', ')
@@ -460,6 +464,7 @@ class SourceGenerator(NodeVisitor):
def visit_ClassDef(self, node):
have_args = []
+
def paren_or_comma():
if have_args:
self.write(', ')
@@ -481,11 +486,11 @@ class SourceGenerator(NodeVisitor):
paren_or_comma()
self.write(keyword.arg + '=')
self.visit(keyword.value)
- if node.starargs is not None:
+ if getattr(node, "starargs", None):
paren_or_comma()
self.write('*')
self.visit(node.starargs)
- if node.kwargs is not None:
+ if getattr(node, "kwargs", None):
paren_or_comma()
self.write('**')
self.visit(node.kwargs)
@@ -631,6 +636,7 @@ class SourceGenerator(NodeVisitor):
def visit_Call(self, node):
want_comma = []
+
def write_comma():
if want_comma:
self.write(', ')
@@ -646,11 +652,11 @@ class SourceGenerator(NodeVisitor):
write_comma()
self.write(keyword.arg + '=')
self.visit(keyword.value)
- if node.starargs is not None:
+ if getattr(node, "starargs", None):
write_comma()
self.write('*')
self.visit(node.starargs)
- if node.kwargs is not None:
+ if getattr(node, "kwargs", None):
write_comma()
self.write('**')
self.visit(node.kwargs)
diff --git a/mako/ast.py b/mako/ast.py
index 65fd84d..c55b29c 100644
--- a/mako/ast.py
+++ b/mako/ast.py
@@ -10,8 +10,11 @@ code, as well as generating Python from AST nodes"""
from mako import exceptions, pyparser, compat
import re
+
class PythonCode(object):
+
"""represents information about a string containing Python code"""
+
def __init__(self, code, **exception_kwargs):
self.code = code
@@ -41,8 +44,11 @@ class PythonCode(object):
f = pyparser.FindIdentifiers(self, **exception_kwargs)
f.visit(expr)
+
class ArgumentList(object):
+
"""parses a fragment of code as a comma-separated list of expressions"""
+
def __init__(self, code, **exception_kwargs):
self.codeargs = []
self.args = []
@@ -52,7 +58,7 @@ class ArgumentList(object):
if re.match(r"\S", code) and not re.match(r",\s*$", code):
# if theres text and no trailing comma, insure its parsed
# as a tuple by adding a trailing comma
- code += ","
+ code += ","
expr = pyparser.parse(code, "exec", **exception_kwargs)
else:
expr = code
@@ -60,7 +66,9 @@ class ArgumentList(object):
f = pyparser.FindTuple(self, PythonCode, **exception_kwargs)
f.visit(expr)
+
class PythonFragment(PythonCode):
+
"""extends PythonCode to provide identifier lookups in partial control
statements
@@ -70,16 +78,17 @@ class PythonFragment(PythonCode):
except (MyException, e):
etc.
"""
+
def __init__(self, code, **exception_kwargs):
m = re.match(r'^(\w+)(?:\s+(.*?))?:\s*(#|$)', code.strip(), re.S)
if not m:
raise exceptions.CompileException(
- "Fragment '%s' is not a partial control statement" %
- code, **exception_kwargs)
+ "Fragment '%s' is not a partial control statement" %
+ code, **exception_kwargs)
if m.group(3):
code = code[:m.start(3)]
- (keyword, expr) = m.group(1,2)
- if keyword in ['for','if', 'while']:
+ (keyword, expr) = m.group(1, 2)
+ if keyword in ['for', 'if', 'while']:
code = code + "pass"
elif keyword == 'try':
code = code + "pass\nexcept:pass"
@@ -91,13 +100,15 @@ class PythonFragment(PythonCode):
code = code + "pass"
else:
raise exceptions.CompileException(
- "Unsupported control keyword: '%s'" %
- keyword, **exception_kwargs)
+ "Unsupported control keyword: '%s'" %
+ keyword, **exception_kwargs)
super(PythonFragment, self).__init__(code, **exception_kwargs)
class FunctionDecl(object):
+
"""function declaration"""
+
def __init__(self, code, allow_kwargs=True, **exception_kwargs):
self.code = code
expr = pyparser.parse(code, "exec", **exception_kwargs)
@@ -106,12 +117,12 @@ class FunctionDecl(object):
f.visit(expr)
if not hasattr(self, 'funcname'):
raise exceptions.CompileException(
- "Code '%s' is not a function declaration" % code,
- **exception_kwargs)
+ "Code '%s' is not a function declaration" % code,
+ **exception_kwargs)
if not allow_kwargs and self.kwargs:
raise exceptions.CompileException(
- "'**%s' keyword argument not allowed here" %
- self.kwargnames[-1], **exception_kwargs)
+ "'**%s' keyword argument not allowed here" %
+ self.kwargnames[-1], **exception_kwargs)
def get_argument_expressions(self, as_call=False):
"""Return the argument declarations of this FunctionDecl as a printable
@@ -170,9 +181,11 @@ class FunctionDecl(object):
def allargnames(self):
return tuple(self.argnames) + tuple(self.kwargnames)
+
class FunctionArgs(FunctionDecl):
+
"""the argument portion of a function declaration"""
def __init__(self, code, **kwargs):
super(FunctionArgs, self).__init__("def ANON(%s):pass" % code,
- **kwargs)
+ **kwargs)
diff --git a/mako/cache.py b/mako/cache.py
index c405c51..c7aabd2 100644
--- a/mako/cache.py
+++ b/mako/cache.py
@@ -13,6 +13,7 @@ register_plugin("beaker", "mako.ext.beaker_cache", "BeakerCacheImpl")
class Cache(object):
+
"""Represents a data content cache made available to the module
space of a specific :class:`.Template` object.
@@ -181,6 +182,7 @@ class Cache(object):
class CacheImpl(object):
+
"""Provide a cache implementation for use by :class:`.Cache`."""
def __init__(self, cache):
diff --git a/mako/cmd.py b/mako/cmd.py
index 1a9ca56..50d47fc 100755
--- a/mako/cmd.py
+++ b/mako/cmd.py
@@ -10,26 +10,31 @@ from mako.template import Template
from mako.lookup import TemplateLookup
from mako import exceptions
+
def varsplit(var):
if "=" not in var:
return (var, "")
return var.split("=", 1)
+
def _exit():
sys.stderr.write(exceptions.text_error_template().render())
sys.exit(1)
+
def cmdline(argv=None):
parser = ArgumentParser("usage: %prog [FILENAME]")
- parser.add_argument("--var", default=[], action="append",
- help="variable (can be used multiple times, use name=value)")
- parser.add_argument("--template-dir", default=[], action="append",
- help="Directory to use for template lookup (multiple "
- "directories may be provided). If not given then if the "
- "template is read from stdin, the value defaults to be "
- "the current directory, otherwise it defaults to be the "
- "parent directory of the file provided.")
+ parser.add_argument(
+ "--var", default=[], action="append",
+ help="variable (can be used multiple times, use name=value)")
+ parser.add_argument(
+ "--template-dir", default=[], action="append",
+ help="Directory to use for template lookup (multiple "
+ "directories may be provided). If not given then if the "
+ "template is read from stdin, the value defaults to be "
+ "the current directory, otherwise it defaults to be the "
+ "parent directory of the file provided.")
parser.add_argument('input', nargs='?', default='-')
options = parser.parse_args(argv)
diff --git a/mako/codegen.py b/mako/codegen.py
index 4b0bda8..bf86d79 100644
--- a/mako/codegen.py
+++ b/mako/codegen.py
@@ -19,22 +19,23 @@ MAGIC_NUMBER = 10
# names which are hardwired into the
# template and are not accessed via the
# context itself
-RESERVED_NAMES = set(['context', 'loop', 'UNDEFINED'])
+TOPLEVEL_DECLARED = set(["UNDEFINED", "STOP_RENDERING"])
+RESERVED_NAMES = set(['context', 'loop']).union(TOPLEVEL_DECLARED)
-def compile(node,
- uri,
- filename=None,
- default_filters=None,
- buffer_filters=None,
- imports=None,
- future_imports=None,
- source_encoding=None,
- generate_magic_comment=True,
- disable_unicode=False,
- strict_undefined=False,
- enable_loop=True,
- reserved_names=frozenset()):
+def compile(node,
+ uri,
+ filename=None,
+ default_filters=None,
+ buffer_filters=None,
+ imports=None,
+ future_imports=None,
+ source_encoding=None,
+ generate_magic_comment=True,
+ disable_unicode=False,
+ strict_undefined=False,
+ enable_loop=True,
+ reserved_names=frozenset()):
"""Generate module source code given a parsetree node,
uri, and optional source filename"""
@@ -45,40 +46,41 @@ def compile(node,
if not compat.py3k and isinstance(source_encoding, compat.text_type):
source_encoding = source_encoding.encode(source_encoding)
-
buf = util.FastEncodingBuffer()
printer = PythonPrinter(buf)
_GenerateRenderMethod(printer,
- _CompileContext(uri,
- filename,
- default_filters,
- buffer_filters,
- imports,
- future_imports,
- source_encoding,
- generate_magic_comment,
- disable_unicode,
- strict_undefined,
- enable_loop,
- reserved_names),
- node)
+ _CompileContext(uri,
+ filename,
+ default_filters,
+ buffer_filters,
+ imports,
+ future_imports,
+ source_encoding,
+ generate_magic_comment,
+ disable_unicode,
+ strict_undefined,
+ enable_loop,
+ reserved_names),
+ node)
return buf.getvalue()
+
class _CompileContext(object):
+
def __init__(self,
- uri,
- filename,
- default_filters,
- buffer_filters,
- imports,
- future_imports,
- source_encoding,
- generate_magic_comment,
- disable_unicode,
- strict_undefined,
- enable_loop,
- reserved_names):
+ uri,
+ filename,
+ default_filters,
+ buffer_filters,
+ imports,
+ future_imports,
+ source_encoding,
+ generate_magic_comment,
+ disable_unicode,
+ strict_undefined,
+ enable_loop,
+ reserved_names):
self.uri = uri
self.filename = filename
self.default_filters = default_filters
@@ -92,11 +94,14 @@ class _CompileContext(object):
self.enable_loop = enable_loop
self.reserved_names = reserved_names
+
class _GenerateRenderMethod(object):
+
"""A template visitor object which generates the
full module source for a template.
"""
+
def __init__(self, printer, compiler, node):
self.printer = printer
self.compiler = compiler
@@ -124,9 +129,9 @@ class _GenerateRenderMethod(object):
args += ['**pageargs']
cached = eval(pagetag.attributes.get('cached', 'False'))
self.compiler.enable_loop = self.compiler.enable_loop or eval(
- pagetag.attributes.get(
- 'enable_loop', 'False')
- )
+ pagetag.attributes.get(
+ 'enable_loop', 'False')
+ )
else:
args = ['**pageargs']
cached = False
@@ -137,9 +142,9 @@ class _GenerateRenderMethod(object):
args = [a for a in ['context'] + args]
self.write_render_callable(
- pagetag or node,
- name, args,
- buffered, filtered, cached)
+ pagetag or node,
+ name, args,
+ buffered, filtered, cached)
if defs is not None:
for node in defs:
@@ -150,7 +155,7 @@ class _GenerateRenderMethod(object):
def write_metadata_struct(self):
self.printer.source_map[self.printer.lineno] = \
- max(self.printer.source_map)
+ max(self.printer.source_map)
struct = {
"filename": self.compiler.filename,
"uri": self.compiler.uri,
@@ -181,12 +186,16 @@ class _GenerateRenderMethod(object):
self.compiler.pagetag = None
class FindTopLevel(object):
+
def visitInheritTag(s, node):
inherit.append(node)
+
def visitNamespaceTag(s, node):
namespaces[node.name] = node
+
def visitPageTag(s, node):
self.compiler.pagetag = node
+
def visitCode(s, node):
if node.ismodule:
module_code.append(node)
@@ -208,49 +217,50 @@ class _GenerateRenderMethod(object):
if self.compiler.generate_magic_comment and \
self.compiler.source_encoding:
self.printer.writeline("# -*- coding:%s -*-" %
- self.compiler.source_encoding)
+ self.compiler.source_encoding)
if self.compiler.future_imports:
self.printer.writeline("from __future__ import %s" %
(", ".join(self.compiler.future_imports),))
self.printer.writeline("from mako import runtime, filters, cache")
self.printer.writeline("UNDEFINED = runtime.UNDEFINED")
+ self.printer.writeline("STOP_RENDERING = runtime.STOP_RENDERING")
self.printer.writeline("__M_dict_builtin = dict")
self.printer.writeline("__M_locals_builtin = locals")
self.printer.writeline("_magic_number = %r" % MAGIC_NUMBER)
self.printer.writeline("_modified_time = %r" % time.time())
self.printer.writeline("_enable_loop = %r" % self.compiler.enable_loop)
self.printer.writeline(
- "_template_filename = %r" % self.compiler.filename)
+ "_template_filename = %r" % self.compiler.filename)
self.printer.writeline("_template_uri = %r" % self.compiler.uri)
self.printer.writeline(
- "_source_encoding = %r" % self.compiler.source_encoding)
+ "_source_encoding = %r" % self.compiler.source_encoding)
if self.compiler.imports:
buf = ''
for imp in self.compiler.imports:
buf += imp + "\n"
self.printer.writeline(imp)
impcode = ast.PythonCode(
- buf,
- source='', lineno=0,
- pos=0,
- filename='template defined imports')
+ buf,
+ source='', lineno=0,
+ pos=0,
+ filename='template defined imports')
else:
impcode = None
main_identifiers = module_identifiers.branch(self.node)
module_identifiers.topleveldefs = \
module_identifiers.topleveldefs.\
- union(main_identifiers.topleveldefs)
- module_identifiers.declared.add("UNDEFINED")
+ union(main_identifiers.topleveldefs)
+ module_identifiers.declared.update(TOPLEVEL_DECLARED)
if impcode:
module_identifiers.declared.update(impcode.declared_identifiers)
self.compiler.identifiers = module_identifiers
self.printer.writeline("_exports = %r" %
- [n.name for n in
- main_identifiers.topleveldefs.values()]
- )
+ [n.name for n in
+ main_identifiers.topleveldefs.values()]
+ )
self.printer.write_blanks(2)
if len(module_code):
@@ -265,7 +275,7 @@ class _GenerateRenderMethod(object):
return list(main_identifiers.topleveldefs.values())
def write_render_callable(self, node, name, args, buffered, filtered,
- cached):
+ cached):
"""write a top-level render callable.
this could be the main render() method or that of a top-level def."""
@@ -274,30 +284,30 @@ class _GenerateRenderMethod(object):
decorator = node.decorator
if decorator:
self.printer.writeline(
- "@runtime._decorate_toplevel(%s)" % decorator)
+ "@runtime._decorate_toplevel(%s)" % decorator)
self.printer.start_source(node.lineno)
self.printer.writelines(
"def %s(%s):" % (name, ','.join(args)),
- # push new frame, assign current frame to __M_caller
- "__M_caller = context.caller_stack._push_frame()",
- "try:"
+ # push new frame, assign current frame to __M_caller
+ "__M_caller = context.caller_stack._push_frame()",
+ "try:"
)
if buffered or filtered or cached:
self.printer.writeline("context._push_buffer()")
self.identifier_stack.append(
- self.compiler.identifiers.branch(self.node))
+ self.compiler.identifiers.branch(self.node))
if (not self.in_def or self.node.is_block) and '**pageargs' in args:
self.identifier_stack[-1].argument_declared.add('pageargs')
if not self.in_def and (
- len(self.identifiers.locally_assigned) > 0 or
- len(self.identifiers.argument_declared) > 0
- ):
+ len(self.identifiers.locally_assigned) > 0 or
+ len(self.identifiers.argument_declared) > 0
+ ):
self.printer.writeline("__M_locals = __M_dict_builtin(%s)" %
- ','.join([
- "%s=%s" % (x, x) for x in
+ ','.join([
+ "%s=%s" % (x, x) for x in
self.identifiers.argument_declared
]))
@@ -311,9 +321,9 @@ class _GenerateRenderMethod(object):
self.printer.write_blanks(2)
if cached:
self.write_cache_decorator(
- node, name,
- args, buffered,
- self.identifiers, toplevel=True)
+ node, name,
+ args, buffered,
+ self.identifiers, toplevel=True)
def write_module_code(self, module_code):
"""write module-level template code, i.e. that which
@@ -327,26 +337,25 @@ class _GenerateRenderMethod(object):
self.printer.writelines(
"def _mako_inherit(template, context):",
- "_mako_generate_namespaces(context)",
- "return runtime._inherit_from(context, %s, _template_uri)" %
- (node.parsed_attributes['file']),
- None
+ "_mako_generate_namespaces(context)",
+ "return runtime._inherit_from(context, %s, _template_uri)" %
+ (node.parsed_attributes['file']),
+ None
)
def write_namespaces(self, namespaces):
"""write the module-level namespace-generating callable."""
self.printer.writelines(
"def _mako_get_namespace(context, name):",
- "try:",
- "return context.namespaces[(__name__, name)]",
- "except KeyError:",
- "_mako_generate_namespaces(context)",
- "return context.namespaces[(__name__, name)]",
+ "try:",
+ "return context.namespaces[(__name__, name)]",
+ "except KeyError:",
+ "_mako_generate_namespaces(context)",
+ "return context.namespaces[(__name__, name)]",
None, None
)
self.printer.writeline("def _mako_generate_namespaces(context):")
-
for node in namespaces.values():
if 'import' in node.attributes:
self.compiler.has_ns_imports = True
@@ -356,7 +365,9 @@ class _GenerateRenderMethod(object):
export = []
identifiers = self.compiler.identifiers.branch(node)
self.in_def = True
+
class NSDefVisitor(object):
+
def visitDefTag(s, node):
s.visitDefOrBase(node)
@@ -384,39 +395,39 @@ class _GenerateRenderMethod(object):
if 'file' in node.parsed_attributes:
self.printer.writeline(
- "ns = runtime.TemplateNamespace(%r,"
- " context._clean_inheritance_tokens(),"
- " templateuri=%s, callables=%s, "
- " calling_uri=_template_uri)" %
- (
- node.name,
- node.parsed_attributes.get('file', 'None'),
- callable_name,
- )
- )
+ "ns = runtime.TemplateNamespace(%r,"
+ " context._clean_inheritance_tokens(),"
+ " templateuri=%s, callables=%s, "
+ " calling_uri=_template_uri)" %
+ (
+ node.name,
+ node.parsed_attributes.get('file', 'None'),
+ callable_name,
+ )
+ )
elif 'module' in node.parsed_attributes:
self.printer.writeline(
- "ns = runtime.ModuleNamespace(%r,"
- " context._clean_inheritance_tokens(),"
- " callables=%s, calling_uri=_template_uri,"
- " module=%s)" %
- (
- node.name,
- callable_name,
- node.parsed_attributes.get(
- 'module', 'None')
- )
- )
+ "ns = runtime.ModuleNamespace(%r,"
+ " context._clean_inheritance_tokens(),"
+ " callables=%s, calling_uri=_template_uri,"
+ " module=%s)" %
+ (
+ node.name,
+ callable_name,
+ node.parsed_attributes.get(
+ 'module', 'None')
+ )
+ )
else:
self.printer.writeline(
- "ns = runtime.Namespace(%r,"
- " context._clean_inheritance_tokens(),"
- " callables=%s, calling_uri=_template_uri)" %
- (
- node.name,
- callable_name,
- )
- )
+ "ns = runtime.Namespace(%r,"
+ " context._clean_inheritance_tokens(),"
+ " callables=%s, calling_uri=_template_uri)" %
+ (
+ node.name,
+ callable_name,
+ )
+ )
if eval(node.attributes.get('inheritable', "False")):
self.printer.writeline("context['self'].%s = ns" % (node.name))
@@ -457,7 +468,7 @@ class _GenerateRenderMethod(object):
# write closure functions for closures that we define
# right here
to_write = to_write.union(
- [c.funcname for c in identifiers.closuredefs.values()])
+ [c.funcname for c in identifiers.closuredefs.values()])
# remove identifiers that are declared in the argument
# signature of the callable
@@ -487,12 +498,12 @@ class _GenerateRenderMethod(object):
for ident, ns in self.compiler.namespaces.items():
if 'import' in ns.attributes:
self.printer.writeline(
- "_mako_get_namespace(context, %r)."
- "_populate(_import_ns, %r)" %
- (
- ident,
- re.split(r'\s*,\s*', ns.attributes['import'])
- ))
+ "_mako_get_namespace(context, %r)."
+ "_populate(_import_ns, %r)" %
+ (
+ ident,
+ re.split(r'\s*,\s*', ns.attributes['import'])
+ ))
if has_loop:
self.printer.writeline(
@@ -515,35 +526,36 @@ class _GenerateRenderMethod(object):
elif ident in self.compiler.namespaces:
self.printer.writeline(
- "%s = _mako_get_namespace(context, %r)" %
- (ident, ident)
- )
+ "%s = _mako_get_namespace(context, %r)" %
+ (ident, ident)
+ )
else:
if getattr(self.compiler, 'has_ns_imports', False):
if self.compiler.strict_undefined:
self.printer.writelines(
- "%s = _import_ns.get(%r, UNDEFINED)" %
- (ident, ident),
- "if %s is UNDEFINED:" % ident,
+ "%s = _import_ns.get(%r, UNDEFINED)" %
+ (ident, ident),
+ "if %s is UNDEFINED:" % ident,
"try:",
- "%s = context[%r]" % (ident, ident),
+ "%s = context[%r]" % (ident, ident),
"except KeyError:",
- "raise NameError(\"'%s' is not defined\")" %
- ident,
+ "raise NameError(\"'%s' is not defined\")" %
+ ident,
None, None
)
else:
self.printer.writeline(
- "%s = _import_ns.get(%r, context.get(%r, UNDEFINED))" %
- (ident, ident, ident))
+ "%s = _import_ns.get"
+ "(%r, context.get(%r, UNDEFINED))" %
+ (ident, ident, ident))
else:
if self.compiler.strict_undefined:
self.printer.writelines(
"try:",
- "%s = context[%r]" % (ident, ident),
+ "%s = context[%r]" % (ident, ident),
"except KeyError:",
- "raise NameError(\"'%s' is not defined\")" %
- ident,
+ "raise NameError(\"'%s' is not defined\")" %
+ ident,
None
)
else:
@@ -560,14 +572,14 @@ class _GenerateRenderMethod(object):
nameargs = node.get_argument_expressions(as_call=True)
if not self.in_def and (
- len(self.identifiers.locally_assigned) > 0 or
- len(self.identifiers.argument_declared) > 0):
+ len(self.identifiers.locally_assigned) > 0 or
+ len(self.identifiers.argument_declared) > 0):
nameargs.insert(0, 'context._locals(__M_locals)')
else:
nameargs.insert(0, 'context')
self.printer.writeline("def %s(%s):" % (funcname, ",".join(namedecls)))
self.printer.writeline(
- "return render_%s(%s)" % (funcname, ",".join(nameargs)))
+ "return render_%s(%s)" % (funcname, ",".join(nameargs)))
self.printer.writeline(None)
def write_inline_def(self, node, identifiers, nested):
@@ -578,9 +590,9 @@ class _GenerateRenderMethod(object):
decorator = node.decorator
if decorator:
self.printer.writeline(
- "@runtime._decorate_inline(context, %s)" % decorator)
+ "@runtime._decorate_inline(context, %s)" % decorator)
self.printer.writeline(
- "def %s(%s):" % (node.funcname, ",".join(namedecls)))
+ "def %s(%s):" % (node.funcname, ",".join(namedecls)))
filtered = len(node.filter_args.args) > 0
buffered = eval(node.attributes.get('buffered', 'False'))
cached = eval(node.attributes.get('cached', 'False'))
@@ -607,11 +619,11 @@ class _GenerateRenderMethod(object):
self.printer.writeline(None)
if cached:
self.write_cache_decorator(node, node.funcname,
- namedecls, False, identifiers,
- inline=True, toplevel=False)
+ namedecls, False, identifiers,
+ inline=True, toplevel=False)
def write_def_finish(self, node, buffered, filtered, cached,
- callstack=True):
+ callstack=True):
"""write the end section of a rendering function, either outermost or
inline.
@@ -625,7 +637,7 @@ class _GenerateRenderMethod(object):
if callstack:
self.printer.writelines(
"finally:",
- "context.caller_stack._pop_frame()",
+ "context.caller_stack._pop_frame()",
None
)
@@ -637,7 +649,7 @@ class _GenerateRenderMethod(object):
# extra buffers
self.printer.writelines(
"finally:",
- "__M_buf = context._pop_buffer()"
+ "__M_buf = context._pop_buffer()"
)
else:
self.printer.writelines(
@@ -665,8 +677,8 @@ class _GenerateRenderMethod(object):
)
def write_cache_decorator(self, node_or_pagetag, name,
- args, buffered, identifiers,
- inline=False, toplevel=False):
+ args, buffered, identifiers,
+ inline=False, toplevel=False):
"""write a post-function decorator to replace a rendering
callable with a cached version of itself."""
@@ -698,40 +710,40 @@ class _GenerateRenderMethod(object):
# form "arg1, arg2, arg3=arg3, arg4=arg4", etc.
pass_args = [
- "%s=%s" % ((a.split('=')[0],) * 2) if '=' in a else a
- for a in args
- ]
+ "%s=%s" % ((a.split('=')[0],) * 2) if '=' in a else a
+ for a in args
+ ]
self.write_variable_declares(
- identifiers,
- toplevel=toplevel,
- limit=node_or_pagetag.undeclared_identifiers()
- )
+ identifiers,
+ toplevel=toplevel,
+ limit=node_or_pagetag.undeclared_identifiers()
+ )
if buffered:
s = "context.get('local')."\
"cache._ctx_get_or_create("\
"%s, lambda:__M_%s(%s), context, %s__M_defname=%r)" % (
- cachekey, name, ','.join(pass_args),
- ''.join(["%s=%s, " % (k, v)
- for k, v in cache_args.items()]),
- name
- )
+ cachekey, name, ','.join(pass_args),
+ ''.join(["%s=%s, " % (k, v)
+ for k, v in cache_args.items()]),
+ name
+ )
# apply buffer_filters
s = self.create_filter_callable(self.compiler.buffer_filters, s,
False)
self.printer.writelines("return " + s, None)
else:
self.printer.writelines(
- "__M_writer(context.get('local')."
- "cache._ctx_get_or_create("
- "%s, lambda:__M_%s(%s), context, %s__M_defname=%r))" %
- (
- cachekey, name, ','.join(pass_args),
- ''.join(["%s=%s, " % (k, v)
- for k, v in cache_args.items()]),
- name,
- ),
- "return ''",
+ "__M_writer(context.get('local')."
+ "cache._ctx_get_or_create("
+ "%s, lambda:__M_%s(%s), context, %s__M_defname=%r))" %
+ (
+ cachekey, name, ','.join(pass_args),
+ ''.join(["%s=%s, " % (k, v)
+ for k, v in cache_args.items()]),
+ name,
+ ),
+ "return ''",
None
)
@@ -775,7 +787,7 @@ class _GenerateRenderMethod(object):
(
self.compiler.pagetag is not None and
len(self.compiler.pagetag.filter_args.args)
- ) or \
+ ) or \
len(self.compiler.default_filters):
s = self.create_filter_callable(node.escapes_code.args,
@@ -806,11 +818,11 @@ class _GenerateRenderMethod(object):
# 3) any control line with no content other than comments
if not children or (
compat.all(isinstance(c, (parsetree.Comment,
- parsetree.ControlLine))
- for c in children) and
+ parsetree.ControlLine))
+ for c in children) and
compat.all((node.is_ternary(c.keyword) or c.isend)
- for c in children
- if isinstance(c, parsetree.ControlLine))):
+ for c in children
+ if isinstance(c, parsetree.ControlLine))):
self.printer.writeline("pass")
def visitText(self, node):
@@ -832,9 +844,9 @@ class _GenerateRenderMethod(object):
"__M_buf, __M_writer = context._pop_buffer_and_writer()",
"__M_writer(%s)" %
self.create_filter_callable(
- node.filter_args.args,
- "__M_buf.getvalue()",
- False),
+ node.filter_args.args,
+ "__M_buf.getvalue()",
+ False),
None
)
@@ -861,12 +873,12 @@ class _GenerateRenderMethod(object):
args = node.attributes.get('args')
if args:
self.printer.writeline(
- "runtime._include_file(context, %s, _template_uri, %s)" %
- (node.parsed_attributes['file'], args))
+ "runtime._include_file(context, %s, _template_uri, %s)" %
+ (node.parsed_attributes['file'], args))
else:
self.printer.writeline(
- "runtime._include_file(context, %s, _template_uri)" %
- (node.parsed_attributes['file']))
+ "runtime._include_file(context, %s, _template_uri)" %
+ (node.parsed_attributes['file']))
def visitNamespaceTag(self, node):
pass
@@ -880,9 +892,10 @@ class _GenerateRenderMethod(object):
else:
nameargs = node.get_argument_expressions(as_call=True)
nameargs += ['**pageargs']
- self.printer.writeline("if 'parent' not in context._data or "
- "not hasattr(context._data['parent'], '%s'):"
- % node.funcname)
+ self.printer.writeline(
+ "if 'parent' not in context._data or "
+ "not hasattr(context._data['parent'], '%s'):"
+ % node.funcname)
self.printer.writeline(
"context['self'].%s(%s)" % (node.funcname, ",".join(nameargs)))
self.printer.writeline("\n")
@@ -905,7 +918,9 @@ class _GenerateRenderMethod(object):
body_identifiers.add_declared('caller')
self.identifier_stack.append(body_identifiers)
+
class DefVisitor(object):
+
def visitDefTag(s, node):
s.visitDefOrBase(node)
@@ -954,19 +969,21 @@ class _GenerateRenderMethod(object):
self.printer.writelines(
# push on caller for nested call
"context.caller_stack.nextcaller = "
- "runtime.Namespace('caller', context, "
- "callables=ccall(__M_caller))",
+ "runtime.Namespace('caller', context, "
+ "callables=ccall(__M_caller))",
"try:")
self.printer.start_source(node.lineno)
self.printer.writelines(
- "__M_writer(%s)" % self.create_filter_callable(
- [], node.expression, True),
+ "__M_writer(%s)" % self.create_filter_callable(
+ [], node.expression, True),
"finally:",
- "context.caller_stack.nextcaller = None",
+ "context.caller_stack.nextcaller = None",
None
)
+
class _Identifiers(object):
+
"""tracks the status of identifier names as template code is rendered."""
def __init__(self, compiler, node=None, parent=None, nested=False):
@@ -980,9 +997,9 @@ class _Identifiers(object):
# things that have already been declared
# in an enclosing namespace (i.e. names we can just use)
self.declared = set(parent.declared).\
- union([c.name for c in parent.closuredefs.values()]).\
- union(parent.locally_declared).\
- union(parent.argument_declared)
+ union([c.name for c in parent.closuredefs.values()]).\
+ union(parent.locally_declared).\
+ union(parent.argument_declared)
# if these identifiers correspond to a "nested"
# scope, it means whatever the parent identifiers
@@ -1026,13 +1043,12 @@ class _Identifiers(object):
node.accept_visitor(self)
illegal_names = self.compiler.reserved_names.intersection(
- self.locally_declared)
+ self.locally_declared)
if illegal_names:
raise exceptions.NameConflictError(
"Reserved words declared in template: %s" %
", ".join(illegal_names))
-
def branch(self, node, **kwargs):
"""create a new Identifiers for a new Node, with
this Identifiers as the parent."""
@@ -1045,15 +1061,15 @@ class _Identifiers(object):
def __repr__(self):
return "Identifiers(declared=%r, locally_declared=%r, "\
- "undeclared=%r, topleveldefs=%r, closuredefs=%r, "\
- "argumentdeclared=%r)" %\
- (
- list(self.declared),
- list(self.locally_declared),
- list(self.undeclared),
- [c.name for c in self.topleveldefs.values()],
- [c.name for c in self.closuredefs.values()],
- self.argument_declared)
+ "undeclared=%r, topleveldefs=%r, closuredefs=%r, "\
+ "argumentdeclared=%r)" %\
+ (
+ list(self.declared),
+ list(self.locally_declared),
+ list(self.undeclared),
+ [c.name for c in self.topleveldefs.values()],
+ [c.name for c in self.closuredefs.values()],
+ self.argument_declared)
def check_declared(self, node):
"""update the state of this Identifiers with the undeclared
@@ -1081,7 +1097,7 @@ class _Identifiers(object):
if not node.ismodule:
self.check_declared(node)
self.locally_assigned = self.locally_assigned.union(
- node.declared_identifiers())
+ node.declared_identifiers())
def visitNamespaceTag(self, node):
# only traverse into the sub-elements of a
@@ -1095,12 +1111,12 @@ class _Identifiers(object):
existing = collection.get(node.funcname)
collection[node.funcname] = node
if existing is not None and \
- existing is not node and \
- (node.is_block or existing.is_block):
+ existing is not node and \
+ (node.is_block or existing.is_block):
raise exceptions.CompileException(
- "%%def or %%block named '%s' already "
- "exists in this template." %
- node.funcname, **node.exception_kwargs)
+ "%%def or %%block named '%s' already "
+ "exists in this template." %
+ node.funcname, **node.exception_kwargs)
def visitDefTag(self, node):
if node.is_root() and not node.is_anonymous:
@@ -1126,13 +1142,13 @@ class _Identifiers(object):
if isinstance(self.node, parsetree.DefTag):
raise exceptions.CompileException(
- "Named block '%s' not allowed inside of def '%s'"
- % (node.name, self.node.name), **node.exception_kwargs)
+ "Named block '%s' not allowed inside of def '%s'"
+ % (node.name, self.node.name), **node.exception_kwargs)
elif isinstance(self.node,
(parsetree.CallTag, parsetree.CallNamespaceTag)):
raise exceptions.CompileException(
- "Named block '%s' not allowed inside of <%%call> tag"
- % (node.name, ), **node.exception_kwargs)
+ "Named block '%s' not allowed inside of <%%call> tag"
+ % (node.name, ), **node.exception_kwargs)
for ident in node.undeclared_identifiers():
if ident != 'context' and \
@@ -1171,7 +1187,7 @@ class _Identifiers(object):
for ident in node.undeclared_identifiers():
if ident != 'context' and \
ident not in self.declared.union(
- self.locally_declared):
+ self.locally_declared):
self.undeclared.add(ident)
for ident in node.declared_identifiers():
self.argument_declared.add(ident)
@@ -1181,15 +1197,16 @@ class _Identifiers(object):
for ident in node.undeclared_identifiers():
if ident != 'context' and \
ident not in self.declared.union(
- self.locally_declared):
+ self.locally_declared):
self.undeclared.add(ident)
_FOR_LOOP = re.compile(
- r'^for\s+((?:\(?)\s*[A-Za-z_][A-Za-z_0-9]*'
- r'(?:\s*,\s*(?:[A-Za-z_][A-Za-z0-9_]*),??)*\s*(?:\)?))\s+in\s+(.*):'
+ r'^for\s+((?:\(?)\s*[A-Za-z_][A-Za-z_0-9]*'
+ r'(?:\s*,\s*(?:[A-Za-z_][A-Za-z0-9_]*),??)*\s*(?:\)?))\s+in\s+(.*):'
)
+
def mangle_mako_loop(node, printer):
"""converts a for loop into a context manager wrapped around a for loop
when access to the `loop` variable has been detected in the for loop body
@@ -1201,9 +1218,9 @@ def mangle_mako_loop(node, printer):
match = _FOR_LOOP.match(node.text)
if match:
printer.writelines(
- 'loop = __M_loop._enter(%s)' % match.group(2),
- 'try:'
- #'with __M_loop(%s) as loop:' % match.group(2)
+ 'loop = __M_loop._enter(%s)' % match.group(2),
+ 'try:'
+ # 'with __M_loop(%s) as loop:' % match.group(2)
)
text = 'for %s in loop:' % match.group(1)
else:
@@ -1214,6 +1231,7 @@ def mangle_mako_loop(node, printer):
class LoopVariable(object):
+
"""A node visitor which looks for the name 'loop' within undeclared
identifiers."""
diff --git a/mako/compat.py b/mako/compat.py
index fe277bb..db22b99 100644
--- a/mako/compat.py
+++ b/mako/compat.py
@@ -10,6 +10,27 @@ win32 = sys.platform.startswith('win')
pypy = hasattr(sys, 'pypy_version_info')
if py3k:
+ # create a "getargspec" from getfullargspec(), which is not deprecated
+ # in Py3K; getargspec() has started to emit warnings as of Py3.5.
+ # As of Py3.4, now they are trying to move from getfullargspec()
+ # to "signature()", but getfullargspec() is not deprecated, so stick
+ # with that for now.
+
+ import collections
+ ArgSpec = collections.namedtuple(
+ "ArgSpec",
+ ["args", "varargs", "keywords", "defaults"])
+ from inspect import getfullargspec as inspect_getfullargspec
+
+ def inspect_getargspec(func):
+ return ArgSpec(
+ *inspect_getfullargspec(func)[0:4]
+ )
+else:
+ from inspect import getargspec as inspect_getargspec # noqa
+
+
+if py3k:
from io import StringIO
import builtins as compat_builtins
from urllib.parse import quote_plus, unquote_plus
@@ -30,7 +51,7 @@ if py3k:
return eval("0o" + lit)
else:
- import __builtin__ as compat_builtins
+ import __builtin__ as compat_builtins # noqa
try:
from cStringIO import StringIO
except:
@@ -38,14 +59,14 @@ else:
byte_buffer = StringIO
- from urllib import quote_plus, unquote_plus
- from htmlentitydefs import codepoint2name, name2codepoint
- string_types = basestring,
+ from urllib import quote_plus, unquote_plus # noqa
+ from htmlentitydefs import codepoint2name, name2codepoint # noqa
+ string_types = basestring, # noqa
binary_type = str
- text_type = unicode
+ text_type = unicode # noqa
def u(s):
- return unicode(s, "utf-8")
+ return unicode(s, "utf-8") # noqa
def b(s):
return s
@@ -56,10 +77,12 @@ else:
if py33:
from importlib import machinery
+
def load_module(module_id, path):
return machinery.SourceFileLoader(module_id, path).load_module()
else:
import imp
+
def load_module(module_id, path):
fp = open(path, 'rb')
try:
@@ -77,7 +100,7 @@ if py3k:
raise value
else:
exec("def reraise(tp, value, tb=None, cause=None):\n"
- " raise tp, value, tb\n")
+ " raise tp, value, tb\n")
def exception_as():
@@ -90,11 +113,11 @@ try:
else:
import thread
except ImportError:
- import dummy_threading as threading
+ import dummy_threading as threading # noqa
if py3k:
import _dummy_thread as thread
else:
- import dummy_thread as thread
+ import dummy_thread as thread # noqa
if win32 or jython:
time_func = time.clock
@@ -113,13 +136,15 @@ except:
all = all
-import json
+import json # noqa
+
def exception_name(exc):
return exc.__class__.__name__
try:
from inspect import CO_VARKEYWORDS, CO_VARARGS
+
def inspect_func_args(fn):
if py3k:
co = fn.__code__
@@ -144,6 +169,7 @@ try:
return args, varargs, varkw, fn.func_defaults
except ImportError:
import inspect
+
def inspect_func_args(fn):
return inspect.getargspec(fn)
diff --git a/mako/exceptions.py b/mako/exceptions.py
index c531f21..84d2297 100644
--- a/mako/exceptions.py
+++ b/mako/exceptions.py
@@ -10,12 +10,15 @@ import traceback
import sys
from mako import util, compat
+
class MakoException(Exception):
pass
+
class RuntimeException(MakoException):
pass
+
def _format_filepos(lineno, pos, filename):
if filename is None:
return " at line: %d char: %d" % (lineno, pos)
@@ -24,42 +27,56 @@ def _format_filepos(lineno, pos, filename):
class CompileException(MakoException):
+
def __init__(self, message, source, lineno, pos, filename):
- MakoException.__init__(self,
- message + _format_filepos(lineno, pos, filename))
+ MakoException.__init__(
+ self,
+ message + _format_filepos(lineno, pos, filename))
self.lineno = lineno
self.pos = pos
self.filename = filename
self.source = source
+
class SyntaxException(MakoException):
+
def __init__(self, message, source, lineno, pos, filename):
- MakoException.__init__(self,
- message + _format_filepos(lineno, pos, filename))
+ MakoException.__init__(
+ self,
+ message + _format_filepos(lineno, pos, filename))
self.lineno = lineno
self.pos = pos
self.filename = filename
self.source = source
+
class UnsupportedError(MakoException):
+
"""raised when a retired feature is used."""
+
class NameConflictError(MakoException):
+
"""raised when a reserved word is used inappropriately"""
+
class TemplateLookupException(MakoException):
pass
+
class TopLevelLookupException(TemplateLookupException):
pass
+
class RichTraceback(object):
+
"""Pull the current exception from the ``sys`` traceback and extracts
Mako-specific template information.
See the usage examples in :ref:`handling_exceptions`.
"""
+
def __init__(self, error=None, traceback=None):
self.source, self.lineno = "", 0
@@ -162,18 +179,18 @@ class RichTraceback(object):
else:
line = line.decode('ascii', 'replace')
new_trcback.append((filename, lineno, function, line,
- None, None, None, None))
+ None, None, None, None))
continue
template_ln = 1
source_map = mako.template.ModuleInfo.\
- get_module_source_metadata(
- module_source, full_line_map=True)
+ get_module_source_metadata(
+ module_source, full_line_map=True)
line_map = source_map['full_line_map']
- template_lines = [line for line in
- template_source.split("\n")]
+ template_lines = [line_ for line_ in
+ template_source.split("\n")]
mods[filename] = (line_map, template_lines)
template_ln = line_map[lineno - 1]
@@ -235,16 +252,19 @@ ${tback.errorname}: ${tback.message}
def _install_pygments():
global syntax_highlight, pygments_html_formatter
- from mako.ext.pygmentplugin import syntax_highlight,\
- pygments_html_formatter
+ from mako.ext.pygmentplugin import syntax_highlight # noqa
+ from mako.ext.pygmentplugin import pygments_html_formatter # noqa
+
def _install_fallback():
global syntax_highlight, pygments_html_formatter
from mako.filters import html_escape
pygments_html_formatter = None
+
def syntax_highlight(filename='', language=None):
return html_escape
+
def _install_highlighting():
try:
_install_pygments()
@@ -252,6 +272,7 @@ def _install_highlighting():
_install_fallback()
_install_highlighting()
+
def html_error_template():
"""Provides a template that renders a stack trace in an HTML format,
providing an excerpt of code as well as substituting source template
@@ -370,4 +391,4 @@ def html_error_template():
</html>
% endif
""", output_encoding=sys.getdefaultencoding(),
- encoding_errors='htmlentityreplace')
+ encoding_errors='htmlentityreplace')
diff --git a/mako/ext/autohandler.py b/mako/ext/autohandler.py
index 8deaae1..9ee780a 100644
--- a/mako/ext/autohandler.py
+++ b/mako/ext/autohandler.py
@@ -25,7 +25,10 @@ or with custom autohandler filename:
"""
-import posixpath, os, re
+import posixpath
+import os
+import re
+
def autohandler(template, context, name='autohandler'):
lookup = context.lookup
@@ -42,24 +45,24 @@ def autohandler(template, context, name='autohandler'):
if path != _template_uri and _file_exists(lookup, path):
if not lookup.filesystem_checks:
return lookup._uri_cache.setdefault(
- (autohandler, _template_uri, name), path)
+ (autohandler, _template_uri, name), path)
else:
return path
if len(tokens) == 1:
break
tokens[-2:] = [name]
-
+
if not lookup.filesystem_checks:
return lookup._uri_cache.setdefault(
- (autohandler, _template_uri, name), None)
+ (autohandler, _template_uri, name), None)
else:
return None
+
def _file_exists(lookup, path):
- psub = re.sub(r'^/', '',path)
+ psub = re.sub(r'^/', '', path)
for d in lookup.directories:
if os.path.exists(d + '/' + psub):
return True
else:
return False
-
diff --git a/mako/ext/babelplugin.py b/mako/ext/babelplugin.py
index ead7081..53d62ba 100644
--- a/mako/ext/babelplugin.py
+++ b/mako/ext/babelplugin.py
@@ -10,14 +10,15 @@ from mako.ext.extract import MessageExtractor
class BabelMakoExtractor(MessageExtractor):
+
def __init__(self, keywords, comment_tags, options):
self.keywords = keywords
self.options = options
self.config = {
- 'comment-tags': u' '.join(comment_tags),
- 'encoding': options.get('input_encoding',
- options.get('encoding', None)),
- }
+ 'comment-tags': u' '.join(comment_tags),
+ 'encoding': options.get('input_encoding',
+ options.get('encoding', None)),
+ }
super(BabelMakoExtractor, self).__init__()
def __call__(self, fileobj):
@@ -27,7 +28,7 @@ class BabelMakoExtractor(MessageExtractor):
comment_tags = self.config['comment-tags']
for lineno, funcname, messages, python_translator_comments \
in extract_python(code,
- self.keywords, comment_tags, self.options):
+ self.keywords, comment_tags, self.options):
yield (code_lineno + (lineno - 1), funcname, messages,
translator_strings + python_translator_comments)
diff --git a/mako/ext/beaker_cache.py b/mako/ext/beaker_cache.py
index 40ef774..c7c260d 100644
--- a/mako/ext/beaker_cache.py
+++ b/mako/ext/beaker_cache.py
@@ -15,6 +15,7 @@ _beaker_cache = None
class BeakerCacheImpl(CacheImpl):
+
"""A :class:`.CacheImpl` provided for the Beaker caching system.
This plugin is used by default, based on the default
diff --git a/mako/ext/extract.py b/mako/ext/extract.py
index 5ce5175..8dd2e96 100644
--- a/mako/ext/extract.py
+++ b/mako/ext/extract.py
@@ -5,6 +5,7 @@ from mako import parsetree
class MessageExtractor(object):
+
def process_file(self, fileobj):
template_node = lexer.Lexer(
fileobj.read(),
@@ -15,6 +16,7 @@ class MessageExtractor(object):
def extract_nodes(self, nodes):
translator_comments = []
in_translator_comments = False
+ input_encoding = self.config['encoding'] or 'ascii'
comment_tags = list(
filter(None, re.split(r'\s+', self.config['comment-tags'])))
@@ -75,13 +77,18 @@ class MessageExtractor(object):
comment[1] for comment in translator_comments]
if isinstance(code, compat.text_type):
- code = code.encode('ascii', 'backslashreplace')
+ code = code.encode(input_encoding, 'backslashreplace')
used_translator_comments = False
- code = compat.byte_buffer(code)
+ # We add extra newline to work around a pybabel bug
+ # (see python-babel/babel#274, parse_encoding dies if the first
+ # input string of the input is non-ascii)
+ # Also, because we added it, we have to subtract one from
+ # node.lineno
+ code = compat.byte_buffer(compat.b('\n') + code)
for message in self.process_python(
- code, node.lineno, translator_strings):
+ code, node.lineno - 1, translator_strings):
yield message
used_translator_comments = True
diff --git a/mako/ext/linguaplugin.py b/mako/ext/linguaplugin.py
index a809072..46b0d6a 100644
--- a/mako/ext/linguaplugin.py
+++ b/mako/ext/linguaplugin.py
@@ -7,6 +7,7 @@ from mako import compat
class LinguaMakoExtractor(Extractor, MessageExtractor):
+
'''Mako templates'''
extensions = ['.mako']
default_config = {
@@ -25,10 +26,14 @@ class LinguaMakoExtractor(Extractor, MessageExtractor):
def process_python(self, code, code_lineno, translator_strings):
source = code.getvalue().strip()
if source.endswith(compat.b(':')):
- source += compat.b(' pass')
- code = io.BytesIO(source)
+ if source in (compat.b('try:'), compat.b('else:')) or source.startswith(compat.b('except')):
+ source = compat.b('') # Ignore try/except and else
+ elif source.startswith(compat.b('elif')):
+ source = source[2:] # Replace "elif" with "if"
+ source += compat.b('pass')
+ code = io.BytesIO(source)
for msg in self.python_extractor(
- self.filename, self.options, code, code_lineno):
+ self.filename, self.options, code, code_lineno -1):
if translator_strings:
msg = Message(msg.msgctxt, msg.msgid, msg.msgid_plural,
msg.flags,
diff --git a/mako/ext/preprocessors.py b/mako/ext/preprocessors.py
index c24893b..5624f70 100644
--- a/mako/ext/preprocessors.py
+++ b/mako/ext/preprocessors.py
@@ -4,17 +4,17 @@
# This module is part of Mako and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
-"""preprocessing functions, used with the 'preprocessor'
+"""preprocessing functions, used with the 'preprocessor'
argument on Template, TemplateLookup"""
import re
+
def convert_comments(text):
"""preprocess old style comments.
-
+
example:
-
+
from mako.ext.preprocessors import convert_comments
t = Template(..., preprocessor=convert_comments)"""
return re.sub(r'(?<=\n)\s*#[^#]', "##", text)
-
diff --git a/mako/ext/pygmentplugin.py b/mako/ext/pygmentplugin.py
index 3adcfb8..1121c5d 100644
--- a/mako/ext/pygmentplugin.py
+++ b/mako/ext/pygmentplugin.py
@@ -5,16 +5,17 @@
# the MIT License: http://www.opensource.org/licenses/mit-license.php
from pygments.lexers.web import \
- HtmlLexer, XmlLexer, JavascriptLexer, CssLexer
+ HtmlLexer, XmlLexer, JavascriptLexer, CssLexer
from pygments.lexers.agile import PythonLexer, Python3Lexer
from pygments.lexer import DelegatingLexer, RegexLexer, bygroups, \
- include, using
+ include, using
from pygments.token import \
- Text, Comment, Operator, Keyword, Name, String, Other
+ Text, Comment, Operator, Keyword, Name, String, Other
from pygments.formatters.html import HtmlFormatter
from pygments import highlight
from mako import compat
+
class MakoLexer(RegexLexer):
name = 'Mako'
aliases = ['mako']
@@ -27,15 +28,15 @@ class MakoLexer(RegexLexer):
(r'(\s*)(\%(?!%))([^\n]*)(\n|\Z)',
bygroups(Text, Comment.Preproc, using(PythonLexer), Other)),
(r'(\s*)(##[^\n]*)(\n|\Z)',
- bygroups(Text, Comment.Preproc, Other)),
+ bygroups(Text, Comment.Preproc, Other)),
(r'''(?s)<%doc>.*?</%doc>''', Comment.Preproc),
(r'(<%)([\w\.\:]+)',
- bygroups(Comment.Preproc, Name.Builtin), 'tag'),
+ bygroups(Comment.Preproc, Name.Builtin), 'tag'),
(r'(</%)([\w\.\:]+)(>)',
- bygroups(Comment.Preproc, Name.Builtin, Comment.Preproc)),
+ bygroups(Comment.Preproc, Name.Builtin, Comment.Preproc)),
(r'<%(?=([\w\.\:]+))', Comment.Preproc, 'ondeftags'),
(r'(<%(?:!?))(.*?)(%>)(?s)',
- bygroups(Comment.Preproc, using(PythonLexer), Comment.Preproc)),
+ bygroups(Comment.Preproc, using(PythonLexer), Comment.Preproc)),
(r'(\$\{)(.*?)(\})',
bygroups(Comment.Preproc, using(PythonLexer), Comment.Preproc)),
(r'''(?sx)
@@ -79,7 +80,8 @@ class MakoHtmlLexer(DelegatingLexer):
def __init__(self, **options):
super(MakoHtmlLexer, self).__init__(HtmlLexer, MakoLexer,
- **options)
+ **options)
+
class MakoXmlLexer(DelegatingLexer):
name = 'XML+Mako'
@@ -87,7 +89,8 @@ class MakoXmlLexer(DelegatingLexer):
def __init__(self, **options):
super(MakoXmlLexer, self).__init__(XmlLexer, MakoLexer,
- **options)
+ **options)
+
class MakoJavascriptLexer(DelegatingLexer):
name = 'JavaScript+Mako'
@@ -95,7 +98,8 @@ class MakoJavascriptLexer(DelegatingLexer):
def __init__(self, **options):
super(MakoJavascriptLexer, self).__init__(JavascriptLexer,
- MakoLexer, **options)
+ MakoLexer, **options)
+
class MakoCssLexer(DelegatingLexer):
name = 'CSS+Mako'
@@ -103,11 +107,13 @@ class MakoCssLexer(DelegatingLexer):
def __init__(self, **options):
super(MakoCssLexer, self).__init__(CssLexer, MakoLexer,
- **options)
+ **options)
pygments_html_formatter = HtmlFormatter(cssclass='syntax-highlighted',
linenos=True)
+
+
def syntax_highlight(filename='', language=None):
mako_lexer = MakoLexer()
if compat.py3k:
@@ -119,4 +125,3 @@ def syntax_highlight(filename='', language=None):
pygments_html_formatter)
return lambda string: highlight(string, python_lexer,
pygments_html_formatter)
-
diff --git a/mako/ext/turbogears.py b/mako/ext/turbogears.py
index d3976d9..2e7d039 100644
--- a/mako/ext/turbogears.py
+++ b/mako/ext/turbogears.py
@@ -4,12 +4,13 @@
# This module is part of Mako and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
-import inspect
from mako import compat
from mako.lookup import TemplateLookup
from mako.template import Template
+
class TGPlugin(object):
+
"""TurboGears compatible Template Plugin."""
def __init__(self, extra_vars_func=None, options=None, extension='mak'):
@@ -30,7 +31,7 @@ class TGPlugin(object):
self.tmpl_options = {}
# transfer lookup args to template args, based on those available
# in getargspec
- for kw in inspect.getargspec(Template.__init__)[0]:
+ for kw in compat.inspect_getargspec(Template.__init__)[0]:
if kw in lookup_options:
self.tmpl_options[kw] = lookup_options[kw]
@@ -41,7 +42,7 @@ class TGPlugin(object):
# Translate TG dot notation to normal / template path
if '/' not in templatename:
templatename = '/' + templatename.replace('.', '/') + '.' +\
- self.extension
+ self.extension
# Lookup template
return self.lookup.get_template(templatename)
@@ -55,4 +56,3 @@ class TGPlugin(object):
info.update(self.extra_vars_func())
return template.render(**info)
-
diff --git a/mako/filters.py b/mako/filters.py
index d79ce23..525aeb8 100644
--- a/mako/filters.py
+++ b/mako/filters.py
@@ -9,7 +9,7 @@ import re
import codecs
from mako.compat import quote_plus, unquote_plus, codepoint2name, \
- name2codepoint
+ name2codepoint
from mako import compat
@@ -24,6 +24,7 @@ xml_escapes = {
# XXX: &quot; is valid in HTML and XML
# &apos; is not valid HTML, but is valid XML
+
def legacy_html_escape(s):
"""legacy HTML escape for non-unicode mode."""
s = s.replace("&", "&amp;")
@@ -40,29 +41,35 @@ try:
except ImportError:
html_escape = legacy_html_escape
+
def xml_escape(string):
return re.sub(r'([&<"\'>])', lambda m: xml_escapes[m.group()], string)
+
def url_escape(string):
# convert into a list of octets
string = string.encode("utf8")
return quote_plus(string)
+
def legacy_url_escape(string):
# convert into a list of octets
return quote_plus(string)
+
def url_unescape(string):
text = unquote_plus(string)
if not is_ascii_str(text):
text = text.decode("utf8")
return text
+
def trim(string):
return string.strip()
class Decode(object):
+
def __getattr__(self, key):
def decode(x):
if isinstance(x, compat.text_type):
@@ -77,12 +84,15 @@ decode = Decode()
_ASCII_re = re.compile(r'\A[\x00-\x7f]*\Z')
+
def is_ascii_str(text):
return isinstance(text, str) and _ASCII_re.match(text)
################################################################
+
class XMLEntityEscaper(object):
+
def __init__(self, codepoint2name, name2codepoint):
self.codepoint2entity = dict([(c, compat.text_type('&%s;' % n))
for c, n in codepoint2name.items()])
@@ -102,7 +112,6 @@ class XMLEntityEscaper(object):
except (KeyError, IndexError):
return '&#x%X;' % codepoint
-
__escapable = re.compile(r'["&<>]|[^\x00-\x7f]')
def escape(self, text):
@@ -198,4 +207,3 @@ if compat.py3k:
NON_UNICODE_ESCAPES = DEFAULT_ESCAPES.copy()
NON_UNICODE_ESCAPES['h'] = 'filters.legacy_html_escape'
NON_UNICODE_ESCAPES['u'] = 'filters.legacy_url_escape'
-
diff --git a/mako/lexer.py b/mako/lexer.py
index 1dda398..2fa08e4 100644
--- a/mako/lexer.py
+++ b/mako/lexer.py
@@ -13,10 +13,12 @@ from mako.pygen import adjust_whitespace
_regexp_cache = {}
+
class Lexer(object):
+
def __init__(self, text, filename=None,
- disable_unicode=False,
- input_encoding=None, preprocessor=None):
+ disable_unicode=False,
+ input_encoding=None, preprocessor=None):
self.text = text
self.filename = filename
self.template = parsetree.TemplateNode(self.filename)
@@ -32,8 +34,8 @@ class Lexer(object):
if compat.py3k and disable_unicode:
raise exceptions.UnsupportedError(
- "Mako for Python 3 does not "
- "support disabling Unicode")
+ "Mako for Python 3 does not "
+ "support disabling Unicode")
if preprocessor is None:
self.preprocessor = []
@@ -87,9 +89,9 @@ class Lexer(object):
cp -= 1
self.matched_charpos = mp - cp
self.lineno += len(lines)
- #print "MATCHED:", match.group(0), "LINE START:",
+ # print "MATCHED:", match.group(0), "LINE START:",
# self.matched_lineno, "LINE END:", self.lineno
- #print "MATCH:", regexp, "\n", self.text[mp : mp + 15], \
+ # print "MATCH:", regexp, "\n", self.text[mp : mp + 15], \
# (match and "TRUE" or "FALSE")
return match
@@ -120,9 +122,9 @@ class Lexer(object):
brace_level -= match.group(1).count('}')
continue
raise exceptions.SyntaxException(
- "Expected: %s" %
- ','.join(text),
- **self.exception_kwargs)
+ "Expected: %s" %
+ ','.join(text),
+ **self.exception_kwargs)
def append_node(self, nodecls, *args, **kwargs):
kwargs.setdefault('source', self.text)
@@ -162,9 +164,9 @@ class Lexer(object):
elif self.control_line and \
not self.control_line[-1].is_ternary(node.keyword):
raise exceptions.SyntaxException(
- "Keyword '%s' not a legal ternary for keyword '%s'" %
- (node.keyword, self.control_line[-1].keyword),
- **self.exception_kwargs)
+ "Keyword '%s' not a legal ternary for keyword '%s'" %
+ (node.keyword, self.control_line[-1].keyword),
+ **self.exception_kwargs)
_coding_re = re.compile(r'#.*coding[:=]\s*([-\w.]+).*\r?\n')
@@ -185,10 +187,10 @@ class Lexer(object):
m = self._coding_re.match(text.decode('utf-8', 'ignore'))
if m is not None and m.group(1) != 'utf-8':
raise exceptions.CompileException(
- "Found utf-8 BOM in file, with conflicting "
- "magic encoding comment of '%s'" % m.group(1),
- text.decode('utf-8', 'ignore'),
- 0, 0, filename)
+ "Found utf-8 BOM in file, with conflicting "
+ "magic encoding comment of '%s'" % m.group(1),
+ text.decode('utf-8', 'ignore'),
+ 0, 0, filename)
else:
m = self._coding_re.match(text.decode('utf-8', 'ignore'))
if m:
@@ -201,18 +203,19 @@ class Lexer(object):
text = text.decode(parsed_encoding)
except UnicodeDecodeError:
raise exceptions.CompileException(
- "Unicode decode operation of encoding '%s' failed" %
- parsed_encoding,
- text.decode('utf-8', 'ignore'),
- 0, 0, filename)
+ "Unicode decode operation of encoding '%s' failed" %
+ parsed_encoding,
+ text.decode('utf-8', 'ignore'),
+ 0, 0, filename)
return parsed_encoding, text
def parse(self):
- self.encoding, self.text = self.decode_raw_stream(self.text,
- not self.disable_unicode,
- self.encoding,
- self.filename,)
+ self.encoding, self.text = self.decode_raw_stream(
+ self.text,
+ not self.disable_unicode,
+ self.encoding,
+ self.filename)
for preproc in self.preprocessor:
self.text = preproc(self.text)
@@ -250,15 +253,15 @@ class Lexer(object):
if len(self.tag):
raise exceptions.SyntaxException("Unclosed tag: <%%%s>" %
- self.tag[-1].keyword,
- **self.exception_kwargs)
+ self.tag[-1].keyword,
+ **self.exception_kwargs)
if len(self.control_line):
raise exceptions.SyntaxException(
- "Unterminated control keyword: '%s'" %
- self.control_line[-1].keyword,
- self.text,
- self.control_line[-1].lineno,
- self.control_line[-1].pos, self.filename)
+ "Unterminated control keyword: '%s'" %
+ self.control_line[-1].keyword,
+ self.text,
+ self.control_line[-1].lineno,
+ self.control_line[-1].pos, self.filename)
return self.template
def match_tag_start(self):
@@ -276,7 +279,7 @@ class Lexer(object):
''',
- re.I | re.S | re.X)
+ re.I | re.S | re.X)
if match:
keyword, attr, isend = match.groups()
@@ -284,7 +287,7 @@ class Lexer(object):
attributes = {}
if attr:
for att in re.findall(
- r"\s*(\w+)\s*=\s*(?:'([^']*)'|\"([^\"]*)\")", attr):
+ r"\s*(\w+)\s*=\s*(?:'([^']*)'|\"([^\"]*)\")", attr):
key, val1, val2 = att
text = val1 or val2
text = text.replace('\r\n', '\n')
@@ -294,12 +297,12 @@ class Lexer(object):
self.tag.pop()
else:
if keyword == 'text':
- match = self.match(r'(.*?)(?=\</%text>)', re.S)
+ match = self.match(r'(.*?)(?=\</%text>)', re.S)
if not match:
raise exceptions.SyntaxException(
- "Unclosed tag: <%%%s>" %
- self.tag[-1].keyword,
- **self.exception_kwargs)
+ "Unclosed tag: <%%%s>" %
+ self.tag[-1].keyword,
+ **self.exception_kwargs)
self.append_node(parsetree.Text, match.group(1))
return self.match_tag_end()
return True
@@ -311,14 +314,14 @@ class Lexer(object):
if match:
if not len(self.tag):
raise exceptions.SyntaxException(
- "Closing tag without opening tag: </%%%s>" %
- match.group(1),
- **self.exception_kwargs)
+ "Closing tag without opening tag: </%%%s>" %
+ match.group(1),
+ **self.exception_kwargs)
elif self.tag[-1].keyword != match.group(1):
raise exceptions.SyntaxException(
- "Closing tag </%%%s> does not match tag: <%%%s>" %
- (match.group(1), self.tag[-1].keyword),
- **self.exception_kwargs)
+ "Closing tag </%%%s> does not match tag: <%%%s>" %
+ (match.group(1), self.tag[-1].keyword),
+ **self.exception_kwargs)
self.tag.pop()
return True
else:
@@ -370,9 +373,9 @@ class Lexer(object):
# compiler.parse() not complain about indentation
text = adjust_whitespace(text) + "\n"
self.append_node(
- parsetree.Code,
- text,
- match.group(1) == '!', lineno=line, pos=pos)
+ parsetree.Code,
+ text,
+ match.group(1) == '!', lineno=line, pos=pos)
return True
else:
return False
@@ -388,17 +391,17 @@ class Lexer(object):
escapes = ""
text = text.replace('\r\n', '\n')
self.append_node(
- parsetree.Expression,
- text, escapes.strip(),
- lineno=line, pos=pos)
+ parsetree.Expression,
+ text, escapes.strip(),
+ lineno=line, pos=pos)
return True
else:
return False
def match_control_line(self):
match = self.match(
- r"(?<=^)[\t ]*(%(?!%)|##)[\t ]*((?:(?:\\r?\n)|[^\r\n])*)"
- r"(?:\r?\n|\Z)", re.M)
+ r"(?<=^)[\t ]*(%(?!%)|##)[\t ]*((?:(?:\\r?\n)|[^\r\n])*)"
+ r"(?:\r?\n|\Z)", re.M)
if match:
operator = match.group(1)
text = match.group(2)
@@ -406,23 +409,23 @@ class Lexer(object):
m2 = re.match(r'(end)?(\w+)\s*(.*)', text)
if not m2:
raise exceptions.SyntaxException(
- "Invalid control line: '%s'" %
- text,
- **self.exception_kwargs)
+ "Invalid control line: '%s'" %
+ text,
+ **self.exception_kwargs)
isend, keyword = m2.group(1, 2)
isend = (isend is not None)
if isend:
if not len(self.control_line):
raise exceptions.SyntaxException(
- "No starting keyword '%s' for '%s'" %
- (keyword, text),
- **self.exception_kwargs)
+ "No starting keyword '%s' for '%s'" %
+ (keyword, text),
+ **self.exception_kwargs)
elif self.control_line[-1].keyword != keyword:
raise exceptions.SyntaxException(
- "Keyword '%s' doesn't match keyword '%s'" %
- (text, self.control_line[-1].keyword),
- **self.exception_kwargs)
+ "Keyword '%s' doesn't match keyword '%s'" %
+ (text, self.control_line[-1].keyword),
+ **self.exception_kwargs)
self.append_node(parsetree.ControlLine, keyword, isend, text)
else:
self.append_node(parsetree.Comment, text)
@@ -438,4 +441,3 @@ class Lexer(object):
return True
else:
return False
-
diff --git a/mako/lookup.py b/mako/lookup.py
index 2af5411..e6dff9d 100644
--- a/mako/lookup.py
+++ b/mako/lookup.py
@@ -4,7 +4,10 @@
# This module is part of Mako and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
-import os, stat, posixpath, re
+import os
+import stat
+import posixpath
+import re
from mako import exceptions, util
from mako.template import Template
@@ -13,7 +16,9 @@ try:
except:
import dummy_threading as threading
+
class TemplateCollection(object):
+
"""Represent a collection of :class:`.Template` objects,
identifiable via URI.
@@ -79,7 +84,9 @@ class TemplateCollection(object):
"""
return uri
+
class TemplateLookup(TemplateCollection):
+
"""Represent a collection of templates that locates template source files
from the local filesystem.
@@ -145,35 +152,35 @@ class TemplateLookup(TemplateCollection):
"""
def __init__(self,
- directories=None,
- module_directory=None,
- filesystem_checks=True,
- collection_size=-1,
- format_exceptions=False,
- error_handler=None,
- disable_unicode=False,
- bytestring_passthrough=False,
- output_encoding=None,
- encoding_errors='strict',
-
- cache_args=None,
- cache_impl='beaker',
- cache_enabled=True,
- cache_type=None,
- cache_dir=None,
- cache_url=None,
-
- modulename_callable=None,
- module_writer=None,
- default_filters=None,
- buffer_filters=(),
- strict_undefined=False,
- imports=None,
- future_imports=None,
- enable_loop=True,
- input_encoding=None,
- preprocessor=None,
- lexer_cls=None):
+ directories=None,
+ module_directory=None,
+ filesystem_checks=True,
+ collection_size=-1,
+ format_exceptions=False,
+ error_handler=None,
+ disable_unicode=False,
+ bytestring_passthrough=False,
+ output_encoding=None,
+ encoding_errors='strict',
+
+ cache_args=None,
+ cache_impl='beaker',
+ cache_enabled=True,
+ cache_type=None,
+ cache_dir=None,
+ cache_url=None,
+
+ modulename_callable=None,
+ module_writer=None,
+ default_filters=None,
+ buffer_filters=(),
+ strict_undefined=False,
+ imports=None,
+ future_imports=None,
+ enable_loop=True,
+ input_encoding=None,
+ preprocessor=None,
+ lexer_cls=None):
self.directories = [posixpath.normpath(d) for d in
util.to_list(directories, ())
@@ -194,26 +201,26 @@ class TemplateLookup(TemplateCollection):
cache_args.setdefault('type', cache_type)
self.template_args = {
- 'format_exceptions':format_exceptions,
- 'error_handler':error_handler,
- 'disable_unicode':disable_unicode,
- 'bytestring_passthrough':bytestring_passthrough,
- 'output_encoding':output_encoding,
- 'cache_impl':cache_impl,
- 'encoding_errors':encoding_errors,
- 'input_encoding':input_encoding,
- 'module_directory':module_directory,
- 'module_writer':module_writer,
- 'cache_args':cache_args,
- 'cache_enabled':cache_enabled,
- 'default_filters':default_filters,
- 'buffer_filters':buffer_filters,
- 'strict_undefined':strict_undefined,
- 'imports':imports,
- 'future_imports':future_imports,
- 'enable_loop':enable_loop,
- 'preprocessor':preprocessor,
- 'lexer_cls':lexer_cls
+ 'format_exceptions': format_exceptions,
+ 'error_handler': error_handler,
+ 'disable_unicode': disable_unicode,
+ 'bytestring_passthrough': bytestring_passthrough,
+ 'output_encoding': output_encoding,
+ 'cache_impl': cache_impl,
+ 'encoding_errors': encoding_errors,
+ 'input_encoding': input_encoding,
+ 'module_directory': module_directory,
+ 'module_writer': module_writer,
+ 'cache_args': cache_args,
+ 'cache_enabled': cache_enabled,
+ 'default_filters': default_filters,
+ 'buffer_filters': buffer_filters,
+ 'strict_undefined': strict_undefined,
+ 'imports': imports,
+ 'future_imports': future_imports,
+ 'enable_loop': enable_loop,
+ 'preprocessor': preprocessor,
+ 'lexer_cls': lexer_cls
}
if collection_size == -1:
@@ -228,7 +235,8 @@ class TemplateLookup(TemplateCollection):
"""Return a :class:`.Template` object corresponding to the given
``uri``.
- .. note:: The ``relativeto`` argument is not supported here at the moment.
+ .. note:: The ``relativeto`` argument is not supported here at
+ the moment.
"""
@@ -240,12 +248,15 @@ class TemplateLookup(TemplateCollection):
except KeyError:
u = re.sub(r'^\/+', '', uri)
for dir in self.directories:
+ # make sure the path seperators are posix - os.altsep is empty
+ # on POSIX and cannot be used.
+ dir = dir.replace(os.path.sep, posixpath.sep)
srcfile = posixpath.normpath(posixpath.join(dir, u))
if os.path.isfile(srcfile):
return self._load(srcfile, uri)
else:
raise exceptions.TopLevelLookupException(
- "Cant locate template for uri %r" % uri)
+ "Cant locate template for uri %r" % uri)
def adjust_uri(self, uri, relativeto):
"""Adjust the given ``uri`` based on the given relative URI."""
@@ -257,14 +268,13 @@ class TemplateLookup(TemplateCollection):
if uri[0] != '/':
if relativeto is not None:
v = self._uri_cache[key] = posixpath.join(
- posixpath.dirname(relativeto), uri)
+ posixpath.dirname(relativeto), uri)
else:
v = self._uri_cache[key] = '/' + uri
else:
v = self._uri_cache[key] = uri
return v
-
def filename_to_uri(self, filename):
"""Convert the given ``filename`` to a URI relative to
this :class:`.TemplateCollection`."""
@@ -304,11 +314,11 @@ class TemplateLookup(TemplateCollection):
else:
module_filename = None
self._collection[uri] = template = Template(
- uri=uri,
- filename=posixpath.normpath(filename),
- lookup=self,
- module_filename=module_filename,
- **self.template_args)
+ uri=uri,
+ filename=posixpath.normpath(filename),
+ lookup=self,
+ module_filename=module_filename,
+ **self.template_args)
return template
except:
# if compilation fails etc, ensure
@@ -326,7 +336,7 @@ class TemplateLookup(TemplateCollection):
try:
template_stat = os.stat(template.filename)
if template.module._modified_time < \
- template_stat[stat.ST_MTIME]:
+ template_stat[stat.ST_MTIME]:
self._collection.pop(uri, None)
return self._load(template.filename, uri)
else:
@@ -334,8 +344,7 @@ class TemplateLookup(TemplateCollection):
except OSError:
self._collection.pop(uri, None)
raise exceptions.TemplateLookupException(
- "Cant locate template for uri %r" % uri)
-
+ "Cant locate template for uri %r" % uri)
def put_string(self, uri, text):
"""Place a new :class:`.Template` object into this
@@ -344,10 +353,10 @@ class TemplateLookup(TemplateCollection):
"""
self._collection[uri] = Template(
- text,
- lookup=self,
- uri=uri,
- **self.template_args)
+ text,
+ lookup=self,
+ uri=uri,
+ **self.template_args)
def put_template(self, uri, template):
"""Place a new :class:`.Template` object into this
@@ -356,4 +365,3 @@ class TemplateLookup(TemplateCollection):
"""
self._collection[uri] = template
-
diff --git a/mako/parsetree.py b/mako/parsetree.py
index 49ec4e0..e7af4bc 100644
--- a/mako/parsetree.py
+++ b/mako/parsetree.py
@@ -9,7 +9,9 @@
from mako import exceptions, ast, util, filters, compat
import re
+
class Node(object):
+
"""base class for a Node in the parse tree."""
def __init__(self, source, lineno, pos, filename):
@@ -34,7 +36,9 @@ class Node(object):
method = getattr(visitor, "visit" + self.__class__.__name__, traverse)
method(self)
+
class TemplateNode(Node):
+
"""a 'container' node that stores the overall collection of nodes."""
def __init__(self, filename):
@@ -47,10 +51,12 @@ class TemplateNode(Node):
def __repr__(self):
return "TemplateNode(%s, %r)" % (
- util.sorted_dict_repr(self.page_attributes),
- self.nodes)
+ util.sorted_dict_repr(self.page_attributes),
+ self.nodes)
+
class ControlLine(Node):
+
"""defines a control line, a line-oriented python line or end tag.
e.g.::
@@ -92,9 +98,9 @@ class ControlLine(Node):
for this ControlLine"""
return keyword in {
- 'if':set(['else', 'elif']),
- 'try':set(['except', 'finally']),
- 'for':set(['else'])
+ 'if': set(['else', 'elif']),
+ 'try': set(['except', 'finally']),
+ 'for': set(['else'])
}.get(self.keyword, [])
def __repr__(self):
@@ -105,7 +111,9 @@ class ControlLine(Node):
(self.lineno, self.pos)
)
+
class Text(Node):
+
"""defines plain text in the template."""
def __init__(self, content, **kwargs):
@@ -115,7 +123,9 @@ class Text(Node):
def __repr__(self):
return "Text(%r, %r)" % (self.content, (self.lineno, self.pos))
+
class Code(Node):
+
"""defines a Python code block, either inline or module level.
e.g.::
@@ -151,7 +161,9 @@ class Code(Node):
(self.lineno, self.pos)
)
+
class Comment(Node):
+
"""defines a comment line.
# this is a comment
@@ -165,7 +177,9 @@ class Comment(Node):
def __repr__(self):
return "Comment(%r, %r)" % (self.text, (self.lineno, self.pos))
+
class Expression(Node):
+
"""defines an inline expression.
${x+y}
@@ -185,10 +199,10 @@ class Expression(Node):
def undeclared_identifiers(self):
# TODO: make the "filter" shortcut list configurable at parse/gen time
return self.code.undeclared_identifiers.union(
- self.escapes_code.undeclared_identifiers.difference(
- set(filters.DEFAULT_ESCAPES.keys())
- )
- ).difference(self.code.declared_identifiers)
+ self.escapes_code.undeclared_identifiers.difference(
+ set(filters.DEFAULT_ESCAPES.keys())
+ )
+ ).difference(self.code.declared_identifiers)
def __repr__(self):
return "Expression(%r, %r, %r)" % (
@@ -197,7 +211,9 @@ class Expression(Node):
(self.lineno, self.pos)
)
+
class _TagMeta(type):
+
"""metaclass to allow Tag to produce a subclass according to
its keyword"""
@@ -212,7 +228,7 @@ class _TagMeta(type):
if ":" in keyword:
ns, defname = keyword.split(':')
return type.__call__(CallNamespaceTag, ns, defname,
- attributes, **kwargs)
+ attributes, **kwargs)
try:
cls = _TagMeta._classmap[keyword]
@@ -226,7 +242,9 @@ class _TagMeta(type):
)
return type.__call__(cls, keyword, attributes, **kwargs)
+
class Tag(compat.with_metaclass(_TagMeta, Node)):
+
"""abstract base class for tags.
<%sometag/>
@@ -239,7 +257,7 @@ class Tag(compat.with_metaclass(_TagMeta, Node)):
__keyword__ = None
def __init__(self, keyword, attributes, expressions,
- nonexpressions, required, **kwargs):
+ nonexpressions, required, **kwargs):
"""construct a new Tag instance.
this constructor not called directly, and is only called
@@ -267,7 +285,7 @@ class Tag(compat.with_metaclass(_TagMeta, Node)):
if len(missing):
raise exceptions.CompileException(
"Missing attribute(s): %s" %
- ",".join([repr(m) for m in missing]),
+ ",".join([repr(m) for m in missing]),
**self.exception_kwargs)
self.parent = None
self.nodes = []
@@ -289,14 +307,14 @@ class Tag(compat.with_metaclass(_TagMeta, Node)):
m = re.compile(r'^\${(.+?)}$', re.S).match(x)
if m:
code = ast.PythonCode(m.group(1).rstrip(),
- **self.exception_kwargs)
+ **self.exception_kwargs)
# we aren't discarding "declared_identifiers" here,
# which we do so that list comprehension-declared
# variables aren't counted. As yet can't find a
# condition that requires it here.
undeclared_identifiers = \
undeclared_identifiers.union(
- code.undeclared_identifiers)
+ code.undeclared_identifiers)
expr.append('(%s)' % m.group(1))
else:
if x:
@@ -305,15 +323,15 @@ class Tag(compat.with_metaclass(_TagMeta, Node)):
elif key in nonexpressions:
if re.search(r'\${.+?}', self.attributes[key]):
raise exceptions.CompileException(
- "Attibute '%s' in tag '%s' does not allow embedded "
- "expressions" % (key, self.keyword),
- **self.exception_kwargs)
+ "Attibute '%s' in tag '%s' does not allow embedded "
+ "expressions" % (key, self.keyword),
+ **self.exception_kwargs)
self.parsed_attributes[key] = repr(self.attributes[key])
else:
raise exceptions.CompileException(
- "Invalid attribute for tag '%s': '%s'" %
- (self.keyword, key),
- **self.exception_kwargs)
+ "Invalid attribute for tag '%s': '%s'" %
+ (self.keyword, key),
+ **self.exception_kwargs)
self.expression_undeclared_identifiers = undeclared_identifiers
def declared_identifiers(self):
@@ -324,48 +342,50 @@ class Tag(compat.with_metaclass(_TagMeta, Node)):
def __repr__(self):
return "%s(%r, %s, %r, %r)" % (self.__class__.__name__,
- self.keyword,
- util.sorted_dict_repr(self.attributes),
- (self.lineno, self.pos),
- self.nodes
- )
+ self.keyword,
+ util.sorted_dict_repr(self.attributes),
+ (self.lineno, self.pos),
+ self.nodes
+ )
+
class IncludeTag(Tag):
__keyword__ = 'include'
def __init__(self, keyword, attributes, **kwargs):
super(IncludeTag, self).__init__(
- keyword,
- attributes,
- ('file', 'import', 'args'),
- (), ('file',), **kwargs)
+ keyword,
+ attributes,
+ ('file', 'import', 'args'),
+ (), ('file',), **kwargs)
self.page_args = ast.PythonCode(
- "__DUMMY(%s)" % attributes.get('args', ''),
- **self.exception_kwargs)
+ "__DUMMY(%s)" % attributes.get('args', ''),
+ **self.exception_kwargs)
def declared_identifiers(self):
return []
def undeclared_identifiers(self):
identifiers = self.page_args.undeclared_identifiers.\
- difference(set(["__DUMMY"])).\
- difference(self.page_args.declared_identifiers)
+ difference(set(["__DUMMY"])).\
+ difference(self.page_args.declared_identifiers)
return identifiers.union(super(IncludeTag, self).
- undeclared_identifiers())
+ undeclared_identifiers())
+
class NamespaceTag(Tag):
__keyword__ = 'namespace'
def __init__(self, keyword, attributes, **kwargs):
super(NamespaceTag, self).__init__(
- keyword, attributes,
- ('file',),
- ('name','inheritable',
- 'import','module'),
- (), **kwargs)
+ keyword, attributes,
+ ('file',),
+ ('name', 'inheritable',
+ 'import', 'module'),
+ (), **kwargs)
self.name = attributes.get('name', '__anon_%s' % hex(abs(id(self))))
- if not 'name' in attributes and not 'import' in attributes:
+ if 'name' not in attributes and 'import' not in attributes:
raise exceptions.CompileException(
"'name' and/or 'import' attributes are required "
"for <%namespace>",
@@ -379,52 +399,53 @@ class NamespaceTag(Tag):
def declared_identifiers(self):
return []
+
class TextTag(Tag):
__keyword__ = 'text'
def __init__(self, keyword, attributes, **kwargs):
super(TextTag, self).__init__(
- keyword,
- attributes, (),
- ('filter'), (), **kwargs)
+ keyword,
+ attributes, (),
+ ('filter'), (), **kwargs)
self.filter_args = ast.ArgumentList(
- attributes.get('filter', ''),
- **self.exception_kwargs)
+ attributes.get('filter', ''),
+ **self.exception_kwargs)
def undeclared_identifiers(self):
return self.filter_args.\
- undeclared_identifiers.\
- difference(filters.DEFAULT_ESCAPES.keys()).union(
- self.expression_undeclared_identifiers
- )
+ undeclared_identifiers.\
+ difference(filters.DEFAULT_ESCAPES.keys()).union(
+ self.expression_undeclared_identifiers
+ )
+
class DefTag(Tag):
__keyword__ = 'def'
def __init__(self, keyword, attributes, **kwargs):
expressions = ['buffered', 'cached'] + [
- c for c in attributes if c.startswith('cache_')]
-
+ c for c in attributes if c.startswith('cache_')]
super(DefTag, self).__init__(
- keyword,
- attributes,
- expressions,
- ('name', 'filter', 'decorator'),
- ('name',),
- **kwargs)
+ keyword,
+ attributes,
+ expressions,
+ ('name', 'filter', 'decorator'),
+ ('name',),
+ **kwargs)
name = attributes['name']
if re.match(r'^[\w_]+$', name):
raise exceptions.CompileException(
- "Missing parenthesis in %def",
- **self.exception_kwargs)
+ "Missing parenthesis in %def",
+ **self.exception_kwargs)
self.function_decl = ast.FunctionDecl("def " + name + ":pass",
- **self.exception_kwargs)
+ **self.exception_kwargs)
self.name = self.function_decl.funcname
self.decorator = attributes.get('decorator', '')
self.filter_args = ast.ArgumentList(
- attributes.get('filter', ''),
- **self.exception_kwargs)
+ attributes.get('filter', ''),
+ **self.exception_kwargs)
is_anonymous = False
is_block = False
@@ -443,50 +464,50 @@ class DefTag(Tag):
res = []
for c in self.function_decl.defaults:
res += list(ast.PythonCode(c, **self.exception_kwargs).
- undeclared_identifiers)
+ undeclared_identifiers)
return set(res).union(
- self.filter_args.\
- undeclared_identifiers.\
- difference(filters.DEFAULT_ESCAPES.keys())
+ self.filter_args.
+ undeclared_identifiers.
+ difference(filters.DEFAULT_ESCAPES.keys())
).union(
self.expression_undeclared_identifiers
).difference(
self.function_decl.allargnames
)
+
class BlockTag(Tag):
__keyword__ = 'block'
def __init__(self, keyword, attributes, **kwargs):
expressions = ['buffered', 'cached', 'args'] + [
- c for c in attributes if c.startswith('cache_')]
+ c for c in attributes if c.startswith('cache_')]
super(BlockTag, self).__init__(
- keyword,
- attributes,
- expressions,
- ('name','filter', 'decorator'),
- (),
- **kwargs)
+ keyword,
+ attributes,
+ expressions,
+ ('name', 'filter', 'decorator'),
+ (),
+ **kwargs)
name = attributes.get('name')
- if name and not re.match(r'^[\w_]+$',name):
+ if name and not re.match(r'^[\w_]+$', name):
raise exceptions.CompileException(
- "%block may not specify an argument signature",
- **self.exception_kwargs)
+ "%block may not specify an argument signature",
+ **self.exception_kwargs)
if not name and attributes.get('args', None):
raise exceptions.CompileException(
- "Only named %blocks may specify args",
- **self.exception_kwargs
- )
+ "Only named %blocks may specify args",
+ **self.exception_kwargs
+ )
self.body_decl = ast.FunctionArgs(attributes.get('args', ''),
- **self.exception_kwargs)
+ **self.exception_kwargs)
self.name = name
self.decorator = attributes.get('decorator', '')
self.filter_args = ast.ArgumentList(
- attributes.get('filter', ''),
- **self.exception_kwargs)
-
+ attributes.get('filter', ''),
+ **self.exception_kwargs)
is_block = True
@@ -505,90 +526,91 @@ class BlockTag(Tag):
return self.body_decl.allargnames
def undeclared_identifiers(self):
- return (self.filter_args.\
- undeclared_identifiers.\
- difference(filters.DEFAULT_ESCAPES.keys())
+ return (self.filter_args.
+ undeclared_identifiers.
+ difference(filters.DEFAULT_ESCAPES.keys())
).union(self.expression_undeclared_identifiers)
-
class CallTag(Tag):
__keyword__ = 'call'
def __init__(self, keyword, attributes, **kwargs):
super(CallTag, self).__init__(keyword, attributes,
- ('args'), ('expr',), ('expr',), **kwargs)
+ ('args'), ('expr',), ('expr',), **kwargs)
self.expression = attributes['expr']
self.code = ast.PythonCode(self.expression, **self.exception_kwargs)
self.body_decl = ast.FunctionArgs(attributes.get('args', ''),
- **self.exception_kwargs)
+ **self.exception_kwargs)
def declared_identifiers(self):
return self.code.declared_identifiers.union(self.body_decl.allargnames)
def undeclared_identifiers(self):
return self.code.undeclared_identifiers.\
- difference(self.code.declared_identifiers)
+ difference(self.code.declared_identifiers)
+
class CallNamespaceTag(Tag):
def __init__(self, namespace, defname, attributes, **kwargs):
super(CallNamespaceTag, self).__init__(
- namespace + ":" + defname,
- attributes,
- tuple(attributes.keys()) + ('args', ),
- (),
- (),
- **kwargs)
+ namespace + ":" + defname,
+ attributes,
+ tuple(attributes.keys()) + ('args', ),
+ (),
+ (),
+ **kwargs)
self.expression = "%s.%s(%s)" % (
- namespace,
- defname,
- ",".join(["%s=%s" % (k, v) for k, v in
- self.parsed_attributes.items()
- if k != 'args'])
- )
+ namespace,
+ defname,
+ ",".join(["%s=%s" % (k, v) for k, v in
+ self.parsed_attributes.items()
+ if k != 'args'])
+ )
self.code = ast.PythonCode(self.expression, **self.exception_kwargs)
self.body_decl = ast.FunctionArgs(
- attributes.get('args', ''),
- **self.exception_kwargs)
+ attributes.get('args', ''),
+ **self.exception_kwargs)
def declared_identifiers(self):
return self.code.declared_identifiers.union(self.body_decl.allargnames)
def undeclared_identifiers(self):
return self.code.undeclared_identifiers.\
- difference(self.code.declared_identifiers)
+ difference(self.code.declared_identifiers)
+
class InheritTag(Tag):
__keyword__ = 'inherit'
def __init__(self, keyword, attributes, **kwargs):
super(InheritTag, self).__init__(
- keyword, attributes,
- ('file',), (), ('file',), **kwargs)
+ keyword, attributes,
+ ('file',), (), ('file',), **kwargs)
+
class PageTag(Tag):
__keyword__ = 'page'
def __init__(self, keyword, attributes, **kwargs):
- expressions = ['cached', 'args', 'expression_filter', 'enable_loop'] + [
- c for c in attributes if c.startswith('cache_')]
+ expressions = \
+ ['cached', 'args', 'expression_filter', 'enable_loop'] + \
+ [c for c in attributes if c.startswith('cache_')]
super(PageTag, self).__init__(
- keyword,
- attributes,
- expressions,
- (),
- (),
- **kwargs)
+ keyword,
+ attributes,
+ expressions,
+ (),
+ (),
+ **kwargs)
self.body_decl = ast.FunctionArgs(attributes.get('args', ''),
- **self.exception_kwargs)
+ **self.exception_kwargs)
self.filter_args = ast.ArgumentList(
- attributes.get('expression_filter', ''),
- **self.exception_kwargs)
+ attributes.get('expression_filter', ''),
+ **self.exception_kwargs)
def declared_identifiers(self):
return self.body_decl.allargnames
-
-
diff --git a/mako/pygen.py b/mako/pygen.py
index 5ba5125..5d87bbd 100644
--- a/mako/pygen.py
+++ b/mako/pygen.py
@@ -9,7 +9,9 @@
import re
from mako import exceptions
+
class PythonPrinter(object):
+
def __init__(self, stream):
# indentation counter
self.indent = 0
@@ -80,10 +82,11 @@ class PythonPrinter(object):
self._flush_adjusted_lines()
self.in_indent_lines = True
- if (line is None or
- re.match(r"^\s*#",line) or
+ if (
+ line is None or
+ re.match(r"^\s*#", line) or
re.match(r"^\s*$", line)
- ):
+ ):
hastext = False
else:
hastext = True
@@ -91,9 +94,10 @@ class PythonPrinter(object):
is_comment = line and len(line) and line[0] == '#'
# see if this line should decrease the indentation level
- if (not is_comment and
+ if (
+ not is_comment and
(not hastext or self._is_unindentor(line))
- ):
+ ):
if self.indent > 0:
self.indent -= 1
@@ -102,7 +106,7 @@ class PythonPrinter(object):
# module wont compile.
if len(self.indent_detail) == 0:
raise exceptions.SyntaxException(
- "Too many whitespace closures")
+ "Too many whitespace closures")
self.indent_detail.pop()
if line is None:
@@ -172,18 +176,18 @@ class PythonPrinter(object):
# should we decide that its not good enough, heres
# more stuff to check.
- #keyword = match.group(1)
+ # keyword = match.group(1)
# match the original indent keyword
- #for crit in [
+ # for crit in [
# (r'if|elif', r'else|elif'),
# (r'try', r'except|finally|else'),
# (r'while|for', r'else'),
- #]:
+ # ]:
# if re.match(crit[0], indentor) and re.match(crit[1], keyword):
# return True
- #return False
+ # return False
def _indent_line(self, line, stripspace=''):
"""indent the given line according to the current indent level.
diff --git a/mako/pyparser.py b/mako/pyparser.py
index bfa46a9..96e5335 100644
--- a/mako/pyparser.py
+++ b/mako/pyparser.py
@@ -41,11 +41,11 @@ def parse(code, mode='exec', **exception_kwargs):
return _ast_util.parse(code, '<unknown>', mode)
except Exception:
raise exceptions.SyntaxException(
- "(%s) %s (%r)" % (
- compat.exception_as().__class__.__name__,
- compat.exception_as(),
- code[0:50]
- ), **exception_kwargs)
+ "(%s) %s (%r)" % (
+ compat.exception_as().__class__.__name__,
+ compat.exception_as(),
+ code[0:50]
+ ), **exception_kwargs)
class FindIdentifiers(_ast_util.NodeVisitor):
@@ -186,10 +186,10 @@ class FindTuple(_ast_util.NodeVisitor):
self.listener.args.append(ExpressionGenerator(n).value())
self.listener.declared_identifiers = \
self.listener.declared_identifiers.union(
- p.declared_identifiers)
+ p.declared_identifiers)
self.listener.undeclared_identifiers = \
self.listener.undeclared_identifiers.union(
- p.undeclared_identifiers)
+ p.undeclared_identifiers)
class ParseFunc(_ast_util.NodeVisitor):
@@ -222,6 +222,7 @@ class ParseFunc(_ast_util.NodeVisitor):
self.listener.varargs = node.args.vararg
self.listener.kwargs = node.args.kwarg
+
class ExpressionGenerator(object):
def __init__(self, astnode):
diff --git a/mako/runtime.py b/mako/runtime.py
index 6b6a35a..8d2f4a9 100644
--- a/mako/runtime.py
+++ b/mako/runtime.py
@@ -13,6 +13,7 @@ import sys
class Context(object):
+
"""Provides runtime namespace, output buffer, and various
callstacks for templates.
@@ -80,7 +81,6 @@ class Context(object):
"""Push a ``caller`` callable onto the callstack for
this :class:`.Context`."""
-
self.caller_stack.append(caller)
def pop_caller(self):
@@ -182,7 +182,9 @@ class Context(object):
x.pop('next', None)
return c
+
class CallerStack(list):
+
def __init__(self):
self.nextcaller = None
@@ -211,6 +213,7 @@ class CallerStack(list):
class Undefined(object):
+
"""Represents an undefined value in a template.
All template modules have a constant value
@@ -218,6 +221,7 @@ class Undefined(object):
object.
"""
+
def __str__(self):
raise NameError("Undefined")
@@ -228,8 +232,11 @@ class Undefined(object):
return False
UNDEFINED = Undefined()
+STOP_RENDERING = ""
+
class LoopStack(object):
+
"""a stack for LoopContexts that implements the context manager protocol
to automatically pop off the top of the stack on context exit
"""
@@ -269,6 +276,7 @@ class LoopStack(object):
class LoopContext(object):
+
"""A magic loop variable.
Automatically accessible in any ``% for`` block.
@@ -334,8 +342,10 @@ class LoopContext(object):
class _NSAttr(object):
+
def __init__(self, parent):
self.__parent = parent
+
def __getattr__(self, key):
ns = self.__parent
while ns:
@@ -345,7 +355,9 @@ class _NSAttr(object):
ns = ns.inherits
raise AttributeError(key)
+
class Namespace(object):
+
"""Provides access to collections of rendering methods, which
can be local, from other templates, or from imported modules.
@@ -362,8 +374,8 @@ class Namespace(object):
"""
def __init__(self, name, context,
- callables=None, inherits=None,
- populate_self=True, calling_uri=None):
+ callables=None, inherits=None,
+ populate_self=True, calling_uri=None):
self.name = name
self.context = context
self.inherits = inherits
@@ -462,8 +474,8 @@ class Namespace(object):
return self.context.namespaces[key]
else:
ns = TemplateNamespace(uri, self.context._copy(),
- templateuri=uri,
- calling_uri=self._templateuri)
+ templateuri=uri,
+ calling_uri=self._templateuri)
self.context.namespaces[key] = ns
return ns
@@ -524,17 +536,19 @@ class Namespace(object):
val = getattr(self.inherits, key)
else:
raise AttributeError(
- "Namespace '%s' has no member '%s'" %
- (self.name, key))
+ "Namespace '%s' has no member '%s'" %
+ (self.name, key))
setattr(self, key, val)
return val
+
class TemplateNamespace(Namespace):
+
"""A :class:`.Namespace` specific to a :class:`.Template` instance."""
def __init__(self, name, context, template=None, templateuri=None,
- callables=None, inherits=None,
- populate_self=True, calling_uri=None):
+ callables=None, inherits=None,
+ populate_self=True, calling_uri=None):
self.name = name
self.context = context
self.inherits = inherits
@@ -543,7 +557,7 @@ class TemplateNamespace(Namespace):
if templateuri is not None:
self.template = _lookup_template(context, templateuri,
- calling_uri)
+ calling_uri)
self._templateuri = self.template.module._template_uri
elif template is not None:
self.template = template
@@ -553,8 +567,8 @@ class TemplateNamespace(Namespace):
if populate_self:
lclcallable, lclcontext = \
- _populate_self_namespace(context, self.template,
- self_ns=self)
+ _populate_self_namespace(context, self.template,
+ self_ns=self)
@property
def module(self):
@@ -589,6 +603,7 @@ class TemplateNamespace(Namespace):
if self.callables:
for key in self.callables:
yield (key, self.callables[key])
+
def get(key):
callable_ = self.template._get_def_callable(key)
return compat.partial(callable_, self.context)
@@ -606,17 +621,19 @@ class TemplateNamespace(Namespace):
else:
raise AttributeError(
- "Namespace '%s' has no member '%s'" %
- (self.name, key))
+ "Namespace '%s' has no member '%s'" %
+ (self.name, key))
setattr(self, key, val)
return val
+
class ModuleNamespace(Namespace):
+
"""A :class:`.Namespace` specific to a Python module instance."""
def __init__(self, name, context, module,
- callables=None, inherits=None,
- populate_self=True, calling_uri=None):
+ callables=None, inherits=None,
+ populate_self=True, calling_uri=None):
self.name = name
self.context = context
self.inherits = inherits
@@ -645,7 +662,6 @@ class ModuleNamespace(Namespace):
if compat.callable(callable_):
yield key, compat.partial(callable_, self.context)
-
def __getattr__(self, key):
if key in self.callables:
val = self.callables[key]
@@ -656,11 +672,12 @@ class ModuleNamespace(Namespace):
val = getattr(self.inherits, key)
else:
raise AttributeError(
- "Namespace '%s' has no member '%s'" %
- (self.name, key))
+ "Namespace '%s' has no member '%s'" %
+ (self.name, key))
setattr(self, key, val)
return val
+
def supports_caller(func):
"""Apply a caller_stack compatibility decorator to a plain
Python function.
@@ -677,6 +694,7 @@ def supports_caller(func):
context.caller_stack._pop_frame()
return wrap_stackframe
+
def capture(context, callable_, *args, **kwargs):
"""Execute the given template def, capturing the output into
a buffer.
@@ -687,9 +705,9 @@ def capture(context, callable_, *args, **kwargs):
if not compat.callable(callable_):
raise exceptions.RuntimeException(
- "capture() function expects a callable as "
- "its argument (i.e. capture(func, *args, **kwargs))"
- )
+ "capture() function expects a callable as "
+ "its argument (i.e. capture(func, *args, **kwargs))"
+ )
context._push_buffer()
try:
callable_(*args, **kwargs)
@@ -697,6 +715,7 @@ def capture(context, callable_, *args, **kwargs):
buf = context._pop_buffer()
return buf.getvalue()
+
def _decorate_toplevel(fn):
def decorate_render(render_fn):
def go(context, *args, **kw):
@@ -711,24 +730,28 @@ def _decorate_toplevel(fn):
return go
return decorate_render
+
def _decorate_inline(context, fn):
def decorate_render(render_fn):
dec = fn(render_fn)
+
def go(*args, **kw):
return dec(context, *args, **kw)
return go
return decorate_render
+
def _include_file(context, uri, calling_uri, **kwargs):
"""locate the template from the given uri and include it in
the current output."""
template = _lookup_template(context, uri, calling_uri)
(callable_, ctx) = _populate_self_namespace(
- context._clean_inheritance_tokens(),
- template)
+ context._clean_inheritance_tokens(),
+ template)
callable_(ctx, **_kwargs_for_include(callable_, context._data, **kwargs))
+
def _inherit_from(context, uri, calling_uri):
"""called by the _inherit method in template modules to set
up the inheritance chain at the start of a template's
@@ -743,9 +766,9 @@ def _inherit_from(context, uri, calling_uri):
ih = ih.inherits
lclcontext = context._locals({'next': ih})
ih.inherits = TemplateNamespace("self:%s" % template.uri,
- lclcontext,
- template=template,
- populate_self=False)
+ lclcontext,
+ template=template,
+ populate_self=False)
context._data['parent'] = lclcontext._data['local'] = ih.inherits
callable_ = getattr(template.module, '_mako_inherit', None)
if callable_ is not None:
@@ -758,23 +781,25 @@ def _inherit_from(context, uri, calling_uri):
gen_ns(context)
return (template.callable_, lclcontext)
+
def _lookup_template(context, uri, relativeto):
lookup = context._with_template.lookup
if lookup is None:
raise exceptions.TemplateLookupException(
- "Template '%s' has no TemplateLookup associated" %
- context._with_template.uri)
+ "Template '%s' has no TemplateLookup associated" %
+ context._with_template.uri)
uri = lookup.adjust_uri(uri, relativeto)
try:
return lookup.get_template(uri)
except exceptions.TopLevelLookupException:
raise exceptions.TemplateLookupException(str(compat.exception_as()))
+
def _populate_self_namespace(context, template, self_ns=None):
if self_ns is None:
self_ns = TemplateNamespace('self:%s' % template.uri,
- context, template=template,
- populate_self=False)
+ context, template=template,
+ populate_self=False)
context._data['self'] = context._data['local'] = self_ns
if hasattr(template.module, '_mako_inherit'):
ret = template.module._mako_inherit(template, context)
@@ -782,6 +807,7 @@ def _populate_self_namespace(context, template, self_ns=None):
return ret
return (template.callable_, context)
+
def _render(template, callable_, args, data, as_unicode=False):
"""create a Context and return the string
output of the given template and template callable."""
@@ -792,17 +818,18 @@ def _render(template, callable_, args, data, as_unicode=False):
buf = compat.StringIO()
else:
buf = util.FastEncodingBuffer(
- as_unicode=as_unicode,
- encoding=template.output_encoding,
- errors=template.encoding_errors)
+ as_unicode=as_unicode,
+ encoding=template.output_encoding,
+ errors=template.encoding_errors)
context = Context(buf, **data)
context._outputting_as_unicode = as_unicode
context._set_with_template(template)
_render_context(template, callable_, context, *args,
- **_kwargs_for_callable(callable_, data))
+ **_kwargs_for_callable(callable_, data))
return context._pop_buffer().getvalue()
+
def _kwargs_for_callable(callable_, data):
argspec = compat.inspect_func_args(callable_)
# for normal pages, **pageargs is usually present
@@ -817,6 +844,7 @@ def _kwargs_for_callable(callable_, data):
kwargs[arg] = data[arg]
return kwargs
+
def _kwargs_for_include(callable_, data, **kwargs):
argspec = compat.inspect_func_args(callable_)
namedargs = argspec[0] + [v for v in argspec[1:3] if v is not None]
@@ -825,6 +853,7 @@ def _kwargs_for_include(callable_, data, **kwargs):
kwargs[arg] = data[arg]
return kwargs
+
def _render_context(tmpl, callable_, context, *args, **kwargs):
import mako.template as template
# create polymorphic 'self' namespace for this
@@ -838,6 +867,7 @@ def _render_context(tmpl, callable_, context, *args, **kwargs):
(inherit, lclcontext) = _populate_self_namespace(context, tmpl.parent)
_exec_template(callable_, context, args=args, kwargs=kwargs)
+
def _exec_template(callable_, context, args=None, kwargs=None):
"""execute a rendering callable given the callable, a
Context, and optional explicit arguments
@@ -859,6 +889,7 @@ def _exec_template(callable_, context, args=None, kwargs=None):
else:
callable_(context, *args, **kwargs)
+
def _render_error(template, context, error):
if template.error_handler:
result = template.error_handler(context, error)
@@ -868,11 +899,11 @@ def _render_error(template, context, error):
error_template = exceptions.html_error_template()
if context._outputting_as_unicode:
context._buffer_stack[:] = [
- util.FastEncodingBuffer(as_unicode=True)]
+ util.FastEncodingBuffer(as_unicode=True)]
else:
context._buffer_stack[:] = [util.FastEncodingBuffer(
- error_template.output_encoding,
- error_template.encoding_errors)]
+ error_template.output_encoding,
+ error_template.encoding_errors)]
context._set_with_template(error_template)
error_template.render_context(context, error=error)
diff --git a/mako/template.py b/mako/template.py
index 2adf0f6..8fd6062 100644
--- a/mako/template.py
+++ b/mako/template.py
@@ -20,6 +20,7 @@ import weakref
class Template(object):
+
"""Represents a compiled template.
:class:`.Template` includes a reference to the original
@@ -215,34 +216,34 @@ class Template(object):
lexer_cls = Lexer
def __init__(self,
- text=None,
- filename=None,
- uri=None,
- format_exceptions=False,
- error_handler=None,
- lookup=None,
- output_encoding=None,
- encoding_errors='strict',
- module_directory=None,
- cache_args=None,
- cache_impl='beaker',
- cache_enabled=True,
- cache_type=None,
- cache_dir=None,
- cache_url=None,
- module_filename=None,
- input_encoding=None,
- disable_unicode=False,
- module_writer=None,
- bytestring_passthrough=False,
- default_filters=None,
- buffer_filters=(),
- strict_undefined=False,
- imports=None,
- future_imports=None,
- enable_loop=True,
- preprocessor=None,
- lexer_cls=None):
+ text=None,
+ filename=None,
+ uri=None,
+ format_exceptions=False,
+ error_handler=None,
+ lookup=None,
+ output_encoding=None,
+ encoding_errors='strict',
+ module_directory=None,
+ cache_args=None,
+ cache_impl='beaker',
+ cache_enabled=True,
+ cache_type=None,
+ cache_dir=None,
+ cache_url=None,
+ module_filename=None,
+ input_encoding=None,
+ disable_unicode=False,
+ module_writer=None,
+ bytestring_passthrough=False,
+ default_filters=None,
+ buffer_filters=(),
+ strict_undefined=False,
+ imports=None,
+ future_imports=None,
+ enable_loop=True,
+ preprocessor=None,
+ lexer_cls=None):
if uri:
self.module_id = re.sub(r'\W', "_", uri)
self.uri = uri
@@ -261,9 +262,9 @@ class Template(object):
u_norm = os.path.normpath(u_norm)
if u_norm.startswith(".."):
raise exceptions.TemplateLookupException(
- "Template uri \"%s\" is invalid - "
- "it cannot be relative outside "
- "of the root path." % self.uri)
+ "Template uri \"%s\" is invalid - "
+ "it cannot be relative outside "
+ "of the root path." % self.uri)
self.input_encoding = input_encoding
self.output_encoding = output_encoding
@@ -276,12 +277,12 @@ class Template(object):
if compat.py3k and disable_unicode:
raise exceptions.UnsupportedError(
- "Mako for Python 3 does not "
- "support disabling Unicode")
+ "Mako for Python 3 does not "
+ "support disabling Unicode")
elif output_encoding and disable_unicode:
raise exceptions.UnsupportedError(
- "output_encoding must be set to "
- "None when disable_unicode is used.")
+ "output_encoding must be set to "
+ "None when disable_unicode is used.")
if default_filters is None:
if compat.py3k or self.disable_unicode:
self.default_filters = ['str']
@@ -311,17 +312,17 @@ class Template(object):
path = module_filename
elif module_directory is not None:
path = os.path.abspath(
- os.path.join(
- os.path.normpath(module_directory),
- u_norm + ".py"
- )
- )
+ os.path.join(
+ os.path.normpath(module_directory),
+ u_norm + ".py"
+ )
+ )
else:
path = None
module = self._compile_from_file(path, filename)
else:
raise exceptions.RuntimeException(
- "Template requires text or filename")
+ "Template requires text or filename")
self.module = module
self.filename = filename
@@ -337,7 +338,6 @@ class Template(object):
cache_type, cache_dir, cache_url
)
-
@util.memoized_property
def reserved_names(self):
if self.enable_loop:
@@ -346,8 +346,8 @@ class Template(object):
return codegen.RESERVED_NAMES.difference(['loop'])
def _setup_cache_args(self,
- cache_impl, cache_enabled, cache_args,
- cache_type, cache_dir, cache_url):
+ cache_impl, cache_enabled, cache_args,
+ cache_type, cache_dir, cache_url):
self.cache_impl = cache_impl
self.cache_enabled = cache_enabled
if cache_args:
@@ -368,24 +368,24 @@ class Template(object):
util.verify_directory(os.path.dirname(path))
filemtime = os.stat(filename)[stat.ST_MTIME]
if not os.path.exists(path) or \
- os.stat(path)[stat.ST_MTIME] < filemtime:
+ os.stat(path)[stat.ST_MTIME] < filemtime:
data = util.read_file(filename)
_compile_module_file(
- self,
- data,
- filename,
- path,
- self.module_writer)
+ self,
+ data,
+ filename,
+ path,
+ self.module_writer)
module = compat.load_module(self.module_id, path)
del sys.modules[self.module_id]
if module._magic_number != codegen.MAGIC_NUMBER:
data = util.read_file(filename)
_compile_module_file(
- self,
- data,
- filename,
- path,
- self.module_writer)
+ self,
+ data,
+ filename,
+ path,
+ self.module_writer)
module = compat.load_module(self.module_id, path)
del sys.modules[self.module_id]
ModuleInfo(module, path, self, filename, None, None)
@@ -394,9 +394,9 @@ class Template(object):
# in memory
data = util.read_file(filename)
code, module = _compile_text(
- self,
- data,
- filename)
+ self,
+ data,
+ filename)
self._source = None
self._code = code
ModuleInfo(module, None, self, filename, code, None)
@@ -421,9 +421,11 @@ class Template(object):
@property
def cache_dir(self):
return self.cache_args['dir']
+
@property
def cache_url(self):
return self.cache_args['url']
+
@property
def cache_type(self):
return self.cache_args['type']
@@ -446,10 +448,10 @@ class Template(object):
"""Render the output of this template as a unicode object."""
return runtime._render(self,
- self.callable_,
- args,
- data,
- as_unicode=True)
+ self.callable_,
+ args,
+ data,
+ as_unicode=True)
def render_context(self, context, *args, **kwargs):
"""Render this :class:`.Template` with the given context.
@@ -484,7 +486,9 @@ class Template(object):
def last_modified(self):
return self.module._modified_time
+
class ModuleTemplate(Template):
+
"""A Template which is constructed given an existing Python module.
e.g.::
@@ -502,25 +506,25 @@ class ModuleTemplate(Template):
"""
def __init__(self, module,
- module_filename=None,
- template=None,
- template_filename=None,
- module_source=None,
- template_source=None,
- output_encoding=None,
- encoding_errors='strict',
- disable_unicode=False,
- bytestring_passthrough=False,
- format_exceptions=False,
- error_handler=None,
- lookup=None,
- cache_args=None,
- cache_impl='beaker',
- cache_enabled=True,
- cache_type=None,
- cache_dir=None,
- cache_url=None,
- ):
+ module_filename=None,
+ template=None,
+ template_filename=None,
+ module_source=None,
+ template_source=None,
+ output_encoding=None,
+ encoding_errors='strict',
+ disable_unicode=False,
+ bytestring_passthrough=False,
+ format_exceptions=False,
+ error_handler=None,
+ lookup=None,
+ cache_args=None,
+ cache_impl='beaker',
+ cache_enabled=True,
+ cache_type=None,
+ cache_dir=None,
+ cache_url=None,
+ ):
self.module_id = re.sub(r'\W', "_", module._template_uri)
self.uri = module._template_uri
self.input_encoding = module._source_encoding
@@ -532,21 +536,21 @@ class ModuleTemplate(Template):
if compat.py3k and disable_unicode:
raise exceptions.UnsupportedError(
- "Mako for Python 3 does not "
- "support disabling Unicode")
+ "Mako for Python 3 does not "
+ "support disabling Unicode")
elif output_encoding and disable_unicode:
raise exceptions.UnsupportedError(
- "output_encoding must be set to "
- "None when disable_unicode is used.")
+ "output_encoding must be set to "
+ "None when disable_unicode is used.")
self.module = module
self.filename = template_filename
ModuleInfo(module,
- module_filename,
- self,
- template_filename,
- module_source,
- template_source)
+ module_filename,
+ self,
+ template_filename,
+ module_source,
+ template_source)
self.callable_ = self.module.render_body
self.format_exceptions = format_exceptions
@@ -557,7 +561,9 @@ class ModuleTemplate(Template):
cache_type, cache_dir, cache_url
)
+
class DefTemplate(Template):
+
"""A :class:`.Template` which represents a callable def in a parent
template."""
@@ -576,7 +582,9 @@ class DefTemplate(Template):
def get_def(self, name):
return self.parent.get_def(name)
+
class ModuleInfo(object):
+
"""Stores information about a module currently loaded into
memory, provides reverse lookups of template source, module
source code based on a module's identifier.
@@ -585,12 +593,12 @@ class ModuleInfo(object):
_modules = weakref.WeakValueDictionary()
def __init__(self,
- module,
- module_filename,
- template,
- template_filename,
- module_source,
- template_source):
+ module,
+ module_filename,
+ template,
+ template_filename,
+ module_source,
+ template_source):
self.module = module
self.module_filename = module_filename
self.template_filename = template_filename
@@ -603,11 +611,12 @@ class ModuleInfo(object):
@classmethod
def get_module_source_metadata(cls, module_source, full_line_map=False):
source_map = re.search(
- r"__M_BEGIN_METADATA(.+?)__M_END_METADATA",
- module_source, re.S).group(1)
+ r"__M_BEGIN_METADATA(.+?)__M_END_METADATA",
+ module_source, re.S).group(1)
source_map = compat.json.loads(source_map)
- source_map['line_map'] = dict((int(k), int(v))
- for k, v in source_map['line_map'].items())
+ source_map['line_map'] = dict(
+ (int(k), int(v))
+ for k, v in source_map['line_map'].items())
if full_line_map:
f_line_map = source_map['full_line_map'] = []
line_map = source_map['line_map']
@@ -632,7 +641,7 @@ class ModuleInfo(object):
if self.module._source_encoding and \
not isinstance(self.template_source, compat.text_type):
return self.template_source.decode(
- self.module._source_encoding)
+ self.module._source_encoding)
else:
return self.template_source
else:
@@ -642,32 +651,34 @@ class ModuleInfo(object):
else:
return data
+
def _compile(template, text, filename, generate_magic_comment):
lexer = template.lexer_cls(text,
- filename,
- disable_unicode=template.disable_unicode,
- input_encoding=template.input_encoding,
- preprocessor=template.preprocessor)
+ filename,
+ disable_unicode=template.disable_unicode,
+ input_encoding=template.input_encoding,
+ preprocessor=template.preprocessor)
node = lexer.parse()
source = codegen.compile(node,
- template.uri,
- filename,
- default_filters=template.default_filters,
- buffer_filters=template.buffer_filters,
- imports=template.imports,
- future_imports=template.future_imports,
- source_encoding=lexer.encoding,
- generate_magic_comment=generate_magic_comment,
- disable_unicode=template.disable_unicode,
- strict_undefined=template.strict_undefined,
- enable_loop=template.enable_loop,
- reserved_names=template.reserved_names)
+ template.uri,
+ filename,
+ default_filters=template.default_filters,
+ buffer_filters=template.buffer_filters,
+ imports=template.imports,
+ future_imports=template.future_imports,
+ source_encoding=lexer.encoding,
+ generate_magic_comment=generate_magic_comment,
+ disable_unicode=template.disable_unicode,
+ strict_undefined=template.strict_undefined,
+ enable_loop=template.enable_loop,
+ reserved_names=template.reserved_names)
return source, lexer
+
def _compile_text(template, text, filename):
identifier = template.module_id
source, lexer = _compile(template, text, filename,
- generate_magic_comment=template.disable_unicode)
+ generate_magic_comment=template.disable_unicode)
cid = identifier
if not compat.py3k and isinstance(cid, compat.text_type):
@@ -679,9 +690,10 @@ def _compile_text(template, text, filename):
exec(code, module.__dict__, module.__dict__)
return (source, module)
+
def _compile_module_file(template, text, filename, outputpath, module_writer):
source, lexer = _compile(template, text, filename,
- generate_magic_comment=True)
+ generate_magic_comment=True)
if isinstance(source, compat.text_type):
source = source.encode(lexer.encoding or 'ascii')
@@ -698,12 +710,13 @@ def _compile_module_file(template, text, filename, outputpath, module_writer):
os.close(dest)
shutil.move(name, outputpath)
+
def _get_module_info_from_callable(callable_):
if compat.py3k:
return _get_module_info(callable_.__globals__['__name__'])
else:
return _get_module_info(callable_.func_globals['__name__'])
+
def _get_module_info(filename):
return ModuleInfo._modules[filename]
-
diff --git a/mako/util.py b/mako/util.py
index cba2ab7..c7dad65 100644
--- a/mako/util.py
+++ b/mako/util.py
@@ -11,6 +11,7 @@ import os
from mako import compat
import operator
+
def update_wrapper(decorated, fn):
decorated.__wrapped__ = fn
decorated.__name__ = fn.__name__
@@ -18,6 +19,7 @@ def update_wrapper(decorated, fn):
class PluginLoader(object):
+
def __init__(self, group):
self.group = group
self.impls = {}
@@ -28,15 +30,15 @@ class PluginLoader(object):
else:
import pkg_resources
for impl in pkg_resources.iter_entry_points(
- self.group,
- name):
+ self.group,
+ name):
self.impls[name] = impl.load
return impl.load()
else:
from mako import exceptions
raise exceptions.RuntimeException(
- "Can't load plugin %s %s" %
- (self.group, name))
+ "Can't load plugin %s %s" %
+ (self.group, name))
def register(self, name, modulepath, objname):
def load():
@@ -46,6 +48,7 @@ class PluginLoader(object):
return getattr(mod, objname)
self.impls[name] = load
+
def verify_directory(dir):
"""create and/or verify a filesystem directory."""
@@ -59,6 +62,7 @@ def verify_directory(dir):
if tries > 5:
raise
+
def to_list(x, default=None):
if x is None:
return default
@@ -69,7 +73,9 @@ def to_list(x, default=None):
class memoized_property(object):
+
"""A read-only @property that is only evaluated once."""
+
def __init__(self, fget, doc=None):
self.fget = fget
self.__doc__ = doc or fget.__doc__
@@ -81,7 +87,9 @@ class memoized_property(object):
obj.__dict__[self.__name__] = result = self.fget(obj)
return result
+
class memoized_instancemethod(object):
+
"""Decorate a method memoize its return value.
Best applied to no-arg methods: memoization is not sensitive to
@@ -89,6 +97,7 @@ class memoized_instancemethod(object):
called with different arguments.
"""
+
def __init__(self, fget, doc=None):
self.fget = fget
self.__doc__ = doc or fget.__doc__
@@ -97,6 +106,7 @@ class memoized_instancemethod(object):
def __get__(self, obj, cls):
if obj is None:
return self
+
def oneshot(*args, **kw):
result = self.fget(obj, *args, **kw)
memo = lambda *a, **kw: result
@@ -108,8 +118,11 @@ class memoized_instancemethod(object):
oneshot.__doc__ = self.__doc__
return oneshot
+
class SetLikeDict(dict):
+
"""a dictionary that has some setlike methods on it"""
+
def union(self, other):
"""produce a 'union' of this dict and another (at the key level).
@@ -118,7 +131,9 @@ class SetLikeDict(dict):
x.update(other)
return x
+
class FastEncodingBuffer(object):
+
"""a very rudimentary buffer that is faster than StringIO,
but doesn't crash on unicode data like cStringIO."""
@@ -144,7 +159,9 @@ class FastEncodingBuffer(object):
else:
return self.delim.join(self.data)
+
class LRUCache(dict):
+
"""A dictionary-like object that stores a limited number of items,
discarding lesser used items periodically.
@@ -154,10 +171,12 @@ class LRUCache(dict):
"""
class _Item(object):
+
def __init__(self, key, value):
self.key = key
self.value = value
self.timestamp = compat.time_func()
+
def __repr__(self):
return repr(self.value)
@@ -206,6 +225,7 @@ _PYTHON_MAGIC_COMMENT_re = re.compile(
r'[ \t\f]* \# .* coding[=:][ \t]*([-\w.]+)',
re.VERBOSE)
+
def parse_encoding(fp):
"""Deduce the encoding of a Python source file (binary mode) from magic
comment.
@@ -238,12 +258,13 @@ def parse_encoding(fp):
else:
line2 = fp.readline()
m = _PYTHON_MAGIC_COMMENT_re.match(
- line2.decode('ascii', 'ignore'))
+ line2.decode('ascii', 'ignore'))
if has_bom:
if m:
- raise SyntaxError("python refuses to compile code with both a UTF8" \
- " byte-order-mark and a magic encoding comment")
+ raise SyntaxError(
+ "python refuses to compile code with both a UTF8"
+ " byte-order-mark and a magic encoding comment")
return 'utf_8'
elif m:
return m.group(1)
@@ -252,6 +273,7 @@ def parse_encoding(fp):
finally:
fp.seek(pos)
+
def sorted_dict_repr(d):
"""repr() a dictionary with the keys in order.
@@ -262,6 +284,7 @@ def sorted_dict_repr(d):
keys.sort()
return "{" + ", ".join(["%r: %r" % (k, d[k]) for k in keys]) + "}"
+
def restore__ast(_ast):
"""Attempt to restore the required classes to the _ast module if it
appears to be missing them
@@ -338,7 +361,6 @@ mako in baz not in mako""", '<unknown>', 'exec', _ast.PyCF_ONLY_AST)
_ast.NotIn = type(m.body[12].value.ops[1])
-
def read_file(path, mode='rb'):
fp = open(path, mode)
try:
@@ -347,6 +369,7 @@ def read_file(path, mode='rb'):
finally:
fp.close()
+
def read_python_file(path):
fp = open(path, "rb")
try:
@@ -357,4 +380,3 @@ def read_python_file(path):
return data
finally:
fp.close()
-
diff --git a/setup.cfg b/setup.cfg
index 6a03440..5bad26e 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -2,8 +2,6 @@
tag_build = dev
-[wheel]
-universal = 1
[pytest]
addopts= --tb native -v -r fxX
diff --git a/test/__init__.py b/test/__init__.py
index 666318f..22d1c83 100644
--- a/test/__init__.py
+++ b/test/__init__.py
@@ -8,9 +8,11 @@ import re
from mako.cache import CacheImpl, register_plugin
try:
+ # unitttest has a SkipTest also but pytest doesn't
+ # honor it unless nose is imported too...
from nose import SkipTest
except ImportError:
- from unittest import SkipTest
+ from _pytest.runner import Skipped as SkipTest
import contextlib
diff --git a/test/ext/test_babelplugin.py b/test/ext/test_babelplugin.py
index c66260e..3658c50 100644
--- a/test/ext/test_babelplugin.py
+++ b/test/ext/test_babelplugin.py
@@ -6,11 +6,10 @@ from mako import compat
try:
import babel.messages.extract as babel
-except:
- babel = None
-
-if babel is not None:
from mako.ext.babelplugin import extract
+
+except ImportError:
+ babel = None
def skip():
@@ -79,3 +78,16 @@ class ExtractMakoTestCase(TemplateTest):
(99, '_', 'No action at a distance.', []),
]
self.assertEqual(expected, messages)
+
+ @skip()
+ def test_extract_utf8(self):
+ mako_tmpl = open(os.path.join(template_base, 'gettext_utf8.mako'), 'rb')
+ message = next(extract(mako_tmpl, set(['_', None]), [], {'encoding': 'utf-8'}))
+ assert message == (1, '_', u'K\xf6ln', [])
+
+ @skip()
+ def test_extract_cp1251(self):
+ mako_tmpl = open(os.path.join(template_base, 'gettext_cp1251.mako'), 'rb')
+ message = next(extract(mako_tmpl, set(['_', None]), [], {'encoding': 'cp1251'}))
+ # "test" in Rusian. File encoding is cp1251 (aka "windows-1251")
+ assert message == (1, '_', u'\u0442\u0435\u0441\u0442', [])
diff --git a/test/templates/gettext.mako b/test/templates/gettext.mako
index ad07c5d..45b8262 100644
--- a/test/templates/gettext.mako
+++ b/test/templates/gettext.mako
@@ -97,3 +97,34 @@ ${_(u'bar')}
## TRANSLATOR: we still ignore comments too far from the string
<p>${_("No action at a distance.")}</p>
+
+## TRANSLATOR: nothing to extract from these blocks
+
+% if 1==1:
+<p>One is one!</p>
+% elif 1==2:
+<p>One is two!</p>
+% else:
+<p>How much is one?</p>
+% endif
+
+% for i in range(10):
+<p>${i} squared is ${i*i}</p>
+% else:
+<p>Done with squares!</p>
+% endfor
+
+% while random.randint(1,6) != 6:
+<p>Not 6!</p>
+% endwhile
+
+## TRANSLATOR: for now, try/except blocks are ignored
+
+% try:
+<% 1/0 %>
+% except:
+<p>Failed!</p>
+% endtry
+
+## TRANSLATOR: this should not cause a parse error
+${ 1 }
diff --git a/test/templates/gettext_cp1251.mako b/test/templates/gettext_cp1251.mako
new file mode 100644
index 0000000..9341d93
--- /dev/null
+++ b/test/templates/gettext_cp1251.mako
@@ -0,0 +1 @@
+${_("òåñò")}
diff --git a/test/templates/gettext_utf8.mako b/test/templates/gettext_utf8.mako
new file mode 100644
index 0000000..761f946
--- /dev/null
+++ b/test/templates/gettext_utf8.mako
@@ -0,0 +1 @@
+${_("Köln")}
diff --git a/test/test_template.py b/test/test_template.py
index c5873dc..f551230 100644
--- a/test/test_template.py
+++ b/test/test_template.py
@@ -757,9 +757,22 @@ class UndefinedVarsTest(TemplateTest):
['t is: T', 'a,b,c']
)
+class StopRenderingTest(TemplateTest):
+ def test_return_in_template(self):
+ t = Template("""
+ Line one
+ <% return STOP_RENDERING %>
+ Line Three
+ """, strict_undefined=True)
+
+ eq_(
+ result_lines(t.render()),
+ ['Line one']
+ )
+
class ReservedNameTest(TemplateTest):
def test_names_on_context(self):
- for name in ('context', 'loop', 'UNDEFINED'):
+ for name in ('context', 'loop', 'UNDEFINED', 'STOP_RENDERING'):
assert_raises_message(
exceptions.NameConflictError,
r"Reserved words passed to render\(\): %s" % name,
@@ -767,7 +780,7 @@ class ReservedNameTest(TemplateTest):
)
def test_names_in_template(self):
- for name in ('context', 'loop', 'UNDEFINED'):
+ for name in ('context', 'loop', 'UNDEFINED', 'STOP_RENDERING'):
assert_raises_message(
exceptions.NameConflictError,
r"Reserved words declared in template: %s" % name,
@@ -1251,13 +1264,13 @@ Text
eq_(
ModuleInfo.get_module_source_metadata(t.code, full_line_map=True),
{
- 'full_line_map': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
- 0, 0, 0, 0, 0, 0, 1, 4, 5, 5, 5, 7, 8,
- 8, 8, 8, 8, 8, 8],
+ 'full_line_map': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 4, 5, 5, 5, 7,
+ 8, 8, 8, 8, 8, 8, 8],
'source_encoding': 'ascii',
'filename': None,
- 'line_map': {34: 28, 14: 0, 21: 1, 22: 4, 23: 5, 24: 5,
- 25: 5, 26: 7, 27: 8, 28: 8},
+ 'line_map': {35: 29, 15: 0, 22: 1, 23: 4, 24: 5, 25: 5,
+ 26: 5, 27: 7, 28: 8, 29: 8},
'uri': '/some/template'
}
@@ -1280,17 +1293,19 @@ Text
eq_(
ModuleInfo.get_module_source_metadata(t.code, full_line_map=True),
{
- 'full_line_map': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 5, 5, 5, 7, 7, 7,
- 7, 7, 10, 10, 10, 10, 10, 10, 8, 8, 8, 8, 8,
- 8, 8, 8, 8, 8, 8, 8],
+ 'full_line_map': [
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 5, 5, 5, 7, 7,
+ 7, 7, 7, 10, 10, 10, 10, 10, 10, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8],
'source_encoding': 'ascii',
'filename': None,
- 'line_map': {33: 10, 39: 8, 45: 8, 14: 0, 51: 45, 23: 1,
- 24: 4, 25: 5, 26: 5, 27: 5, 28: 7},
+ 'line_map': {34: 10, 40: 8, 46: 8, 15: 0, 52: 46,
+ 24: 1, 25: 4, 26: 5, 27: 5, 28: 5, 29: 7},
'uri': '/some/template'}
)
+
class PreprocessTest(TemplateTest):
def test_old_comments(self):
t = Template("""
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..8d4a71d
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,16 @@
+# content of: tox.ini , put in same dir as setup.py
+[tox]
+envlist = py{26,27,33,34}
+
+[testenv]
+deps=pytest
+ py{26,27}: mock
+ beaker
+ markupsafe
+ pygments
+ babel
+ dogpile.cache
+ lingua
+
+commands=py.test
+