aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGES3
-rw-r--r--mako/codegen.py258
-rw-r--r--test/test_template.py12
3 files changed, 207 insertions, 66 deletions
diff --git a/CHANGES b/CHANGES
index 91bfa09..b490f90 100644
--- a/CHANGES
+++ b/CHANGES
@@ -29,6 +29,9 @@
- A percent sign can be emitted as the first
non-whitespace character on a line by escaping
it as in "%%". [ticket:112]
+
+- Template accepts empty control structure, i.e.
+ % if: %endif, etc. [ticket:94]
0.2.6
diff --git a/mako/codegen.py b/mako/codegen.py
index ded9396..71a608f 100644
--- a/mako/codegen.py
+++ b/mako/codegen.py
@@ -211,8 +211,6 @@ class _GenerateRenderMethod(object):
return main_identifiers.topleveldefs.values()
-##### continue [ticket:98] below ####
-
def write_render_callable(self, node, name, args, buffered, filtered, cached):
"""write a top-level render callable.
@@ -235,8 +233,15 @@ class _GenerateRenderMethod(object):
if not self.in_def 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):
- self.printer.writeline("__M_locals = __M_dict_builtin(%s)" % ','.join(["%s=%s" % (x, x) for x in self.identifiers.argument_declared]))
+ if not self.in_def and (
+ 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
+ self.identifiers.argument_declared
+ ]))
self.write_variable_declares(self.identifiers, toplevel=True)
@@ -247,21 +252,26 @@ class _GenerateRenderMethod(object):
self.printer.writeline(None)
self.printer.write("\n\n")
if cached:
- self.write_cache_decorator(node, name, args, buffered, self.identifiers, toplevel=True)
+ self.write_cache_decorator(
+ node, name,
+ args, buffered,
+ self.identifiers, toplevel=True)
def write_module_code(self, module_code):
- """write module-level template code, i.e. that which is enclosed in <%! %> tags
- in the template."""
+ """write module-level template code, i.e. that which
+ is enclosed in <%! %> tags in the template."""
for n in module_code:
self.write_source_comment(n)
self.printer.write_indented_block(n.text)
def write_inherit(self, node):
"""write the module-level inheritance-determination callable."""
+
self.printer.writelines(
"def _mako_inherit(template, context):",
"_mako_generate_namespaces(context)",
- "return runtime._inherit_from(context, %s, _template_uri)" % (node.parsed_attributes['file']),
+ "return runtime._inherit_from(context, %s, _template_uri)" %
+ (node.parsed_attributes['file']),
None
)
@@ -297,9 +307,18 @@ class _GenerateRenderMethod(object):
callable_name = "make_namespace()"
else:
callable_name = "None"
- self.printer.writeline("ns = runtime.Namespace(%s, context._clean_inheritance_tokens(), templateuri=%s, callables=%s, calling_uri=_template_uri, module=%s)" % (repr(node.name), node.parsed_attributes.get('file', 'None'), callable_name, node.parsed_attributes.get('module', 'None')))
+ self.printer.writeline(
+ "ns = runtime.Namespace(%r, context._clean_inheritance_tokens(),"
+ " templateuri=%s, callables=%s, calling_uri=_template_uri, module=%s)" %
+ (
+ node.name,
+ node.parsed_attributes.get('file', 'None'),
+ callable_name,
+ node.parsed_attributes.get('module', 'None'))
+ )
if eval(node.attributes.get('inheritable', "False")):
self.printer.writeline("context['self'].%s = ns" % (node.name))
+
self.printer.writeline("context.namespaces[(__name__, %s)] = ns" % repr(node.name))
self.printer.write("\n")
if not len(namespaces):
@@ -309,31 +328,42 @@ class _GenerateRenderMethod(object):
def write_variable_declares(self, identifiers, toplevel=False, limit=None):
"""write variable declarations at the top of a function.
- the variable declarations are in the form of callable definitions for defs and/or
- name lookup within the function's context argument. the names declared are based on the
- names that are referenced in the function body, which don't otherwise have any explicit
- assignment operation. names that are assigned within the body are assumed to be
- locally-scoped variables and are not separately declared.
+ the variable declarations are in the form of callable
+ definitions for defs and/or name lookup within the
+ function's context argument. the names declared are based
+ on the names that are referenced in the function body,
+ which don't otherwise have any explicit assignment
+ operation. names that are assigned within the body are
+ assumed to be locally-scoped variables and are not
+ separately declared.
- for def callable definitions, if the def is a top-level callable then a
- 'stub' callable is generated which wraps the current Context into a closure. if the def
- is not top-level, it is fully rendered as a local closure."""
+ for def callable definitions, if the def is a top-level
+ callable then a 'stub' callable is generated which wraps
+ the current Context into a closure. if the def is not
+ top-level, it is fully rendered as a local closure.
+
+ """
# collection of all defs available to us in this scope
comp_idents = dict([(c.name, c) for c in identifiers.defs])
to_write = set()
- # write "context.get()" for all variables we are going to need that arent in the namespace yet
+ # write "context.get()" for all variables we are going to
+ # need that arent in the namespace yet
to_write = to_write.union(identifiers.undeclared)
- # write closure functions for closures that we define right here
- to_write = to_write.union(set([c.name for c in identifiers.closuredefs.values()]))
+ # write closure functions for closures that we define
+ # right here
+ to_write = to_write.union([c.name for c in identifiers.closuredefs.values()])
- # remove identifiers that are declared in the argument signature of the callable
+ # remove identifiers that are declared in the argument
+ # signature of the callable
to_write = to_write.difference(identifiers.argument_declared)
- # remove identifiers that we are going to assign to. in this way we mimic Python's behavior,
- # i.e. assignment to a variable within a block means that variable is now a "locally declared" var,
+ # remove identifiers that we are going to assign to.
+ # in this way we mimic Python's behavior,
+ # i.e. assignment to a variable within a block
+ # means that variable is now a "locally declared" var,
# which cannot be referenced beforehand.
to_write = to_write.difference(identifiers.locally_declared)
@@ -347,7 +377,12 @@ class _GenerateRenderMethod(object):
self.compiler.has_imports = True
for ident, ns in self.compiler.namespaces.iteritems():
if ns.attributes.has_key('import'):
- self.printer.writeline("_mako_get_namespace(context, %s)._populate(_import_ns, %s)" % (repr(ident), repr(re.split(r'\s*,\s*', ns.attributes['import']))))
+ self.printer.writeline(
+ "_mako_get_namespace(context, %r)._populate(_import_ns, %r)" %
+ (
+ ident,
+ re.split(r'\s*,\s*', ns.attributes['import'])
+ ))
for ident in to_write:
if ident in comp_idents:
@@ -357,12 +392,17 @@ class _GenerateRenderMethod(object):
else:
self.write_inline_def(comp, identifiers, nested=True)
elif ident in self.compiler.namespaces:
- self.printer.writeline("%s = _mako_get_namespace(context, %s)" % (ident, repr(ident)))
+ self.printer.writeline(
+ "%s = _mako_get_namespace(context, %r)" %
+ (ident, ident)
+ )
else:
if getattr(self.compiler, 'has_ns_imports', False):
- self.printer.writeline("%s = _import_ns.get(%s, context.get(%s, UNDEFINED))" % (ident, repr(ident), repr(ident)))
+ self.printer.writeline(
+ "%s = _import_ns.get(%r, context.get(%r, UNDEFINED))" %
+ (ident, ident, ident))
else:
- self.printer.writeline("%s = context.get(%s, UNDEFINED)" % (ident, repr(ident)))
+ self.printer.writeline("%s = context.get(%r, UNDEFINED)" % (ident, ident))
self.printer.writeline("__M_writer = context.writer()")
@@ -377,7 +417,10 @@ class _GenerateRenderMethod(object):
funcname = node.function_decl.funcname
namedecls = node.function_decl.get_argument_expressions()
nameargs = node.function_decl.get_argument_expressions(include_defaults=False)
- if not self.in_def and (len(self.identifiers.locally_assigned) > 0 or len(self.identifiers.argument_declared) > 0):
+
+ if not self.in_def and (
+ 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')
@@ -387,6 +430,7 @@ class _GenerateRenderMethod(object):
def write_inline_def(self, node, identifiers, nested):
"""write a locally-available def callable inside an enclosing def."""
+
namedecls = node.function_decl.get_argument_expressions()
decorator = node.decorator
@@ -417,14 +461,17 @@ class _GenerateRenderMethod(object):
self.write_def_finish(node, buffered, filtered, cached)
self.printer.writeline(None)
if cached:
- self.write_cache_decorator(node, node.name, namedecls, False, identifiers, inline=True, toplevel=False)
+ self.write_cache_decorator(node, node.name,
+ namedecls, False, identifiers,
+ inline=True, toplevel=False)
def write_def_finish(self, node, buffered, filtered, cached, callstack=True):
"""write the end section of a rendering function, either outermost or inline.
this takes into account if the rendering function was filtered, buffered, etc.
- and closes the corresponding try: block if any, and writes code to retrieve captured content,
- apply filters, send proper return value."""
+ and closes the corresponding try: block if any, and writes code to retrieve
+ captured content, apply filters, send proper return value."""
+
if not buffered and not cached and not filtered:
self.printer.writeline("return ''")
if callstack:
@@ -467,12 +514,18 @@ class _GenerateRenderMethod(object):
"return ''"
)
- def write_cache_decorator(self, node_or_pagetag, name, args, buffered, identifiers, inline=False, toplevel=False):
- """write a post-function decorator to replace a rendering callable with a cached version of itself."""
+ def write_cache_decorator(self, node_or_pagetag, name,
+ args, buffered, identifiers,
+ inline=False, toplevel=False):
+ """write a post-function decorator to replace a rendering
+ callable with a cached version of itself."""
+
self.printer.writeline("__M_%s = %s" % (name, name))
cachekey = node_or_pagetag.parsed_attributes.get('cache_key', repr(name))
cacheargs = {}
- for arg in (('cache_type', 'type'), ('cache_dir', 'data_dir'), ('cache_timeout', 'expiretime'), ('cache_url', 'url')):
+ for arg in (
+ ('cache_type', 'type'), ('cache_dir', 'data_dir'),
+ ('cache_timeout', 'expiretime'), ('cache_url', 'url')):
val = node_or_pagetag.parsed_attributes.get(arg[0], None)
if val is not None:
if arg[1] == 'expiretime':
@@ -491,24 +544,41 @@ class _GenerateRenderMethod(object):
self.printer.writeline("def %s(%s):" % (name, ','.join(args)))
# form "arg1, arg2, arg3=arg3, arg4=arg4", etc.
- pass_args = [ '=' in a and "%s=%s" % ((a.split('=')[0],)*2) or a for a in args]
-
- self.write_variable_declares(identifiers, toplevel=toplevel, limit=node_or_pagetag.undeclared_identifiers())
+ pass_args = [
+ '=' in a and "%s=%s" % ((a.split('=')[0],)*2) or a
+ for a in args
+ ]
+
+ self.write_variable_declares(
+ identifiers,
+ toplevel=toplevel,
+ limit=node_or_pagetag.undeclared_identifiers()
+ )
if buffered:
- s = "context.get('local').get_cached(%s, defname=%r, %screatefunc=lambda:__M_%s(%s))" % (cachekey, name, ''.join(["%s=%s, " % (k,v) for k, v in cacheargs.iteritems()]), name, ','.join(pass_args))
+ s = "context.get('local')."\
+ "get_cached(%s, defname=%r, %screatefunc=lambda:__M_%s(%s))" % \
+ (cachekey, name,
+ ''.join(["%s=%s, " % (k,v) for k, v in cacheargs.iteritems()]),
+ name, ','.join(pass_args))
# 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').get_cached(%s, defname=%r, %screatefunc=lambda:__M_%s(%s)))" % (cachekey, name, ''.join(["%s=%s, " % (k,v) for k, v in cacheargs.iteritems()]), name, ','.join(pass_args)),
+ "__M_writer(context.get('local')."
+ "get_cached(%s, defname=%r, %screatefunc=lambda:__M_%s(%s)))" %
+ (cachekey, name,
+ ''.join(["%s=%s, " % (k,v) for k, v in cacheargs.iteritems()]),
+ name, ','.join(pass_args)),
"return ''",
None
)
def create_filter_callable(self, args, target, is_expression):
- """write a filter-applying expression based on the filters present in the given
- filter names, adjusting for the global 'default' filter aliases as needed."""
+ """write a filter-applying expression based on the filters
+ present in the given filter names, adjusting for the global
+ 'default' filter aliases as needed."""
+
def locate_encode(name):
if re.match(r'decode\..+', name):
return "filters." + name
@@ -539,7 +609,13 @@ class _GenerateRenderMethod(object):
def visitExpression(self, node):
self.write_source_comment(node)
- if len(node.escapes) or (self.compiler.pagetag is not None and len(self.compiler.pagetag.filter_args.args)) or len(self.compiler.default_filters):
+ if len(node.escapes) or \
+ (
+ self.compiler.pagetag is not None and
+ len(self.compiler.pagetag.filter_args.args)
+ ) or \
+ len(self.compiler.default_filters):
+
s = self.create_filter_callable(node.escapes_code.args, "%s" % node.text, True)
self.printer.writeline("__M_writer(%s)" % s)
else:
@@ -547,13 +623,17 @@ class _GenerateRenderMethod(object):
def visitControlLine(self, node):
if node.isend:
+ if not node.get_children():
+ self.printer.writeline("pass")
self.printer.writeline(None)
else:
self.write_source_comment(node)
self.printer.writeline(node.text)
+
def visitText(self, node):
self.write_source_comment(node)
self.printer.writeline("__M_writer(%s)" % repr(node.content))
+
def visitTextTag(self, node):
filtered = len(node.filter_args.args) > 0
if filtered:
@@ -567,7 +647,11 @@ class _GenerateRenderMethod(object):
self.printer.writelines(
"finally:",
"__M_buf, __M_writer = context._pop_buffer_and_writer()",
- "__M_writer(%s)" % self.create_filter_callable(node.filter_args.args, "__M_buf.getvalue()", False),
+ "__M_writer(%s)" %
+ self.create_filter_callable(
+ node.filter_args.args,
+ "__M_buf.getvalue()",
+ False),
None
)
@@ -577,18 +661,28 @@ class _GenerateRenderMethod(object):
self.printer.write_indented_block(node.text)
if not self.in_def and len(self.identifiers.locally_assigned) > 0:
- # if we are the "template" def, fudge locally declared/modified variables into the "__M_locals" dictionary,
- # which is used for def calls within the same template, to simulate "enclosing scope"
+ # if we are the "template" def, fudge locally
+ # declared/modified variables into the "__M_locals" dictionary,
+ # which is used for def calls within the same template,
+ # to simulate "enclosing scope"
self.printer.writeline('__M_locals_builtin_stored = __M_locals_builtin()')
- self.printer.writeline('__M_locals.update(__M_dict_builtin([(__M_key, __M_locals_builtin_stored[__M_key]) for __M_key in [%s] if __M_key in __M_locals_builtin_stored]))' % ','.join([repr(x) for x in node.declared_identifiers()]))
+ self.printer.writeline(
+ '__M_locals.update(__M_dict_builtin([(__M_key,'
+ ' __M_locals_builtin_stored[__M_key]) for '
+ '__M_key in [%s] if __M_key in __M_locals_builtin_stored]))' %
+ ','.join([repr(x) for x in node.declared_identifiers()]))
def visitIncludeTag(self, node):
self.write_source_comment(node)
args = node.attributes.get('args')
if args:
- self.printer.writeline("runtime._include_file(context, %s, _template_uri, %s)" % (node.parsed_attributes['file'], args))
+ self.printer.writeline(
+ "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']))
+ self.printer.writeline(
+ "runtime._include_file(context, %s, _template_uri)" %
+ (node.parsed_attributes['file']))
def visitNamespaceTag(self, node):
pass
@@ -607,8 +701,10 @@ class _GenerateRenderMethod(object):
export = ['body']
callable_identifiers = self.identifiers.branch(node, nested=True)
body_identifiers = callable_identifiers.branch(node, nested=False)
- # we want the 'caller' passed to ccall to be used for the body() function,
- # but for other non-body() <%def>s within <%call> we want the current caller off the call stack (if any)
+ # we want the 'caller' passed to ccall to be used
+ # for the body() function, but for other non-body()
+ # <%def>s within <%call> we want the current caller
+ # off the call stack (if any)
body_identifiers.add_declared('caller')
self.identifier_stack.append(body_identifiers)
@@ -628,7 +724,9 @@ class _GenerateRenderMethod(object):
bodyargs = node.body_decl.get_argument_expressions()
self.printer.writeline("def body(%s):" % ','.join(bodyargs))
- # TODO: figure out best way to specify buffering/nonbuffering (at call time would be better)
+
+ # TODO: figure out best way to specify
+ # buffering/nonbuffering (at call time would be better)
buffered = False
if buffered:
self.printer.writelines(
@@ -653,7 +751,8 @@ class _GenerateRenderMethod(object):
# get local reference to current caller, if any
"caller = context.caller_stack._get_caller()",
# push on caller for nested call
- "context.caller_stack.nextcaller = runtime.Namespace('caller', context, callables=ccall(caller))",
+ "context.caller_stack.nextcaller = "
+ "runtime.Namespace('caller', context, callables=ccall(caller))",
"try:")
self.write_source_comment(node)
self.printer.writelines(
@@ -665,13 +764,19 @@ class _GenerateRenderMethod(object):
class _Identifiers(object):
"""tracks the status of identifier names as template code is rendered."""
+
def __init__(self, node=None, parent=None, nested=False):
if parent is not None:
- # 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)
+ # 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)
- # if these identifiers correspond to a "nested" scope, it means whatever the
- # parent identifiers had as undeclared will have been declared by that parent,
+ # if these identifiers correspond to a "nested"
+ # scope, it means whatever the parent identifiers
+ # had as undeclared will have been declared by that parent,
# and therefore we have them in our scope.
if nested:
self.declared = self.declared.union(parent.undeclared)
@@ -682,18 +787,22 @@ class _Identifiers(object):
self.declared = set()
self.topleveldefs = util.SetLikeDict()
- # things within this level that are referenced before they are declared (e.g. assigned to)
+ # things within this level that are referenced before they
+ # are declared (e.g. assigned to)
self.undeclared = set()
- # things that are declared locally. some of these things could be in the "undeclared"
- # list as well if they are referenced before declared
+ # things that are declared locally. some of these things
+ # could be in the "undeclared" list as well if they are
+ # referenced before declared
self.locally_declared = set()
- # assignments made in explicit python blocks. these will be propigated to
+ # assignments made in explicit python blocks.
+ # these will be propagated to
# the context of local def calls.
self.locally_assigned = set()
- # things that are declared in the argument signature of the def callable
+ # things that are declared in the argument
+ # signature of the def callable
self.argument_declared = set()
# closure defs that are defined in this level
@@ -705,16 +814,30 @@ class _Identifiers(object):
node.accept_visitor(self)
def branch(self, node, **kwargs):
- """create a new Identifiers for a new Node, with this Identifiers as the parent."""
+ """create a new Identifiers for a new Node, with
+ this Identifiers as the parent."""
+
return _Identifiers(node, self, **kwargs)
- defs = property(lambda self:set(self.topleveldefs.union(self.closuredefs).values()))
+ @property
+ def defs(self):
+ return set(self.topleveldefs.union(self.closuredefs).values())
def __repr__(self):
- return "Identifiers(declared=%s, locally_declared=%s, undeclared=%s, topleveldefs=%s, closuredefs=%s, argumenetdeclared=%s)" % (repr(list(self.declared)), repr(list(self.locally_declared)), repr(list(self.undeclared)), repr([c.name for c in self.topleveldefs.values()]), repr([c.name for c in self.closuredefs.values()]), repr(self.argument_declared))
+ 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)
def check_declared(self, node):
- """update the state of this Identifiers with the undeclared and declared identifiers of the given node."""
+ """update the state of this Identifiers with the undeclared
+ and declared identifiers of the given node."""
+
for ident in node.undeclared_identifiers():
if ident != 'context' and ident not in self.declared.union(self.locally_declared):
self.undeclared.add(ident)
@@ -728,12 +851,15 @@ class _Identifiers(object):
def visitExpression(self, node):
self.check_declared(node)
+
def visitControlLine(self, node):
self.check_declared(node)
+
def visitCode(self, node):
if not node.ismodule:
self.check_declared(node)
self.locally_assigned = self.locally_assigned.union(node.declared_identifiers())
+
def visitDefTag(self, node):
if node.is_root():
self.topleveldefs[node.name] = node
@@ -748,8 +874,10 @@ class _Identifiers(object):
self.argument_declared.add(ident)
for n in node.nodes:
n.accept_visitor(self)
+
def visitIncludeTag(self, node):
self.check_declared(node)
+
def visitPageTag(self, node):
for ident in node.declared_identifiers():
self.argument_declared.add(ident)
diff --git a/test/test_template.py b/test/test_template.py
index 970565a..204ee3b 100644
--- a/test/test_template.py
+++ b/test/test_template.py
@@ -509,7 +509,17 @@ class ControlTest(TemplateTest):
"no x does not have test",
"yes x has test"
]
-
+
+ def test_blank_control(self):
+ self._do_memory_test(
+ """
+ % if True:
+ % endif
+ """,
+ "",
+ filters=lambda s:s.strip()
+ )
+
def test_multiline_control(self):
t = Template("""
% for x in \\