rework macro import
Problem
The issue this PR fixes is https://github.com/flosch/pongo2/issues/330
Background: How a macro is currently executed ?
- importNode.Execute / macroNode.Execute places the macroNode into current context (Private)
- variable finds macroNode in the current context (Private)
- macroNode.call calls the wrapper with context of the caller
Since, macro definitions are looked up from the execution context, only those macros that are available there can be successfully lookedup when a variable evaluates.
The variable should also be able to look into the lexical context (i.e. the containing document) and find macroNodes there as well.
{# filename: macros.j2 #}
{% macro child() %}Child{% endmacro %}
{% macro main() export %}
Main calls {{ child() }} // 3. looks up "child" in ctx ? does not exist !
{% endmacro %}
---
{# filename: test1.j2 #}
{% import "macros.j2" main %} // 1. ctx["main"] = macroNode#main
{{ main() }} // 2. lookup "main" in context
Maintain lexicalCtx as well; and it should be attached to the file. Variable resolution should lookup there as well.
Whenever a macro definition or import is executed, it should place the definitions in the lexical scope instead.
This solution at a glance
Following TypeScript code snippet shows the proposed mechanism for dealing with macros that can solves the issue mentioned above.
class MacroNode {
name: string;
call(ctx: ExecutionContext) {
}
execute() {
// don't do anything; macros are no more looked up from the Private context
}
static parse(template: Template): MacroNode {
const node = new MacroNode();
// register macro node on the template
template.ownMacroNodes.push(node);
return node;
}
}
class ImportNode {
macros: MacroNode[];
template: Template;
execute() {
// don't do anything; macros are no more looked up from the Private context
}
static parse(template: Template): ImportNode {
const node = new ImportNode();
// register import nodes on the template
node.template = template;
template.macroImportNodes.push(node);
return node;
}
}
class Template {
ownMacroNodes: MacroNode[];
macroImportNodes: ImportNode[];
}
class ExecutionContext {
template: Template;
}
class VariableResolver {
resolve(ctx: ExecutionContext, varname: string) {
let macroNode = find(ctx.template.ownMacroNodes, varname);
if (macroNode) {
// call own macros with same context
macroNode.call(ctx);
}
else {
for (const importNode of ctx.template.macroImportNodes) {
macroNode = find(importNode.macros, varname);
if (macroNode) {
const thisTemplate = ctx.template;
ctx.template = importNode.template;
// call imported macros with adjusted context
macroNode.call(ctx);
ctx.template = thisTemplate;
break;
}
}
}
}
}
function find(macros: MacroNode[], name: string): MacroNode {
throw new Error("Function not implemented.");
}







