def parse(self, parser): lineno = next(parser.stream).lineno files = [] output = nodes.Const(None) filters = nodes.Const(None) dbg = nodes.Const(None) depends = nodes.Const(None) # Parse the arguments first = True while parser.stream.current.type != 'block_end': if not first: parser.stream.expect('comma') first = False # Lookahead to see if this is an assignment (an option) if parser.stream.current.test('name') and parser.stream.look().test('assign'): name = next(parser.stream).value parser.stream.skip() value = parser.parse_expression() if name == 'filters': filters = value elif name == 'filter': filters = value warnings.warn('The "filter" option of the {%% assets %%} ' 'template tag has been renamed to ' '"filters" for consistency reasons ' '(line %s).' % lineno, ImminentDeprecationWarning) elif name == 'output': output = value elif name == 'debug': dbg = value elif name == 'depends': depends = value else: parser.fail('Invalid keyword argument: %s' % name) # Otherwise assume a source file is given, which may be any # expression, except note that strings are handled separately above. else: expression = parser.parse_expression() if isinstance(expression, (nodes.List, nodes.Tuple)): files.extend(expression.iter_child_nodes()) else: files.append(expression) # Parse the contents of this tag body = parser.parse_statements(['name:endassets'], drop_needle=True) # We want to make some values available to the body of our tag. # Specifically, the file url(s) (ASSET_URL), and any extra dict set in # the bundle (EXTRA). # # A short interlope: I would have preferred to make the values of the # extra dict available directly. Unfortunately, the way Jinja2 does # things makes this problematic. I'll explain. # # Jinja2 generates Python code from it's AST which it then executes. # So the way extensions implement making custom variables available to # a block of code is by generating a ``CallBlock``, which essentially # wraps our child nodes in a Python function. The arguments of this # function are the values that are available to our tag contents. # # But we need to generate this ``CallBlock`` now, during parsing, and # right now we don't know the actual ``Bundle.extra`` values yet. We # only resolve the bundle during rendering! # # This would easily be solved if Jinja2 where to allow extensions to # scope it's context, which is a dict of values that templates can # access, just like in Django (you might see on occasion # ``context.resolve('foo')`` calls in Jinja2's generated code). # However, it seems the context is essentially only for the initial # set of data passed to render(). There are some statements by Armin # that this might change at some point, but I've run into this problem # before, and I'm not holding my breath. # # I **really** did try to get around this, including crazy things like # inserting custom Python code by patching the tag child nodes:: # # rv = object.__new__(nodes.InternalName) # # l_EXTRA is the argument we defined for the CallBlock/Macro # # Letting Jinja define l_kwargs is also possible # nodes.Node.__init__(rv, '; context.vars.update(l_EXTRA)', # lineno=lineno) # # Scope required to ensure our code on top # body = [rv, nodes.Scope(body)] # # This custom code would run at the top of the function in which the # CallBlock node would wrap the code generated from our tag's child # nodes. Note that it actually does works, but doesn't clear the values # at the end of the scope). # # If it is possible to do this, it certainly isn't reasonable/ # # There is of course another option altogether: Simple resolve the tag # definition to a bundle right here and now, thus get access to the # extra dict, make all values arguments to the CallBlock (Limited to # 255 arguments to a Python function!). And while that would work fine # in 99% of cases, it wouldn't be correct. The compiled template could # be cached and used with different bundles/environments, and this # would require the bundle to resolve at parse time, and hardcode it's # extra values. # # Interlope end. # # Summary: We have to be satisfied with a single EXTRA variable. args = [nodes.Name('ASSET_URL', 'param'), nodes.Name('ASSET_SRI', 'param'), nodes.Name('EXTRA', 'param')] # Return a ``CallBlock``, which means Jinja2 will call a Python method # of ours when the tag needs to be rendered. That method can then # render the template body. call = self.call_method( # Note: Changing the args here requires updating ``Jinja2Loader`` '_render_assets', args=[filters, output, dbg, depends, nodes.List(files)]) call_block = nodes.CallBlock(call, args, [], body) call_block.set_lineno(lineno) return call_block
def parse(self, parser): """ Parse the referred template and the namespace. """ token = parser.stream.next() lineno = token.lineno parser.stream.expect('name:to') template = parser.parse_expression() parser.stream.expect('name:as') namespace = parser.stream.next().value includeNode = nodes.Include(lineno=lineno) includeNode.with_context = True includeNode.ignore_missing = False includeNode.template = template temp = parser.free_identifier(lineno) return [ nodes.Assign(nodes.Name(temp.name, 'store'), nodes.Name(MARKINGS, 'load')).set_lineno(lineno), nodes.Assign(nodes.Name(MARKINGS, 'store'), nodes.Const({})).set_lineno(lineno), nodes.Assign(nodes.Name(namespace, 'store'), nodes.Const({})).set_lineno(lineno), nodes.CallBlock( self.call_method('_push_resource', args=[ nodes.Name(namespace, 'load'), nodes.Name('site', 'load'), nodes.Name('resource', 'load'), template ]), [], [], []).set_lineno(lineno), nodes.Assign( nodes.Name('resource', 'store'), nodes.Getitem(nodes.Name(namespace, 'load'), nodes.Const('resource'), 'load')).set_lineno(lineno), nodes.CallBlock( self.call_method('_assign_reference', args=[ nodes.Name(MARKINGS, 'load'), nodes.Name(namespace, 'load') ]), [], [], [includeNode]).set_lineno(lineno), nodes.Assign( nodes.Name('resource', 'store'), nodes.Getitem(nodes.Name(namespace, 'load'), nodes.Const('parent_resource'), 'load')).set_lineno(lineno), nodes.Assign(nodes.Name(MARKINGS, 'store'), nodes.Name(temp.name, 'load')).set_lineno(lineno), ]
def parse(self, parser): """Parse a translatable tag.""" lineno = next(parser.stream).lineno num_called_num = False # find all the variables referenced. Additionally a variable can be # defined in the body of the trans block too, but this is checked at # a later state. plural_expr = None plural_expr_assignment = None variables = {} trimmed = None while parser.stream.current.type != 'block_end': if variables: parser.stream.expect('comma') # skip colon for python compatibility if parser.stream.skip_if('colon'): break name = parser.stream.expect('name') if name.value in variables: parser.fail('translatable variable %r defined twice.' % name.value, name.lineno, exc=TemplateAssertionError) # expressions if parser.stream.current.type == 'assign': next(parser.stream) variables[name.value] = var = parser.parse_expression() elif trimmed is None and name.value in ('trimmed', 'notrimmed'): trimmed = name.value == 'trimmed' continue else: variables[name.value] = var = nodes.Name(name.value, 'load') if plural_expr is None: if isinstance(var, nodes.Call): plural_expr = nodes.Name('_trans', 'load') variables[name.value] = plural_expr plural_expr_assignment = nodes.Assign( nodes.Name('_trans', 'store'), var) else: plural_expr = var num_called_num = name.value == 'num' parser.stream.expect('block_end') plural = None have_plural = False referenced = set() # now parse until endtrans or pluralize singular_names, singular = self._parse_block(parser, True) if singular_names: referenced.update(singular_names) if plural_expr is None: plural_expr = nodes.Name(singular_names[0], 'load') num_called_num = singular_names[0] == 'num' # if we have a pluralize block, we parse that too if parser.stream.current.test('name:pluralize'): have_plural = True next(parser.stream) if parser.stream.current.type != 'block_end': name = parser.stream.expect('name') if name.value not in variables: parser.fail('unknown variable %r for pluralization' % name.value, name.lineno, exc=TemplateAssertionError) plural_expr = variables[name.value] num_called_num = name.value == 'num' parser.stream.expect('block_end') plural_names, plural = self._parse_block(parser, False) next(parser.stream) referenced.update(plural_names) else: next(parser.stream) # register free names as simple name expressions for var in referenced: if var not in variables: variables[var] = nodes.Name(var, 'load') if not have_plural: plural_expr = None elif plural_expr is None: parser.fail('pluralize without variables', lineno) if trimmed is None: trimmed = self.environment.policies['ext.i18n.trimmed'] if trimmed: singular = self._trim_whitespace(singular) if plural: plural = self._trim_whitespace(plural) node = self._make_node(singular, plural, variables, plural_expr, bool(referenced), num_called_num and have_plural) node.set_lineno(lineno) if plural_expr_assignment is not None: return [plural_expr_assignment, node] else: return node
def parse(self, parser): """Parse a translatable tag.""" lineno = next(parser.stream).lineno num_called_num = False plural_expr = None variables = {} while parser.stream.current.type != 'block_end': if variables: parser.stream.expect('comma') if parser.stream.skip_if('colon'): break name = parser.stream.expect('name') if name.value in variables: parser.fail('translatable variable %r defined twice.' % name.value, name.lineno, exc=TemplateAssertionError) if parser.stream.current.type == 'assign': next(parser.stream) variables[name.value] = var = parser.parse_expression() else: variables[name.value] = var = nodes.Name(name.value, 'load') if plural_expr is None: plural_expr = var num_called_num = name.value == 'num' parser.stream.expect('block_end') plural = plural_names = None have_plural = False referenced = set() singular_names, singular = self._parse_block(parser, True) if singular_names: referenced.update(singular_names) if plural_expr is None: plural_expr = nodes.Name(singular_names[0], 'load') num_called_num = singular_names[0] == 'num' if parser.stream.current.test('name:pluralize'): have_plural = True next(parser.stream) if parser.stream.current.type != 'block_end': name = parser.stream.expect('name') if name.value not in variables: parser.fail('unknown variable %r for pluralization' % name.value, name.lineno, exc=TemplateAssertionError) plural_expr = variables[name.value] num_called_num = name.value == 'num' parser.stream.expect('block_end') plural_names, plural = self._parse_block(parser, False) next(parser.stream) referenced.update(plural_names) else: next(parser.stream) for var in referenced: if var not in variables: variables[var] = nodes.Name(var, 'load') if not have_plural: plural_expr = None elif plural_expr is None: parser.fail('pluralize without variables', lineno) node = self._make_node(singular, plural, variables, plural_expr, bool(referenced), num_called_num and have_plural) node.set_lineno(lineno) return node
def _set_var(var_name, var_value, lineno): target_var = nodes.Name(var_name, 'store', lineno=lineno) return nodes.Assign(target_var, var_value, lineno=lineno)
def parse(self, parser): lineno = parser.stream.expect('name:csrf_token').lineno call = self.call_method( '_render', [nodes.Name('csrf_token', 'load', lineno=lineno)], lineno=lineno) return nodes.Output([nodes.MarkSafe(call)])
def parse(self, parser): lineno = six.next(parser.stream).lineno return nodes.Output([ self.call_method('_render', [nodes.Name('csrf_token', 'load')]), ]).set_lineno(lineno)
def parse(self, parser): stream = parser.stream tag = six.next(stream) # get view name if stream.current.test('string'): # Need to work around Jinja2 syntax here. Jinja by default acts # like Python and concats subsequent strings. In this case # though, we want {% url "app.views.post" "1" %} to be treated # as view + argument, while still supporting # {% url "app.views.post"|filter %}. Essentially, what we do is # rather than let ``parser.parse_primary()`` deal with a "string" # token, we do so ourselves, and let parse_expression() handle all # other cases. if stream.look().test('string'): token = six.next(stream) viewname = nodes.Const(token.value, lineno=token.lineno) else: viewname = parser.parse_expression() else: # parse valid tokens and manually build a string from them bits = [] name_allowed = True while True: if stream.current.test_any('dot', 'sub', 'colon'): bits.append(six.next(stream)) name_allowed = True elif stream.current.test('name') and name_allowed: bits.append(six.next(stream)) name_allowed = False else: break viewname = nodes.Const("".join([b.value for b in bits])) if not bits: raise TemplateSyntaxError( "'%s' requires path to view" % tag.value, tag.lineno) # get arguments args = [] kwargs = [] while not stream.current.test_any('block_end', 'name:as'): if args or kwargs: stream.expect('comma') if stream.current.test('name') and stream.look().test('assign'): key = nodes.Const(six.next(stream).value) stream.skip() value = parser.parse_expression() kwargs.append(nodes.Pair(key, value, lineno=key.lineno)) else: args.append(parser.parse_expression()) def make_call_node(*kw): return self.call_method('_reverse', args=[ viewname, nodes.List(args), nodes.Dict(kwargs), nodes.Name('_current_app', 'load'), ], kwargs=kw) # if an as-clause is specified, write the result to context... if stream.next_if('name:as'): var = nodes.Name(stream.expect('name').value, 'store') call_node = make_call_node( nodes.Keyword('fail', nodes.Const(False))) return nodes.Assign(var, call_node) # ...otherwise print it out. else: return nodes.Output([make_call_node()]).set_lineno(tag.lineno)
def parse(self, parser): """Parse a translatable tag.""" lineno = next(parser.stream).lineno # find all the variables referenced. Additionally a variable can be # defined in the body of the trans block too, but this is checked at # a later state. plural_expr = None variables = {} while parser.stream.current.type != 'block_end': if variables: parser.stream.expect('comma') # skip colon for python compatibility if parser.stream.skip_if('colon'): break name = parser.stream.expect('name') if name.value in variables: parser.fail('translatable variable %r defined twice.' % name.value, name.lineno, exc=TemplateAssertionError) # expressions if parser.stream.current.type == 'assign': next(parser.stream) variables[name.value] = var = parser.parse_expression() else: variables[name.value] = var = nodes.Name(name.value, 'load') if plural_expr is None: plural_expr = var parser.stream.expect('block_end') plural = plural_names = None have_plural = False referenced = set() # now parse until endtrans or pluralize singular_names, singular = self._parse_block(parser, True) if singular_names: referenced.update(singular_names) if plural_expr is None: plural_expr = nodes.Name(singular_names[0], 'load') # if we have a pluralize block, we parse that too if parser.stream.current.test('name:pluralize'): have_plural = True next(parser.stream) if parser.stream.current.type != 'block_end': name = parser.stream.expect('name') if name.value not in variables: parser.fail('unknown variable %r for pluralization' % name.value, name.lineno, exc=TemplateAssertionError) plural_expr = variables[name.value] parser.stream.expect('block_end') plural_names, plural = self._parse_block(parser, False) next(parser.stream) referenced.update(plural_names) else: next(parser.stream) # register free names as simple name expressions for var in referenced: if var not in variables: variables[var] = nodes.Name(var, 'load') # no variables referenced? no need to escape if not referenced: singular = singular.replace('%%', '%') if plural: plural = plural.replace('%%', '%') if not have_plural: plural_expr = None elif plural_expr is None: parser.fail('pluralize without variables', lineno) if variables: variables = nodes.Dict([nodes.Pair(nodes.Const(x, lineno=lineno), y) for x, y in variables.items()]) else: variables = None node = self._make_node(singular, plural, variables, plural_expr) node.set_lineno(lineno) return node
def test_complex(): title_block = nodes.Block( 'title', [nodes.Output([nodes.TemplateData(u'Page Title')])], False) render_title_macro = nodes.Macro( 'render_title', [nodes.Name('title', 'param')], [], [ nodes.Output([ nodes.TemplateData(u'\n <div class="title">\n <h1>'), nodes.Name('title', 'load'), nodes.TemplateData(u'</h1>\n <p>'), nodes.Name('subtitle', 'load'), nodes.TemplateData(u'</p>\n ') ]), nodes.Assign(nodes.Name('subtitle', 'store'), nodes.Const('something else')), nodes.Output([ nodes.TemplateData(u'\n <p>'), nodes.Name('subtitle', 'load'), nodes.TemplateData(u'</p>\n </div>\n'), nodes.If(nodes.Name('something', 'load'), [ nodes.Assign( nodes.Name('title_upper', 'store'), nodes.Filter(nodes.Name('title', 'load'), 'upper', [], [], None, None)), nodes.Output([ nodes.Name('title_upper', 'load'), nodes.Call(nodes.Name('render_title', 'load'), [nodes.Const('Aha')], [], None, None) ]) ], [], []) ]) ]) for_loop = nodes.For(nodes.Name('item', 'store'), nodes.Name( 'seq', 'load'), [ nodes.Output([ nodes.TemplateData(u'\n <li>'), nodes.Name('item', 'load'), nodes.TemplateData(u'</li>\n <span>') ]), nodes.Include(nodes.Const('helper.html'), True, False), nodes.Output([nodes.TemplateData(u'</span>\n ')]) ], [], None, False) body_block = nodes.Block('body', [ nodes.Output([ nodes.TemplateData(u'\n '), nodes.Call(nodes.Name('render_title', 'load'), [nodes.Name('item', 'load')], [], None, None), nodes.TemplateData(u'\n <ul>\n ') ]), for_loop, nodes.Output([nodes.TemplateData(u'\n </ul>\n')]) ], False) tmpl = nodes.Template([ nodes.Extends(nodes.Const('layout.html')), title_block, render_title_macro, body_block, ]) tmpl_sym = symbols_for_node(tmpl) assert tmpl_sym.refs == { 'render_title': 'l_0_render_title', } assert tmpl_sym.loads == { 'l_0_render_title': ('undefined', None), } assert tmpl_sym.stores == set(['render_title']) assert tmpl_sym.dump_stores() == { 'render_title': 'l_0_render_title', } macro_sym = symbols_for_node(render_title_macro, tmpl_sym) assert macro_sym.refs == { 'subtitle': 'l_1_subtitle', 'something': 'l_1_something', 'title': 'l_1_title', 'title_upper': 'l_1_title_upper', } assert macro_sym.loads == { 'l_1_subtitle': ('resolve', 'subtitle'), 'l_1_something': ('resolve', 'something'), 'l_1_title': ('param', None), 'l_1_title_upper': ('resolve', 'title_upper'), } assert macro_sym.stores == set(['title', 'title_upper', 'subtitle']) assert macro_sym.find_ref('render_title') == 'l_0_render_title' assert macro_sym.dump_stores() == { 'title': 'l_1_title', 'title_upper': 'l_1_title_upper', 'subtitle': 'l_1_subtitle', 'render_title': 'l_0_render_title', } body_sym = symbols_for_node(body_block) assert body_sym.refs == { 'item': 'l_0_item', 'seq': 'l_0_seq', 'render_title': 'l_0_render_title', } assert body_sym.loads == { 'l_0_item': ('resolve', 'item'), 'l_0_seq': ('resolve', 'seq'), 'l_0_render_title': ('resolve', 'render_title'), } assert body_sym.stores == set([]) for_sym = symbols_for_node(for_loop, body_sym) assert for_sym.refs == { 'item': 'l_1_item', } assert for_sym.loads == { 'l_1_item': ('param', None), } assert for_sym.stores == set(['item']) assert for_sym.dump_stores() == { 'item': 'l_1_item', }
def _parse_query(self, parser, lineno): name = parser.parse_assign_target(with_tuple=False).name body = parser.parse_statements(['name:endquery'], drop_needle=True) # name, params, defaults, body return nodes.Macro(name, [nodes.Name('__blocks__', 'param')], [], body).set_lineno(lineno)
>>> env.compile_expression('var', undefined_to_none=False)() Undefined **new in Jinja 2.1** """ parser = Parser(self, source, state='variable') try: expr = parser.parse_expression() if not parser.stream.eos: raise TemplateSyntaxError('chunk after expression', parser.stream.current.lineno, None, None) except TemplateSyntaxError, e: e.source = source raise e body = [nodes.Assign(nodes.Name('result', 'store'), expr, lineno=1)] template = self.from_string(nodes.Template(body, lineno=1)) return TemplateExpression(template, undefined_to_none) def join_path(self, template, parent): """Join a template with the parent. By default all the lookups are relative to the loader root so this method returns the `template` parameter unchanged, but if the paths should be relative to the parent template, this function can be used to calculate the real template name. Subclasses may override this method and implement template path joining here. """ return template
def tablerow( token: "Token", parser: "Parser" ) -> Union[nodes.Node, List[nodes.Node]]: """The tablerow tag {% tablerow ... %} ... {% endtablerow %} Args: token: The token matches tag name parser: The parser Returns: The parsed node """ target = parser.parse_assign_target(extra_end_rules=("name:in", )) parser.stream.expect("name:in") iter_ = parser.parse_tuple( with_condexpr=False, extra_end_rules=("name:cols", "name:limit", "name:offset"), ) cols = parse_tag_args(parser.stream, "cols", token.lineno) limit = parse_tag_args(parser.stream, "limit", token.lineno) offset = parse_tag_args(parser.stream, "offset", token.lineno) if limit and offset: limit = nodes.Add(offset, limit) if limit or offset: iter_ = nodes.Getitem(iter_, nodes.Slice(offset, limit, None), "load") if cols: slice_start = nodes.Mul(nodes.Name("_tablerow_i", "load"), cols) inner_iter = nodes.Getitem( iter_, nodes.Slice( slice_start, nodes.Add(slice_start, cols), None, ), "load", ) else: inner_iter: nodes.Getitem = iter_ inner_body = [ nodes.Output( [ nodes.Const('<td class="col'), nodes.Getattr(nodes.Name("loop", "load"), "index", "load"), nodes.Const('">'), ] ), *parser.parse_statements(("name:endtablerow",), drop_needle=True), nodes.Output([nodes.Const("</td>")]), ] tr_begin = nodes.Output( [ nodes.Const('<tr class="row'), nodes.CondExpr( nodes.Name("loop", "load"), nodes.Getattr(nodes.Name("loop", "load"), "index", "load"), nodes.Const(1), ), nodes.Const('">'), ] ) tr_end = nodes.Output([nodes.Const("</tr>")]) inner_loop = nodes.For( target, inner_iter, inner_body, [], None, False, lineno=token.lineno ) if not cols: return [tr_begin, inner_loop, tr_end] # (iter_ | length) / cols iter_length = nodes.Div( nodes.Filter(iter_, "length", [], [], None, None), cols, ) # float # int(iter_length) iter_length_int = nodes.Filter(iter_length, "int", [], [], None, None) # implement ceil, as jinja's ceil is implemented as round(..., "ceil") # while liquid has a ceil filter # iter_length_int if iter_length == iter_length_int # else iter_length_int + 1 iter_length = nodes.CondExpr( nodes.Compare(iter_length, [nodes.Operand("eq", iter_length_int)]), iter_length_int, nodes.Add(iter_length_int, nodes.Const(1)), ) return nodes.For( nodes.Name("_tablerow_i", "store"), nodes.Call(nodes.Name("range", "load"), [iter_length], [], None, None), [tr_begin, inner_loop, tr_end], [], None, False, lineno=token.lineno, )
class Builtin(object): def __init__(self, name): self.name = name def __repr__(self): return self.name DJEDI_NODE_STORAGE_NODE = ( nodes.If( # if not isinstance(DJEDI_NODE_STORAGE, dict): nodes.Not( nodes.Call(nodes.Const(Builtin('isinstance')), [ nodes.Name(DJEDI_NODE_STORAGE, 'load'), nodes.Const(Builtin('dict')), ], [], None, None)), # DJEDI_NODE_STORAGE = {} [ nodes.Assign(nodes.Name(DJEDI_NODE_STORAGE, 'store'), nodes.Dict( [])), ], # else: pass [])) class NodeExtension(Extension): tags = set([DJEDI_INIT_TAG, DJEDI_TAG, DJEDI_BLOCK_TAG]) def create_node_id(self, uri, default):
def output(self, parser, block_call, target, tag_name, lineno): if target: target_node = nodes.Name(target, 'store', lineno=lineno) return nodes.Assign(target_node, block_call, lineno=lineno) call = nodes.MarkSafe(block_call, lineno=lineno) return nodes.Output([call], lineno=lineno)
def test_complex(): title_block = nodes.Block( "title", [nodes.Output([nodes.TemplateData("Page Title")])], False) render_title_macro = nodes.Macro( "render_title", [nodes.Name("title", "param")], [], [ nodes.Output([ nodes.TemplateData('\n <div class="title">\n <h1>'), nodes.Name("title", "load"), nodes.TemplateData("</h1>\n <p>"), nodes.Name("subtitle", "load"), nodes.TemplateData("</p>\n "), ]), nodes.Assign(nodes.Name("subtitle", "store"), nodes.Const("something else")), nodes.Output([ nodes.TemplateData("\n <p>"), nodes.Name("subtitle", "load"), nodes.TemplateData("</p>\n </div>\n"), nodes.If( nodes.Name("something", "load"), [ nodes.Assign( nodes.Name("title_upper", "store"), nodes.Filter( nodes.Name("title", "load"), "upper", [], [], None, None, ), ), nodes.Output([ nodes.Name("title_upper", "load"), nodes.Call( nodes.Name("render_title", "load"), [nodes.Const("Aha")], [], None, None, ), ]), ], [], [], ), ]), ], ) for_loop = nodes.For( nodes.Name("item", "store"), nodes.Name("seq", "load"), [ nodes.Output([ nodes.TemplateData("\n <li>"), nodes.Name("item", "load"), nodes.TemplateData("</li>\n <span>"), ]), nodes.Include(nodes.Const("helper.html"), True, False), nodes.Output([nodes.TemplateData("</span>\n ")]), ], [], None, False, ) body_block = nodes.Block( "body", [ nodes.Output([ nodes.TemplateData("\n "), nodes.Call( nodes.Name("render_title", "load"), [nodes.Name("item", "load")], [], None, None, ), nodes.TemplateData("\n <ul>\n "), ]), for_loop, nodes.Output([nodes.TemplateData("\n </ul>\n")]), ], False, ) tmpl = nodes.Template([ nodes.Extends(nodes.Const("layout.html")), title_block, render_title_macro, body_block, ]) tmpl_sym = symbols_for_node(tmpl) assert tmpl_sym.refs == { "render_title": "l_0_render_title", } assert tmpl_sym.loads == { "l_0_render_title": ("undefined", None), } assert tmpl_sym.stores == {"render_title"} assert tmpl_sym.dump_stores() == { "render_title": "l_0_render_title", } macro_sym = symbols_for_node(render_title_macro, tmpl_sym) assert macro_sym.refs == { "subtitle": "l_1_subtitle", "something": "l_1_something", "title": "l_1_title", "title_upper": "l_1_title_upper", } assert macro_sym.loads == { "l_1_subtitle": ("resolve", "subtitle"), "l_1_something": ("resolve", "something"), "l_1_title": ("param", None), "l_1_title_upper": ("resolve", "title_upper"), } assert macro_sym.stores == {"title", "title_upper", "subtitle"} assert macro_sym.find_ref("render_title") == "l_0_render_title" assert macro_sym.dump_stores() == { "title": "l_1_title", "title_upper": "l_1_title_upper", "subtitle": "l_1_subtitle", "render_title": "l_0_render_title", } body_sym = symbols_for_node(body_block) assert body_sym.refs == { "item": "l_0_item", "seq": "l_0_seq", "render_title": "l_0_render_title", } assert body_sym.loads == { "l_0_item": ("resolve", "item"), "l_0_seq": ("resolve", "seq"), "l_0_render_title": ("resolve", "render_title"), } assert body_sym.stores == set() for_sym = symbols_for_node(for_loop, body_sym) assert for_sym.refs == { "item": "l_1_item", } assert for_sym.loads == { "l_1_item": ("param", None), } assert for_sym.stores == {"item"} assert for_sym.dump_stores() == { "item": "l_1_item", }