Skip to content

SOURCE CODE liquid.patching DOCS

"""Patch a couple of jinja functions to implement some features
that are impossible or too complex to be implemented by extensions

Including
1. Patching Parser.parse to allow 'elsif' in addition to 'elif'
2. Patching LoopContext to allow rindex and rindex0
3. Adding liquid_cycle method to LoopContext to allow cycle to have a name
4. Patching Parser.parse_for to allow arguments for tag 'for'
"""
from typing import Any
from jinja2 import nodes
from jinja2.parser import Parser
from jinja2.runtime import LoopContext

from .utils import parse_tag_args


# patching Parser.parse_if to allow elsif in addition to elif
# -----------------------------------------------------------
def parse_if(self) -> nodes.Node:
    node = result = nodes.If(lineno=self.stream.expect("name:if").lineno)
    while True:
        node.test = self.parse_tuple(with_condexpr=False)
        node.body = self.parse_statements(
            ("name:elif", "name:elsif", "name:else", "name:endif")
        )
        node.elif_ = []
        node.else_ = []
        token = next(self.stream)
        if token.test_any("name:elif", "name:elsif"):
            node = nodes.If(lineno=self.stream.current.lineno)
            result.elif_.append(node)
            continue
        elif token.test("name:else"):
            result.else_ = self.parse_statements(
                ("name:endif",), drop_needle=True
            )
        break
    return result


jinja_nodes_if_fields = nodes.If.fields
jinja_parse_if = Parser.parse_if


# patching LoopContext to allow rindex and rindex0
# Also add liquid_cycle method to allow cycle to have a name
# -----------------------------------------------------------
def cycle(self, *args: Any, name: Any = None) -> Any:
    if not hasattr(self, "_liquid_cyclers"):
        setattr(self, "_liquid_cyclers", {})
    cyclers = self._liquid_cyclers
    if name not in cyclers:
        cyclers[name] = [args, -1]
    cycler = cyclers[name]
    cycler[1] += 1
    return cycler[0][cycler[1] % len(cycler[0])]


# patching Parser.parse_for to allow arguments
# -----------------------------------------------------------
def parse_for(self) -> nodes.Node:
    lineno = self.stream.expect("name:for").lineno
    target = self.parse_assign_target(extra_end_rules=("name:in",))
    self.stream.expect("name:in")
    iter = self.parse_tuple(
        with_condexpr=False,
        extra_end_rules=(
            "name:recursive",
            "name:reversed",
            "name:limit",
            "name:offset",
        ),
    )
    reverse = self.stream.skip_if("name:reversed")
    limit = parse_tag_args(self.stream, "limit", lineno)
    offset = parse_tag_args(self.stream, "offset", 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 reverse:
        iter = nodes.Filter(iter, "reverse", [], [], None, None)

    test = None
    if self.stream.skip_if("name:if"):
        test = self.parse_expression()
    recursive = self.stream.skip_if("name:recursive")
    body = self.parse_statements(("name:endfor", "name:else"))
    if next(self.stream).value == "endfor":
        else_ = []
    else:
        else_ = self.parse_statements(("name:endfor",), drop_needle=True)
    return nodes.For(target, iter, body, else_, test, recursive, lineno=lineno)


jinja_parse_for = Parser.parse_for


def patch_jinja():DOCS
    """Monkey-patch jinja"""
    nodes.If.fields = jinja_nodes_if_fields + ("elsif",)
    nodes.If.elsif = None
    Parser.parse_if = parse_if

    LoopContext.rindex = LoopContext.revindex
    LoopContext.rindex0 = LoopContext.revindex0
    LoopContext.liquid_cycle = cycle

    Parser.parse_for = parse_for


def unpatch_jinja():DOCS
    """Restore the patches to jinja"""
    nodes.If.fields = jinja_nodes_if_fields
    del nodes.If.elsif

    Parser.parse_if = jinja_parse_if
    del LoopContext.rindex
    del LoopContext.rindex0
    del LoopContext.liquid_cycle

    Parser.parse_for = jinja_parse_for