pongo2 icon indicating copy to clipboard operation
pongo2 copied to clipboard

rework macro import

Open jjkavalam opened this issue 2 years ago • 1 comments

Problem

The issue this PR fixes is https://github.com/flosch/pongo2/issues/330

Background: How a macro is currently executed ?

  1. importNode.Execute / macroNode.Execute places the macroNode into current context (Private)
  2. variable finds macroNode in the current context (Private)
  3. 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.");
}

jjkavalam avatar Feb 12 '23 16:02 jjkavalam

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

No Coverage information No Coverage information
No Duplication information No Duplication information

sonarqubecloud[bot] avatar Feb 12 '23 16:02 sonarqubecloud[bot]