diff --git a/doc/build/templates/base.html b/doc/build/templates/base.html index 109a111557e28d9bf5097856adc9bf968fb97e0b..a26fd5b15828f4a2706a3d8e9b223036631a07ae 100644 --- a/doc/build/templates/base.html +++ b/doc/build/templates/base.html @@ -23,11 +23,11 @@ version = toc.version last_updated = toc.last_updated - extension = context.get('extension', 'html') - paged = context.get('paged', True) - requestattr['extension'] = extension - requestattr['paged'] = paged - requestattr['toc'] = toc + kwargs = context.kwargs + kwargs.setdefault('extension', 'html') + extension = kwargs['extension'] + kwargs.setdefault('paged', True) + kwargs.setdefault('toc', toc) %> <div id="topanchor"><a name="top"></a> </div> @@ -38,7 +38,7 @@ <div class="versionheader">Version: ${version} Last Updated: ${time.strftime('%x %X', time.localtime(last_updated))}</div> -${next.body()} +${next.body(**kwargs)} diff --git a/doc/build/templates/content_layout.html b/doc/build/templates/content_layout.html index d9be1298e2ded62a51a517d5b26296921a0d6739..587eaaaeda78cf877e2fbccf9fdc717f9e558286 100644 --- a/doc/build/templates/content_layout.html +++ b/doc/build/templates/content_layout.html @@ -4,11 +4,11 @@ <%namespace file="nav.html" import="topnav"/> <% - current = requestattr['toc'].get_by_file(self.template.module.filename) + current = toc.get_by_file(self.template.module.filename) %> <A name="<% current.path %>"></a> ${topnav(item=current)} -${next.body()} +${next.body(**context.kwargs)} diff --git a/doc/build/templates/formatting.html b/doc/build/templates/formatting.html index 13bfdb18951d94262255fbbfcc5aaed79c0710bf..6e9c16ca517475ee71766c30c1317ea18802a08b 100644 --- a/doc/build/templates/formatting.html +++ b/doc/build/templates/formatting.html @@ -16,7 +16,6 @@ <%def name="section(path, description=None)"> # Main section formatting element. <% - toc = requestattr['toc'] item = toc.get_by_path(path) subsection = item.depth > 1 %> diff --git a/doc/build/templates/nav.html b/doc/build/templates/nav.html index a4b6044a9cdec8ebca3649dbd315d6aeaf55e56d..c3ad05ead0cacac5ffe6101734d1e61713e0a244 100644 --- a/doc/build/templates/nav.html +++ b/doc/build/templates/nav.html @@ -3,12 +3,11 @@ <%namespace name="tocns" file="toc.html"/> <%def name="itemlink(item, anchor=True)" filter="trim"> - <a href="${ item.get_link(anchor=anchor, usefilename=requestattr['paged']) }">${ item.description }</a> + <a href="${ item.get_link(anchor=anchor, usefilename=paged) }">${ item.description }</a> </%def> <%def name="toclink(path, description=None)" filter="trim"> <% - toc = requestattr['toc'] item = toc.get_by_path(path) if description is None: if item: @@ -16,12 +15,12 @@ else: description = path if item: - anchor = not requestattr['paged'] or item.depth > 1 + anchor = not paged or item.depth > 1 else: anchor = False %> % if item: - <a href="${ item.get_link(extension=requestattr['extension'], anchor=anchor, usefilename=requestattr['paged']) }">${ description }</a> + <a href="${ item.get_link(extension=extension, anchor=anchor, usefilename=paged) }">${ description }</a> % else: <b>${ description }</b> % endif @@ -39,7 +38,7 @@ ${pagenav(item)} </div> - <h3><a href="index.${ requestattr['extension'] }">Table of Contents</a></h3> + <h3><a href="index.${ extension }">Table of Contents</a></h3> <br/> ${itemlink(item=item, anchor=True)} @@ -49,11 +48,11 @@ <%def name="pagenav(item)"> % if item.previous is not None: - Previous: ${itemlink(item=item.previous, anchor=not requestattr['paged'])} + Previous: ${itemlink(item=item.previous, anchor=not paged)} % endif % if item.next is not None: ${item.previous is not None and "|" or ""} - Next: ${itemlink(item=item.next, anchor=not requestattr['paged'])} + Next: ${itemlink(item=item.next, anchor=not paged)} % endif </%def> diff --git a/doc/build/templates/toc.html b/doc/build/templates/toc.html index 1fcde011a7c23065f3a07cf369a94f36aa3310f3..87f256ecb84c3e37c001148e893c32927c3c70ef 100644 --- a/doc/build/templates/toc.html +++ b/doc/build/templates/toc.html @@ -6,7 +6,7 @@ <a name="full_index"></a> <h3>Table of Contents</h3> - ${printtoc(root=requestattr['toc'],current=None,full=True,children=True,anchor_toplevel=False)} + ${printtoc(root=context.get('toc'),current=None,full=True,children=True,anchor_toplevel=False)} </div> </%def> @@ -15,7 +15,7 @@ <%def name="printtoc(root,current=None,full=False,children=True,anchor_toplevel=False)"> <ul> % for item in root.children: - <li><A style="${item is current and "font-weight:bold;" or "" }" href="${item.get_link(extension=requestattr['extension'],anchor=anchor_toplevel, usefilename=paged) }">${item.description}</a></li> + <li><A style="${item is current and "font-weight:bold;" or "" }" href="${item.get_link(extension=extension,anchor=anchor_toplevel, usefilename=paged) }">${item.description}</a></li> % if children: ${printtoc(item, current=current, full=full,children=True,anchor_toplevel=True)} diff --git a/lib/mako/ast.py b/lib/mako/ast.py index c2e039479295a0867d07f14401964fcfa2cb91a1..e44e5a8ff3b35a353b8a27390fe1edd8cd9aa2ca 100644 --- a/lib/mako/ast.py +++ b/lib/mako/ast.py @@ -145,7 +145,7 @@ class walker(visitor.ASTVisitor): class FunctionDecl(object): """function declaration""" - def __init__(self, code, lineno, pos, filename): + def __init__(self, code, lineno, pos, filename, allow_kwargs=True): self.code = code expr = parse(code, "exec", lineno, pos, filename) class ParseFunc(object): @@ -160,6 +160,9 @@ class FunctionDecl(object): visitor.walk(expr, f) if not hasattr(self, 'funcname'): raise exceptions.CompileException("Code '%s' is not a function declaration" % code, lineno, pos, filename) + if not allow_kwargs and self.kwargs: + raise exceptions.CompileException("'**%s' keyword argument not allowed here" % self.argnames[-1], lineno, pos, filename) + def get_argument_expressions(self, include_defaults=True): """return the argument declarations of this FunctionDecl as a printable list.""" namedecls = [] @@ -186,8 +189,8 @@ class FunctionDecl(object): class FunctionArgs(FunctionDecl): """the argument portion of a function declaration""" - def __init__(self, code, lineno, pos, filename): - super(FunctionArgs, self).__init__("def ANON(%s):pass" % code, lineno, pos, filename) + def __init__(self, code, lineno, pos, filename, **kwargs): + super(FunctionArgs, self).__init__("def ANON(%s):pass" % code, lineno, pos, filename, **kwargs) class ExpressionGenerator(object): diff --git a/lib/mako/codegen.py b/lib/mako/codegen.py index e744ee7d2d971d370110393d1a1bb377edaec9c3..0c538cde9b04a1d39b512cfdeadc00739dbb61db 100644 --- a/lib/mako/codegen.py +++ b/lib/mako/codegen.py @@ -47,16 +47,20 @@ class _GenerateRenderMethod(object): else: (pagetag, defs) = self.write_toplevel() name = "render_body" - args = None + if pagetag is not None: + args = pagetag.body_decl.get_argument_expressions() + if not pagetag.body_decl.kwargs: + args += ['**_extra_pageargs'] + cached = eval(pagetag.attributes.get('cached', 'False')) + else: + args = ['**_extra_pageargs'] + cached = False buffered = filtered = False self.compiler.pagetag = pagetag - cached = pagetag is not None and eval(pagetag.attributes.get('cached', 'False')) if args is None: args = ['context'] else: args = [a for a in ['context'] + args] - if not self.in_def: - args.append('**kwargs') self.write_render_callable(pagetag or node, name, args, buffered, filtered, cached) @@ -137,10 +141,8 @@ class _GenerateRenderMethod(object): self.identifier_stack.append(self.compiler.identifiers.branch(self.node)) - if not self.in_def: - self.printer.writeline("context = context.locals_(kwargs)") - if not self.in_def and len(self.identifiers.locally_assigned) > 0: - self.printer.writeline("__locals = {}") + if not self.in_def and (len(self.identifiers.locally_assigned) > 0 or len(self.identifiers.argument_declared)>0): + self.printer.writeline("__locals = dict(%s)" % ','.join("%s=%s" % (x, x) for x in self.identifiers.argument_declared)) self.write_variable_declares(self.identifiers, toplevel=True) @@ -244,7 +246,7 @@ class _GenerateRenderMethod(object): # (this is used for the caching decorator) if limit is not None: to_write = to_write.intersection(limit) - + if toplevel and getattr(self.compiler, 'has_ns_imports', False): self.printer.writeline("_import_ns = {}") self.compiler.has_imports = True @@ -278,7 +280,7 @@ 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: + if not self.in_def and (len(self.identifiers.locally_assigned) > 0 or len(self.identifiers.argument_declared) > 0): nameargs.insert(0, 'context.locals_(__locals)') else: nameargs.insert(0, 'context') @@ -568,7 +570,10 @@ class _Identifiers(object): def visitIncludeTag(self, node): self.check_declared(node) def visitPageTag(self, node): - self.check_declared(node) + for ident in node.declared_identifiers(): + self.argument_declared.add(ident) + self.check_declared(node) + def visitCallTag(self, node): if node is self.node: for ident in node.undeclared_identifiers(): diff --git a/lib/mako/exceptions.py b/lib/mako/exceptions.py index 15e77d33e04ac545d01de3cfdcce84d68212a634..064ce3f7f53ef378db2f774093acabe8fd7a760e 100644 --- a/lib/mako/exceptions.py +++ b/lib/mako/exceptions.py @@ -75,7 +75,7 @@ class RichTraceback(object): self.reverse_records.reverse() def _get_reformatted_records(self, records): for rec in records: - if rec[6]: + if rec[6] is not None: yield (rec[4], rec[5], rec[2], rec[6]) else: yield tuple(rec[0:4]) @@ -97,7 +97,7 @@ class RichTraceback(object): rawrecords = traceback.extract_tb(trcback) new_trcback = [] for filename, lineno, function, line in rawrecords: - #print "TB", filename, lineno, function, line + print "TB", filename, lineno, function, line try: (line_map, template_lines) = mods[filename] except KeyError: @@ -129,6 +129,7 @@ class RichTraceback(object): else: template_line = None new_trcback.append((filename, lineno, function, line, template_filename, template_ln, template_line, template_source)) + print "AND THE TB IS", new_trcback[-1] if not self.source: for l in range(len(new_trcback)-1, 0, -1): if new_trcback[l][5]: diff --git a/lib/mako/parsetree.py b/lib/mako/parsetree.py index 99cb9dc392cd8bb3952a556639bacc72d8e6149f..fd4741d0c2d01f11b7a3a117873a9a992866e151 100644 --- a/lib/mako/parsetree.py +++ b/lib/mako/parsetree.py @@ -274,5 +274,9 @@ class InheritTag(Tag): class PageTag(Tag): __keyword__ = 'page' def __init__(self, keyword, attributes, **kwargs): - super(PageTag, self).__init__(keyword, attributes, ('cached', 'cache_key', 'cache_timeout', 'cache_type', 'cache_dir'), (), (), **kwargs) + super(PageTag, self).__init__(keyword, attributes, ('cached', 'cache_key', 'cache_timeout', 'cache_type', 'cache_dir', 'args'), (), (), **kwargs) + self.body_decl = ast.FunctionArgs(attributes.get('args', ''), self.lineno, self.pos, self.filename) + def declared_identifiers(self): + return self.body_decl.argnames + \ No newline at end of file diff --git a/lib/mako/runtime.py b/lib/mako/runtime.py index 254ddee2054bd464471fce68640caafef501662e..22851df8b9dd54ed877da6d9391151a609688b2b 100644 --- a/lib/mako/runtime.py +++ b/lib/mako/runtime.py @@ -14,6 +14,7 @@ class Context(object): def __init__(self, buffer, **data): self._buffer_stack = [buffer] self._data = data + self._kwargs = data.copy() self._with_template = None self.namespaces = {} @@ -24,12 +25,11 @@ class Context(object): self.caller_stack = [Undefined] data['caller'] = _StackFacade(self.caller_stack) lookup = property(lambda self:self._with_template.lookup) + kwargs = property(lambda self:self._kwargs.copy()) def keys(self): return self._data.keys() def __getitem__(self, key): return self._data[key] - def _put(self, key, value): - self._data[key] = value def push_buffer(self): """push a capturing buffer onto this Context.""" self._buffer_stack.append(util.FastEncodingBuffer()) @@ -45,6 +45,7 @@ class Context(object): c = Context.__new__(Context) c._buffer_stack = self._buffer_stack c._data = self._data.copy() + c._kwargs = self._kwargs c._with_template = self._with_template c.namespaces = self.namespaces c.caller_stack = self.caller_stack @@ -154,9 +155,6 @@ class Namespace(object): yield (k, getattr(self.module, k)) def __getattr__(self, key): - return self._get_callable(key) - - def _get_callable(self, key): if self.callables is not None: try: return self.callables[key] diff --git a/test/def.py b/test/def.py index 4214fc7035e6399a2bd793298ca4753e974e44a5..b9aad1bc686538109b94c6637f1c12c9c3ff7841 100644 --- a/test/def.py +++ b/test/def.py @@ -326,6 +326,7 @@ class ScopeTest(unittest.TestCase): l.put_string("main", """ <%inherit file="base"/> + <%page args="x"/> this is main. x is ${x} ${a()} @@ -336,7 +337,7 @@ class ScopeTest(unittest.TestCase): """) # test via inheritance - #print l.get_template("main").code + print l.get_template("main").code assert result_lines(l.get_template("main").render()) == [ "this is main. x is 12", "this is a, x is 12" diff --git a/test/inheritance.py b/test/inheritance.py index e3a59d411cbcbed3dcf09d3c493234fe1867abcc..096c7f651c32f48f9301bf1a139a4e50ed327288 100644 --- a/test/inheritance.py +++ b/test/inheritance.py @@ -181,6 +181,73 @@ ${next.body()} 'c is: secondary_c. a is layout_a b is base_b d is secondary_d.' ] + def test_pageargs(self): + collection = lookup.TemplateLookup() + collection.put_string("base", """ + this is the base. + + <%def name="foo()"> + ${next.body(**context.kwargs)} + </%def> + + ${foo()} + """) + collection.put_string("index", """ + <%inherit file="base"/> + <%page args="x, y, z=7"/> + print ${x}, ${y}, ${z} + """) + assert result_lines(collection.get_template('index').render(x=5,y=10)) == [ + "this is the base.", + "print 5, 10, 7" + ] + def test_pageargs_2(self): + collection = lookup.TemplateLookup() + collection.put_string("base", """ + this is the base. + + ${next.body(**context.kwargs)} + + <%def name="foo(**kwargs)"> + ${next.body(**kwargs)} + </%def> + + <%def name="bar(**otherargs)"> + ${next.body(z=16, **context.kwargs)} + </%def> + + ${foo(x=12, y=15, z=8)} + ${bar(x=19, y=17)} + """) + collection.put_string("index", """ + <%inherit file="base"/> + <%page args="x, y, z=7"/> + pageargs: ${x}, ${y}, ${z} + """) + assert result_lines(collection.get_template('index').render(x=5,y=10)) == [ + "this is the base.", + "pageargs: 5, 10, 7", + "pageargs: 12, 15, 8", + "pageargs: 5, 10, 16" + ] + + def test_pageargs_err(self): + collection = lookup.TemplateLookup() + collection.put_string("base", """ + this is the base. + ${next.body()} + """) + collection.put_string("index", """ + <%inherit file="base"/> + <%page args="x, y, z=7"/> + print ${x}, ${y}, ${z} + """) + try: + print collection.get_template('index').render(x=5,y=10) + assert False + except TypeError: + assert True + def test_dynamic(self): collection = lookup.TemplateLookup() collection.put_string("base", """ diff --git a/test/lexer.py b/test/lexer.py index e51947ba1276552d1c2cfb2f3612d46999e8cc1a..04c157ddbbc897539bbdd8fb90f367dacdb8082d 100644 --- a/test/lexer.py +++ b/test/lexer.py @@ -132,6 +132,14 @@ class LexerTest(unittest.TestCase): #print nodes assert repr(nodes) == r"""TemplateNode({}, [Text('\n ', (1, 1)), CallTag('call', {'expr': "foo>bar and 'lala' or 'hoho'"}, (2, 13), []), Text('\n ', (2, 57)), CallTag('call', {'expr': 'foo<bar and hoho>lala and "x" + "y"'}, (3, 13), []), Text('\n ', (3, 64))])""" + def test_pagetag(self): + template = """ + <%page cached="True", args="a, b"/> + + some template + """ + nodes = Lexer(template).parse() + assert repr(nodes) == r"""TemplateNode({}, [Text('\n ', (1, 1)), PageTag('page', {'cached': 'True', 'args': 'a, b'}, (2, 13), []), Text('\n \n some template\n ', (2, 48))])""" def test_nesting(self): template = """ diff --git a/test/template.py b/test/template.py index 543f35acfd90e3636be66dddc55f05fa79d6b0ff..00de06c5029c07d619a2806b72a8b2879a9b01ce 100644 --- a/test/template.py +++ b/test/template.py @@ -28,6 +28,40 @@ class EncodingTest(unittest.TestCase): val = "# -*- encoding: utf-8 -*-\n" + val.encode('utf-8') template = Template(val) assert template.render_unicode() == u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petit voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""" + + def test_encoding(self): + val = u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petit voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""" + template = Template(val, output_encoding='utf-8') + assert template.render() == val.encode('utf-8') + +class PageArgsTest(unittest.TestCase): + def test_basic(self): + template = Template(""" + <%page args="x, y, z=7"/> + + this is page, ${x}, ${y}, ${z} +""") + + assert flatten_result(template.render(x=5, y=10)) == "this is page, 5, 10, 7" + assert flatten_result(template.render(x=5, y=10, z=32)) == "this is page, 5, 10, 32" + try: + template.render(y=10) + assert False + except TypeError, e: + assert True + + def test_with_context(self): + template = Template(""" + <%page args="x, y, z=7"/> + + this is page, ${x}, ${y}, ${z}, ${w} +""") + + assert flatten_result(template.render(x=5, y=10, w=17)) == "this is page, 5, 10, 7, 17" + + + + class ControlTest(unittest.TestCase): def test_control(self):