mirror of
				https://github.com/explosion/spaCy.git
				synced 2025-11-04 09:57:26 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			1274 lines
		
	
	
		
			46 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1274 lines
		
	
	
		
			46 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# Forked from: https://github.com/jonbretman/jinja-to-js
 | 
						|
# With additional functionality: in/not in, replace, pprint, round, + for lists,
 | 
						|
# rendering empty dicts
 | 
						|
# This script is mostly used to generate the JavaScript function for the
 | 
						|
# training quickstart widget.
 | 
						|
import contextlib
 | 
						|
import json
 | 
						|
import re
 | 
						|
import os
 | 
						|
from os import path
 | 
						|
from io import StringIO
 | 
						|
from jinja2 import Environment, FileSystemLoader, nodes
 | 
						|
from pathlib import Path
 | 
						|
import srsly
 | 
						|
import sys
 | 
						|
 | 
						|
 | 
						|
OPERANDS = {
 | 
						|
    "eq": "===",
 | 
						|
    "ne": "!==",
 | 
						|
    "lt": " < ",
 | 
						|
    "gt": " > ",
 | 
						|
    "lteq": " <= ",
 | 
						|
    "gteq": " >= ",
 | 
						|
}
 | 
						|
 | 
						|
DICT_ITER_METHODS = ("iteritems", "items", "values", "keys")
 | 
						|
 | 
						|
STATE_DEFAULT = 0
 | 
						|
STATE_EXECUTING = 1
 | 
						|
STATE_INTERPOLATING = 2
 | 
						|
 | 
						|
LOOP_HELPER_INDEX = "index"
 | 
						|
LOOP_HELPER_INDEX_0 = "index0"
 | 
						|
LOOP_HELPER_FIRST = "first"
 | 
						|
LOOP_HELPER_LAST = "last"
 | 
						|
LOOP_HELPER_LENGTH = "length"
 | 
						|
LOOP_HELPERS = (
 | 
						|
    LOOP_HELPER_INDEX,
 | 
						|
    LOOP_HELPER_INDEX_0,
 | 
						|
    LOOP_HELPER_FIRST,
 | 
						|
    LOOP_HELPER_LAST,
 | 
						|
    LOOP_HELPER_LENGTH,
 | 
						|
)
 | 
						|
 | 
						|
 | 
						|
def amd_format(dependencies, template_function):
 | 
						|
    result = "define(["
 | 
						|
    result += ",".join('"{0}"'.format(x[0]) for x in dependencies)
 | 
						|
    result += "], function ("
 | 
						|
    result += ",".join(x[1] for x in dependencies)
 | 
						|
    result += ") { return "
 | 
						|
    result += template_function
 | 
						|
    result += "; });"
 | 
						|
    return result
 | 
						|
 | 
						|
 | 
						|
def commonjs_format(dependencies, template_function):
 | 
						|
    result = "".join('var {0} = require("{1}");'.format(y, x) for x, y in dependencies)
 | 
						|
    result += "module.exports = {0};".format(template_function)
 | 
						|
    return result
 | 
						|
 | 
						|
 | 
						|
def es6_format(dependencies, template_function):
 | 
						|
    result = "".join('import {0} from "{1}";'.format(y, x) for x, y in dependencies)
 | 
						|
    result += "export default {0}".format(template_function)
 | 
						|
    return result
 | 
						|
 | 
						|
 | 
						|
JS_MODULE_FORMATS = {
 | 
						|
    None: lambda dependencies, template_function: template_function,
 | 
						|
    "amd": amd_format,
 | 
						|
    "commonjs": commonjs_format,
 | 
						|
    "es6": es6_format,
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
# This string has to double all the '{' and '}' due to Python's string formatting.
 | 
						|
# See - https://docs.python.org/2/library/string.html#formatstrings
 | 
						|
TEMPLATE_WRAPPER = """
 | 
						|
function {function_name}(ctx) {{
 | 
						|
    var __result = "";
 | 
						|
    var __tmp;
 | 
						|
    var __runtime = jinjaToJS.runtime;
 | 
						|
    var __filters = jinjaToJS.filters;
 | 
						|
    var __globals = jinjaToJS.globals;
 | 
						|
    var context = jinjaToJS.createContext(ctx);
 | 
						|
    {template_code}
 | 
						|
    return __result;
 | 
						|
}}
 | 
						|
"""
 | 
						|
 | 
						|
 | 
						|
class ExtendsException(Exception):
 | 
						|
    """
 | 
						|
    Raised when an {% extends %} is encountered. At this point the parent template is
 | 
						|
    loaded and all blocks defined in the current template passed to it.
 | 
						|
    """
 | 
						|
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
@contextlib.contextmanager
 | 
						|
def option(current_kwargs, **kwargs):
 | 
						|
    """
 | 
						|
    Context manager for temporarily setting a keyword argument and
 | 
						|
    then restoring it to whatever it was before.
 | 
						|
    """
 | 
						|
 | 
						|
    tmp_kwargs = dict((key, current_kwargs.get(key)) for key, value in kwargs.items())
 | 
						|
    current_kwargs.update(kwargs)
 | 
						|
    yield
 | 
						|
    current_kwargs.update(tmp_kwargs)
 | 
						|
 | 
						|
 | 
						|
def is_method_call(node, method_name):
 | 
						|
    """
 | 
						|
    Returns True if `node` is a method call for `method_name`. `method_name`
 | 
						|
    can be either a string or an iterable of strings.
 | 
						|
    """
 | 
						|
 | 
						|
    if not isinstance(node, nodes.Call):
 | 
						|
        return False
 | 
						|
 | 
						|
    if isinstance(node.node, nodes.Getattr):
 | 
						|
        # e.g. foo.bar()
 | 
						|
        method = node.node.attr
 | 
						|
 | 
						|
    elif isinstance(node.node, nodes.Name):
 | 
						|
        # e.g. bar()
 | 
						|
        method = node.node.name
 | 
						|
 | 
						|
    elif isinstance(node.node, nodes.Getitem):
 | 
						|
        # e.g. foo["bar"]()
 | 
						|
        method = node.node.arg.value
 | 
						|
 | 
						|
    else:
 | 
						|
        return False
 | 
						|
 | 
						|
    if isinstance(method_name, (list, tuple)):
 | 
						|
        return method in method_name
 | 
						|
 | 
						|
    return method == method_name
 | 
						|
 | 
						|
 | 
						|
def is_loop_helper(node):
 | 
						|
    """
 | 
						|
    Returns True is node is a loop helper e.g. {{ loop.index }} or {{ loop.first }}
 | 
						|
    """
 | 
						|
    return (
 | 
						|
        hasattr(node, "node")
 | 
						|
        and isinstance(node.node, nodes.Name)
 | 
						|
        and node.node.name == "loop"
 | 
						|
    )
 | 
						|
 | 
						|
 | 
						|
def temp_var_names_generator():
 | 
						|
    x = 0
 | 
						|
    while True:
 | 
						|
        yield "__$%s" % x
 | 
						|
        x += 1
 | 
						|
 | 
						|
 | 
						|
class JinjaToJS(object):
 | 
						|
    def __init__(
 | 
						|
        self,
 | 
						|
        template_root,
 | 
						|
        template_name,
 | 
						|
        js_module_format=None,
 | 
						|
        runtime_path="jinja-to-js",
 | 
						|
        include_prefix="",
 | 
						|
        include_ext="",
 | 
						|
        child_blocks=None,
 | 
						|
        dependencies=None,
 | 
						|
        custom_filters=None,
 | 
						|
    ):
 | 
						|
        """
 | 
						|
        Args:
 | 
						|
            template_root (str): The path to where templates should be loaded from.
 | 
						|
            template_name (str): The name of the template to compile (relative to `template_root`).
 | 
						|
            js_module_format (str, optional): The JavaScript module format to use.
 | 
						|
                                              One of ('amd', 'commonjs', 'es6')
 | 
						|
            runtime_path (str, optional): If `js_module_format` is specified then the JavaScript
 | 
						|
                                          runtime will be imported using the appropriate method.
 | 
						|
                                          It defaults to assuming it will be imported from
 | 
						|
                                          `node_modules` but you can change it using this option.
 | 
						|
            include_prefix (str, optional): If using the `amd` module format you can use this option
 | 
						|
                                            to add a prefix to every include path as AMD imports are
 | 
						|
                                            generally relative to the main file, not the module
 | 
						|
                                            importing.
 | 
						|
            include_ext (str, optional): By default any includes will be references without an
 | 
						|
                                         extension, as neither AMD, commonJS or ES6 require the
 | 
						|
                                         '.js' extension. If you want to use an extension, say
 | 
						|
                                         '.template' then set this option to a string including
 | 
						|
                                         the leading '.'
 | 
						|
            child_blocks (dict, optional): Used internally when handling templates that extend
 | 
						|
                                           other templates.
 | 
						|
            dependencies (list of tuple, optional): Used internally when handling templates that
 | 
						|
                                                    extend other templates.
 | 
						|
            custom_filters (list of str, optional): List of custom filters which should be allowed.
 | 
						|
                                                    These may be filters supported by Jinja but not
 | 
						|
                                                    supported by jinja-to-js. These filters MUST be
 | 
						|
                                                    registered with the jinja-to-js JS runtime.
 | 
						|
        """
 | 
						|
 | 
						|
        self.environment = Environment(
 | 
						|
            loader=FileSystemLoader(template_root),
 | 
						|
            autoescape=True,
 | 
						|
            extensions=["jinja2.ext.with_", "jinja2.ext.autoescape"],
 | 
						|
        )
 | 
						|
        self.output = StringIO()
 | 
						|
        self.stored_names = set()
 | 
						|
        self.temp_var_names = temp_var_names_generator()
 | 
						|
        self.state = STATE_DEFAULT
 | 
						|
        self.child_blocks = child_blocks or {}
 | 
						|
        self.dependencies = dependencies or []
 | 
						|
        self._runtime_function_cache = []
 | 
						|
        self.js_module_format = js_module_format
 | 
						|
        self.runtime_path = runtime_path
 | 
						|
        self.include_prefix = include_prefix
 | 
						|
        self.include_ext = include_ext
 | 
						|
        self.template_root = template_root
 | 
						|
        self.template_name = template_name
 | 
						|
        self.custom_filters = custom_filters or []
 | 
						|
 | 
						|
        # The name of the JavaScript function that will output this template. By using a named
 | 
						|
        # function the template can call itself which is required to support recursive includes.
 | 
						|
        self.js_function_name = "template" + "".join(
 | 
						|
            x.title()
 | 
						|
            for x in re.split(r"[^\w]|_", path.splitext(self.template_name)[0])
 | 
						|
        )
 | 
						|
 | 
						|
        self.context_name = "context"
 | 
						|
 | 
						|
        self._add_dependency(self.runtime_path, "jinjaToJS")
 | 
						|
 | 
						|
        # Jinja2 doesn't accept Windows filepaths
 | 
						|
        if os.name == "nt":
 | 
						|
            self.template_name = self.template_name.replace(os.pathsep, "/")
 | 
						|
 | 
						|
        template_string, template_path, _ = self.environment.loader.get_source(
 | 
						|
            self.environment, self.template_name
 | 
						|
        )
 | 
						|
 | 
						|
        # It is assumed that this will be the absolute path to the template. It is used to work out
 | 
						|
        # related paths for inclues.
 | 
						|
        self.template_path = template_path
 | 
						|
 | 
						|
        if self.js_module_format not in JS_MODULE_FORMATS.keys():
 | 
						|
            raise ValueError(
 | 
						|
                "The js_module_format option must be one of: %s"
 | 
						|
                % JS_MODULE_FORMATS.keys()
 | 
						|
            )
 | 
						|
 | 
						|
        self.ast = self.environment.parse(template_string)
 | 
						|
 | 
						|
        try:
 | 
						|
            for node in self.ast.body:
 | 
						|
                self._process_node(node)
 | 
						|
        except ExtendsException:
 | 
						|
            pass
 | 
						|
 | 
						|
    def get_output(self):
 | 
						|
        """
 | 
						|
        Returns the generated JavaScript code.
 | 
						|
 | 
						|
        Returns:
 | 
						|
            str
 | 
						|
        """
 | 
						|
        # generate the JS function string
 | 
						|
        template_function = TEMPLATE_WRAPPER.format(
 | 
						|
            function_name=self.js_function_name, template_code=self.output.getvalue()
 | 
						|
        ).strip()
 | 
						|
 | 
						|
        # get the correct module format template
 | 
						|
        module_format = JS_MODULE_FORMATS[self.js_module_format]
 | 
						|
 | 
						|
        # generate the module code
 | 
						|
        return module_format(self.dependencies, template_function)
 | 
						|
 | 
						|
    def _get_depencency_var_name(self, dependency):
 | 
						|
        """
 | 
						|
        Returns the variable name assigned to the given dependency or None if the dependency has
 | 
						|
        not yet been registered.
 | 
						|
 | 
						|
        Args:
 | 
						|
            dependency (str): Thet dependency that needs to be imported.
 | 
						|
 | 
						|
        Returns:
 | 
						|
            str or None
 | 
						|
        """
 | 
						|
        for dep_path, var_name in self.dependencies:
 | 
						|
            if dep_path == dependency:
 | 
						|
                return var_name
 | 
						|
 | 
						|
    def _add_dependency(self, dependency, var_name=None):
 | 
						|
        """
 | 
						|
        Adds the given dependency and returns the variable name to use to access it. If `var_name`
 | 
						|
        is not given then a random one will be created.
 | 
						|
 | 
						|
        Args:
 | 
						|
            dependency (str):
 | 
						|
            var_name (str, optional):
 | 
						|
 | 
						|
        Returns:
 | 
						|
            str
 | 
						|
        """
 | 
						|
        if var_name is None:
 | 
						|
            var_name = next(self.temp_var_names)
 | 
						|
        # Don't add duplicate dependencies
 | 
						|
        if (dependency, var_name) not in self.dependencies:
 | 
						|
            self.dependencies.append((dependency, var_name))
 | 
						|
        return var_name
 | 
						|
 | 
						|
    def _process_node(self, node, **kwargs):
 | 
						|
        node_name = node.__class__.__name__.lower()
 | 
						|
        handler = getattr(self, "_process_" + node_name, None)
 | 
						|
        if callable(handler):
 | 
						|
            handler(node, **kwargs)
 | 
						|
        else:
 | 
						|
            raise Exception(f"Unknown node {node} ({node_name})")
 | 
						|
 | 
						|
    def _process_extends(self, node, **kwargs):
 | 
						|
        """
 | 
						|
        Processes an extends block e.g. `{% extends "some/template.jinja" %}`
 | 
						|
        """
 | 
						|
 | 
						|
        # find all the blocks in this template
 | 
						|
        for b in self.ast.find_all(nodes.Block):
 | 
						|
 | 
						|
            # if not already in `child_blocks` then this is the first time a
 | 
						|
            # block with this name has been encountered.
 | 
						|
            if b.name not in self.child_blocks:
 | 
						|
                self.child_blocks[b.name] = b
 | 
						|
            else:
 | 
						|
 | 
						|
                # otherwise we have seen this block before, so we need to find the last
 | 
						|
                # super_block and add the block from this template to the end.
 | 
						|
                block = self.child_blocks.get(b.name)
 | 
						|
                while hasattr(block, "super_block"):
 | 
						|
                    block = block.super_block
 | 
						|
                block.super_block = b
 | 
						|
 | 
						|
        # load the parent template
 | 
						|
        parent_template = JinjaToJS(
 | 
						|
            template_root=self.template_root,
 | 
						|
            template_name=node.template.value,
 | 
						|
            js_module_format=self.js_module_format,
 | 
						|
            runtime_path=self.runtime_path,
 | 
						|
            include_prefix=self.include_prefix,
 | 
						|
            include_ext=self.include_ext,
 | 
						|
            child_blocks=self.child_blocks,
 | 
						|
            dependencies=self.dependencies,
 | 
						|
        )
 | 
						|
 | 
						|
        # add the parent templates output to the current output
 | 
						|
        self.output.write(parent_template.output.getvalue())
 | 
						|
 | 
						|
        # Raise an exception so we stop parsing this template
 | 
						|
        raise ExtendsException
 | 
						|
 | 
						|
    def _process_block(self, node, **kwargs):
 | 
						|
        """
 | 
						|
        Processes a block e.g. `{% block my_block %}{% endblock %}`
 | 
						|
        """
 | 
						|
 | 
						|
        # check if this node already has a 'super_block' attribute
 | 
						|
        if not hasattr(node, "super_block"):
 | 
						|
 | 
						|
            # since it doesn't it must be the last block in the inheritance chain
 | 
						|
            node.super_block = None
 | 
						|
 | 
						|
            # see if there has been a child block defined - if there is this
 | 
						|
            # will be the first block in the inheritance chain
 | 
						|
            child_block = self.child_blocks.get(node.name)
 | 
						|
 | 
						|
            if child_block:
 | 
						|
 | 
						|
                # we have child nodes so we need to set `node` as the
 | 
						|
                # super of the last one in the chain
 | 
						|
                last_block = child_block
 | 
						|
                while hasattr(last_block, "super_block"):
 | 
						|
                    last_block = child_block.super_block
 | 
						|
 | 
						|
                # once we have found it, set this node as it's super block
 | 
						|
                last_block.super_block = node
 | 
						|
 | 
						|
                # this is the node we want to process as it's the first in the inheritance chain
 | 
						|
                node = child_block
 | 
						|
 | 
						|
        # process the block passing the it's super along, if this block
 | 
						|
        # calls super() it will be handled by `_process_call`
 | 
						|
        for n in node.body:
 | 
						|
            self._process_node(n, super_block=node.super_block, **kwargs)
 | 
						|
 | 
						|
    def _process_output(self, node, **kwargs):
 | 
						|
        """
 | 
						|
        Processes an output node, which will contain things like `Name` and `TemplateData` nodes.
 | 
						|
        """
 | 
						|
        for n in node.nodes:
 | 
						|
            self._process_node(n, **kwargs)
 | 
						|
 | 
						|
    def _process_templatedata(self, node, **_):
 | 
						|
        """
 | 
						|
        Processes a `TemplateData` node, this is just a bit of as-is text
 | 
						|
        to be written to the output.
 | 
						|
        """
 | 
						|
 | 
						|
        # escape double quotes
 | 
						|
        value = re.sub('"', r'\\"', node.data)
 | 
						|
 | 
						|
        # escape new lines
 | 
						|
        value = re.sub("\n", r"\\n", value)
 | 
						|
 | 
						|
        # append value to the result
 | 
						|
        self.output.write('__result += "' + value + '";')
 | 
						|
 | 
						|
    def _process_name(self, node, **kwargs):
 | 
						|
        """
 | 
						|
        Processes a `Name` node. Some examples of `Name` nodes:
 | 
						|
            {{ foo }} -> 'foo' is a Name
 | 
						|
            {% if foo }} -> 'foo' is a Name
 | 
						|
        """
 | 
						|
 | 
						|
        with self._interpolation():
 | 
						|
            with self._python_bool_wrapper(**kwargs):
 | 
						|
 | 
						|
                if node.name not in self.stored_names and node.ctx != "store":
 | 
						|
                    self.output.write(self.context_name)
 | 
						|
                    self.output.write(".")
 | 
						|
 | 
						|
                if node.ctx == "store":
 | 
						|
                    self.stored_names.add(node.name)
 | 
						|
 | 
						|
                self.output.write(node.name)
 | 
						|
 | 
						|
    def _process_dict(self, node, **kwargs):
 | 
						|
        with self._interpolation():
 | 
						|
            with self._python_bool_wrapper(**kwargs):
 | 
						|
                if node.items:
 | 
						|
                    err = f"Can't process non-empty dict in expression: {node}"
 | 
						|
                    raise ValueError(err)
 | 
						|
                self.output.write("{}")
 | 
						|
 | 
						|
    def _process_getattr(self, node, **kwargs):
 | 
						|
        """
 | 
						|
        Processes a `GetAttr` node. e.g. {{ foo.bar }}
 | 
						|
        """
 | 
						|
 | 
						|
        with self._interpolation():
 | 
						|
            with self._python_bool_wrapper(**kwargs) as new_kwargs:
 | 
						|
                if is_loop_helper(node):
 | 
						|
                    self._process_loop_helper(node, **new_kwargs)
 | 
						|
                else:
 | 
						|
                    self._process_node(node.node, **new_kwargs)
 | 
						|
                    self.output.write(".")
 | 
						|
                    self.output.write(node.attr)
 | 
						|
 | 
						|
    def _process_getitem(self, node, **kwargs):
 | 
						|
        """
 | 
						|
        Processes a `GetItem` node e.g. {{ foo["bar"] }}
 | 
						|
        """
 | 
						|
 | 
						|
        with self._interpolation():
 | 
						|
            with self._python_bool_wrapper(**kwargs) as new_kwargs:
 | 
						|
                self._process_node(node.node, **new_kwargs)
 | 
						|
 | 
						|
                if isinstance(node.arg, nodes.Slice):
 | 
						|
                    self.output.write(".slice(")
 | 
						|
 | 
						|
                    if node.arg.step is not None:
 | 
						|
                        raise Exception(
 | 
						|
                            "The step argument is not supported when slicing."
 | 
						|
                        )
 | 
						|
 | 
						|
                    if node.arg.start is None:
 | 
						|
                        self.output.write("0")
 | 
						|
                    else:
 | 
						|
                        self._process_node(node.arg.start, **new_kwargs)
 | 
						|
 | 
						|
                    if node.arg.stop is None:
 | 
						|
                        self.output.write(")")
 | 
						|
                    else:
 | 
						|
                        self.output.write(",")
 | 
						|
                        self._process_node(node.arg.stop, **new_kwargs)
 | 
						|
                        self.output.write(")")
 | 
						|
                else:
 | 
						|
                    self.output.write("[")
 | 
						|
                    self._process_node(node.arg, **new_kwargs)
 | 
						|
                    self.output.write("]")
 | 
						|
 | 
						|
    def _process_for(self, node, **kwargs):
 | 
						|
        """
 | 
						|
        Processes a for loop. e.g.
 | 
						|
            {% for number in numbers %}
 | 
						|
                {{ number }}
 | 
						|
            {% endfor %}
 | 
						|
            {% for key, value in somemap.items() %}
 | 
						|
                {{ key }} -> {{ value }}
 | 
						|
            {% %}
 | 
						|
        """
 | 
						|
 | 
						|
        # since a for loop can introduce new names into the context
 | 
						|
        # we need to remember the ones that existed outside the loop
 | 
						|
        previous_stored_names = self.stored_names.copy()
 | 
						|
 | 
						|
        with self._execution():
 | 
						|
            self.output.write("__runtime.each(")
 | 
						|
 | 
						|
            if is_method_call(node.iter, dict.keys.__name__):
 | 
						|
                self.output.write("Object.keys(")
 | 
						|
 | 
						|
            self._process_node(node.iter, **kwargs)
 | 
						|
 | 
						|
            if is_method_call(node.iter, dict.keys.__name__):
 | 
						|
                self.output.write(")")
 | 
						|
 | 
						|
            self.output.write(",")
 | 
						|
            self.output.write("function")
 | 
						|
            self.output.write("(")
 | 
						|
 | 
						|
            # javascript iterations put the value first, then the key
 | 
						|
            if isinstance(node.target, nodes.Tuple):
 | 
						|
                if len(node.target.items) > 2:
 | 
						|
                    raise Exception(
 | 
						|
                        "De-structuring more than 2 items is not supported."
 | 
						|
                    )
 | 
						|
 | 
						|
                for i, item in enumerate(reversed(node.target.items)):
 | 
						|
                    self._process_node(item, **kwargs)
 | 
						|
                    if i < len(node.target.items) - 1:
 | 
						|
                        self.output.write(",")
 | 
						|
            else:
 | 
						|
                self._process_node(node.target, **kwargs)
 | 
						|
 | 
						|
            self.output.write(")")
 | 
						|
            self.output.write("{")
 | 
						|
 | 
						|
            if node.test:
 | 
						|
                self.output.write("if (!(")
 | 
						|
                self._process_node(node.test, **kwargs)
 | 
						|
                self.output.write(")) { return; }")
 | 
						|
 | 
						|
        assigns = (
 | 
						|
            node.target.items if isinstance(node.target, nodes.Tuple) else [node.target]
 | 
						|
        )
 | 
						|
 | 
						|
        with self._scoped_variables(assigns, **kwargs):
 | 
						|
            for n in node.body:
 | 
						|
                self._process_node(n, **kwargs)
 | 
						|
 | 
						|
        with self._execution():
 | 
						|
            self.output.write("}")
 | 
						|
            self.output.write(")")
 | 
						|
            self.output.write(";")
 | 
						|
 | 
						|
        # restore the stored names
 | 
						|
        self.stored_names = previous_stored_names
 | 
						|
 | 
						|
    def _process_if(self, node, execute_end=None, **kwargs):
 | 
						|
        """
 | 
						|
        Processes an if block e.g. `{% if foo %} do something {% endif %}`
 | 
						|
        """
 | 
						|
 | 
						|
        with self._execution():
 | 
						|
            self.output.write("if")
 | 
						|
            self.output.write("(")
 | 
						|
 | 
						|
            with option(kwargs, use_python_bool_wrapper=True):
 | 
						|
                self._process_node(node.test, **kwargs)
 | 
						|
 | 
						|
            self.output.write(")")
 | 
						|
            self.output.write("{")
 | 
						|
 | 
						|
        # We accept an `execute_end` function as a keyword argument as this function is
 | 
						|
        # recursive in the case of something like if-elif-elif-else. In these cases this
 | 
						|
        # invocation of this function may have to close execution opened by a previous
 | 
						|
        # invocation of this function.
 | 
						|
        if execute_end:
 | 
						|
            execute_end()
 | 
						|
 | 
						|
        # body
 | 
						|
        for n in node.body:
 | 
						|
            self._process_node(n, **kwargs)
 | 
						|
 | 
						|
        if not node.else_ and not node.elif_:
 | 
						|
            # no else - just close the if
 | 
						|
            with self._execution():
 | 
						|
                self.output.write("}")
 | 
						|
 | 
						|
        else:
 | 
						|
            # either an else or an elif
 | 
						|
            with self._execution() as execute_end:
 | 
						|
                self.output.write("}")
 | 
						|
                self.output.write(" else ")
 | 
						|
 | 
						|
                # check for elif
 | 
						|
                for n in node.elif_:
 | 
						|
                    self._process_node(n, execute_end=execute_end, **kwargs)
 | 
						|
 | 
						|
                if node.elif_ and node.else_:
 | 
						|
                    self.output.write(" else ")
 | 
						|
 | 
						|
                # open up the body
 | 
						|
                self.output.write("{")
 | 
						|
 | 
						|
            # process the body of the else
 | 
						|
            for n in node.else_:
 | 
						|
                self._process_node(n, **kwargs)
 | 
						|
 | 
						|
            # close the body
 | 
						|
            with self._execution():
 | 
						|
                self.output.write("}")
 | 
						|
 | 
						|
    def _process_condexpr(self, node, **kwargs):
 | 
						|
        with self._interpolation():
 | 
						|
            self.output.write("(")
 | 
						|
 | 
						|
            with self._python_bool_wrapper(**kwargs) as new_kwargs:
 | 
						|
                self._process_node(node.test, **new_kwargs)
 | 
						|
 | 
						|
            self.output.write(" ? ")
 | 
						|
            self._process_node(node.expr1, **kwargs)
 | 
						|
            self.output.write(" : ")
 | 
						|
            self._process_node(node.expr2, **kwargs)
 | 
						|
            self.output.write(")")
 | 
						|
 | 
						|
    def _process_not(self, node, **kwargs):
 | 
						|
        self.output.write("!")
 | 
						|
 | 
						|
        with self._python_bool_wrapper(**kwargs) as new_kwargs:
 | 
						|
            self._process_node(node.node, **new_kwargs)
 | 
						|
 | 
						|
    def _process_or(self, node, **kwargs):
 | 
						|
        self._process_node(node.left, **kwargs)
 | 
						|
        self.output.write(" || ")
 | 
						|
        self._process_node(node.right, **kwargs)
 | 
						|
 | 
						|
    def _process_and(self, node, **kwargs):
 | 
						|
        self._process_node(node.left, **kwargs)
 | 
						|
        self.output.write(" && ")
 | 
						|
        self._process_node(node.right, **kwargs)
 | 
						|
 | 
						|
    def _process_tuple(self, node, **kwargs):
 | 
						|
        self.output.write("[")
 | 
						|
        for i, item in enumerate(node.items):
 | 
						|
            self._process_node(item, **kwargs)
 | 
						|
            if i < len(node.items) - 1:
 | 
						|
                self.output.write(",")
 | 
						|
        self.output.write("]")
 | 
						|
 | 
						|
    def _process_call(self, node, super_block=None, **kwargs):
 | 
						|
        if is_method_call(node, DICT_ITER_METHODS):
 | 
						|
            # special case for dict methods
 | 
						|
            self._process_node(node.node.node, **kwargs)
 | 
						|
 | 
						|
        elif is_method_call(node, "super"):
 | 
						|
            # special case for the super() method which is available inside blocks
 | 
						|
            if not super_block:
 | 
						|
                raise Exception("super() called outside of a block with a parent.")
 | 
						|
            self._process_node(super_block, **kwargs)
 | 
						|
 | 
						|
        else:
 | 
						|
            # just a normal function call on a context variable
 | 
						|
            with self._interpolation():
 | 
						|
                with self._python_bool_wrapper(**kwargs) as new_kwargs:
 | 
						|
                    self._process_node(node.node, **new_kwargs)
 | 
						|
                    self.output.write("(")
 | 
						|
                    self._process_args(node, **new_kwargs)
 | 
						|
                    self.output.write(")")
 | 
						|
 | 
						|
                    # only output the semi-colon if we are not interpolating
 | 
						|
                    if self.state != STATE_INTERPOLATING:
 | 
						|
                        self.output.write("")
 | 
						|
 | 
						|
    def _process_filter(self, node, **kwargs):
 | 
						|
        method_name = getattr(self, "_process_filter_%s" % node.name, None)
 | 
						|
        if callable(method_name):
 | 
						|
            method_name(node, **kwargs)
 | 
						|
        elif node.name in self.custom_filters:
 | 
						|
            with self._interpolation(safe=True):
 | 
						|
                with self._python_bool_wrapper(**kwargs) as new_kwargs:
 | 
						|
                    self.output.write("__filters.%s(" % node.name)
 | 
						|
                    self._process_node(node.node, **new_kwargs)
 | 
						|
                    if getattr(node, "args", None):
 | 
						|
                        self.output.write(",")
 | 
						|
                        self._process_args(node, **new_kwargs)
 | 
						|
                    self.output.write(")")
 | 
						|
        else:
 | 
						|
            raise Exception("Unsupported filter: %s" % node.name)
 | 
						|
 | 
						|
    def _process_filter_safe(self, node, **kwargs):
 | 
						|
        with self._interpolation(safe=True):
 | 
						|
            with self._python_bool_wrapper(**kwargs) as new_kwargs:
 | 
						|
                self._process_node(node.node, **new_kwargs)
 | 
						|
 | 
						|
    def _process_filter_capitalize(self, node, **kwargs):
 | 
						|
        with self._interpolation():
 | 
						|
            with self._python_bool_wrapper(**kwargs) as new_kwargs:
 | 
						|
                self.output.write("__filters.capitalize(")
 | 
						|
                self._process_node(node.node, **new_kwargs)
 | 
						|
                self.output.write(")")
 | 
						|
 | 
						|
    def _process_filter_abs(self, node, **kwargs):
 | 
						|
        with self._interpolation():
 | 
						|
            with self._python_bool_wrapper(**kwargs) as new_kwargs:
 | 
						|
                self.output.write("Math.abs(")
 | 
						|
                self._process_node(node.node, **new_kwargs)
 | 
						|
                self.output.write(")")
 | 
						|
 | 
						|
    def _process_filter_replace(self, node, **kwargs):
 | 
						|
        # We're getting a quoted string from Python/Jinja as the pattern to
 | 
						|
        # replace, but to replace all occurrences in JS, we typically need a
 | 
						|
        # regex, which would be annoying to convert. So we're using split/join
 | 
						|
        # instead here.
 | 
						|
        with self._interpolation():
 | 
						|
            with self._python_bool_wrapper(**kwargs) as new_kwargs:
 | 
						|
                self._process_node(node.node, **new_kwargs)
 | 
						|
                self.output.write(".split(")
 | 
						|
                self._process_node(node.args[0], **new_kwargs)
 | 
						|
                self.output.write(").join(")
 | 
						|
                self._process_node(node.args[1], **new_kwargs)
 | 
						|
                self.output.write(")")
 | 
						|
 | 
						|
    def _process_filter_pprint(self, node, **kwargs):
 | 
						|
        with self._interpolation():
 | 
						|
            with self._python_bool_wrapper(**kwargs) as new_kwargs:
 | 
						|
                self.output.write("JSON.stringify(")
 | 
						|
                self._process_node(node.node, **new_kwargs)
 | 
						|
                self.output.write(")")
 | 
						|
 | 
						|
    def _process_filter_attr(self, node, **kwargs):
 | 
						|
        with self._interpolation():
 | 
						|
            with self._python_bool_wrapper(**kwargs) as new_kwargs:
 | 
						|
                self._process_node(node.node, **new_kwargs)
 | 
						|
                self.output.write("[")
 | 
						|
                self._process_node(node.args[0], **new_kwargs)
 | 
						|
                self.output.write("]")
 | 
						|
 | 
						|
    def _process_filter_batch(self, node, **kwargs):
 | 
						|
        with self._interpolation():
 | 
						|
            with self._python_bool_wrapper(**kwargs) as new_kwargs:
 | 
						|
                self.output.write("__filters.batch(")
 | 
						|
                self._process_node(node.node, **new_kwargs)
 | 
						|
                self.output.write(",")
 | 
						|
                self._process_args(node, **new_kwargs)
 | 
						|
                self.output.write(")")
 | 
						|
 | 
						|
    def _process_filter_default(self, node, **kwargs):
 | 
						|
        with self._interpolation():
 | 
						|
            with self._python_bool_wrapper(**kwargs) as new_kwargs:
 | 
						|
                self.output.write("__filters.default(")
 | 
						|
                self._process_node(node.node, **new_kwargs)
 | 
						|
                if node.args:
 | 
						|
                    self.output.write(",")
 | 
						|
                self._process_args(node, **new_kwargs)
 | 
						|
                self.output.write(")")
 | 
						|
 | 
						|
    def _process_filter_first(self, node, **kwargs):
 | 
						|
        with self._interpolation():
 | 
						|
            with self._python_bool_wrapper(**kwargs) as new_kwargs:
 | 
						|
                self.output.write("__filters.first(")
 | 
						|
                self._process_node(node.node, **new_kwargs)
 | 
						|
                self.output.write(")")
 | 
						|
 | 
						|
    def _process_filter_int(self, node, **kwargs):
 | 
						|
        with self._interpolation():
 | 
						|
            with self._python_bool_wrapper(**kwargs) as new_kwargs:
 | 
						|
                self.output.write("__filters.int(")
 | 
						|
                self._process_node(node.node, **new_kwargs)
 | 
						|
                if node.args:
 | 
						|
                    self.output.write(",")
 | 
						|
                self._process_args(node, **new_kwargs)
 | 
						|
                self.output.write(")")
 | 
						|
 | 
						|
    def _process_filter_round(self, node, **kwargs):
 | 
						|
        with self._interpolation():
 | 
						|
            with self._python_bool_wrapper(**kwargs) as new_kwargs:
 | 
						|
                self.output.write("Math.round((")
 | 
						|
                self._process_node(node.node, **new_kwargs)
 | 
						|
                self.output.write("+ Number.EPSILON) * 10**")
 | 
						|
                self._process_node(node.args[0], **new_kwargs)
 | 
						|
                self.output.write(") / 10**")
 | 
						|
                self._process_node(node.args[0], **new_kwargs)
 | 
						|
 | 
						|
    def _process_filter_last(self, node, **kwargs):
 | 
						|
        with self._interpolation():
 | 
						|
            with self._python_bool_wrapper(**kwargs) as new_kwargs:
 | 
						|
                self.output.write("__filters.last(")
 | 
						|
                self._process_node(node.node, **new_kwargs)
 | 
						|
                self.output.write(")")
 | 
						|
 | 
						|
    def _process_filter_length(self, node, **kwargs):
 | 
						|
        with self._interpolation():
 | 
						|
            with self._python_bool_wrapper(**kwargs) as new_kwargs:
 | 
						|
                self.output.write("__filters.size(")
 | 
						|
                self._process_node(node.node, **new_kwargs)
 | 
						|
                self.output.write(")")
 | 
						|
 | 
						|
    def _process_filter_lower(self, node, **kwargs):
 | 
						|
        with self._interpolation():
 | 
						|
            with self._python_bool_wrapper(**kwargs) as new_kwargs:
 | 
						|
                self.output.write("(")
 | 
						|
                self._process_node(node.node, **new_kwargs)
 | 
						|
                self.output.write(' + "").toLowerCase()')
 | 
						|
 | 
						|
    def _process_filter_slice(self, node, **kwargs):
 | 
						|
        with self._interpolation():
 | 
						|
            with self._python_bool_wrapper(**kwargs) as new_kwargs:
 | 
						|
                self.output.write("__filters.slice(")
 | 
						|
                self._process_node(node.node, **new_kwargs)
 | 
						|
                self.output.write(",")
 | 
						|
                self._process_args(node, **new_kwargs)
 | 
						|
                self.output.write(")")
 | 
						|
 | 
						|
    def _process_filter_title(self, node, **kwargs):
 | 
						|
        with self._interpolation():
 | 
						|
            with self._python_bool_wrapper(**kwargs) as new_kwargs:
 | 
						|
                self.output.write("__filters.title(")
 | 
						|
                self._process_node(node.node, **new_kwargs)
 | 
						|
                self.output.write(")")
 | 
						|
 | 
						|
    def _process_filter_trim(self, node, **kwargs):
 | 
						|
        with self._interpolation():
 | 
						|
            with self._python_bool_wrapper(**kwargs) as new_kwargs:
 | 
						|
                self.output.write("(")
 | 
						|
                self._process_node(node.node, **new_kwargs)
 | 
						|
                self.output.write(' + "").trim()')
 | 
						|
 | 
						|
    def _process_filter_upper(self, node, **kwargs):
 | 
						|
        with self._interpolation():
 | 
						|
            with self._python_bool_wrapper(**kwargs) as new_kwargs:
 | 
						|
                self.output.write("(")
 | 
						|
                self._process_node(node.node, **new_kwargs)
 | 
						|
                self.output.write(' + "").toUpperCase()')
 | 
						|
 | 
						|
    def _process_filter_truncate(self, node, **kwargs):
 | 
						|
        with self._interpolation():
 | 
						|
            with self._python_bool_wrapper(**kwargs) as new_kwargs:
 | 
						|
                self.output.write("__filters.truncate(")
 | 
						|
                self._process_node(node.node, **new_kwargs)
 | 
						|
                self.output.write(",")
 | 
						|
                self._process_args(node, **new_kwargs)
 | 
						|
                self.output.write(")")
 | 
						|
 | 
						|
    def _process_assign(self, node, **kwargs):
 | 
						|
        with self._execution():
 | 
						|
            self.output.write("var ")
 | 
						|
            self._process_node(node.target, **kwargs)
 | 
						|
            self.output.write(" = ")
 | 
						|
            self._process_node(node.node, **kwargs)
 | 
						|
            self.output.write(";")
 | 
						|
 | 
						|
    def _process_with(self, node, **kwargs):
 | 
						|
 | 
						|
        # keep a copy of the stored names before the scope
 | 
						|
        previous_stored_names = self.stored_names.copy()
 | 
						|
 | 
						|
        # assigns in the with tag
 | 
						|
        # e.g. {% with var = "something %}
 | 
						|
        assigns_in_tag = [nodes.Assign(t, v) for t, v in zip(node.targets, node.values)]
 | 
						|
 | 
						|
        # assigns in the with body
 | 
						|
        # e.g. {% set name = 'John' %}
 | 
						|
        assigns_in_body = [x for x in node.body if isinstance(x, nodes.Assign)]
 | 
						|
 | 
						|
        # remove assigns from the body
 | 
						|
        node.body = [x for x in node.body if not isinstance(x, nodes.Assign)]
 | 
						|
 | 
						|
        # get a list of all the assigns in this with block
 | 
						|
        # both on the tag, and within the body of the block
 | 
						|
        all_assigns = assigns_in_tag + assigns_in_body
 | 
						|
 | 
						|
        with self._execution():
 | 
						|
            self.output.write("(function () {")
 | 
						|
 | 
						|
        with self._scoped_variables(all_assigns, **kwargs):
 | 
						|
            for node in node.body:
 | 
						|
                self._process_node(node, **kwargs)
 | 
						|
 | 
						|
        with self._execution():
 | 
						|
            self.output.write("})();")
 | 
						|
 | 
						|
        # restore previous stored names
 | 
						|
        self.stored_names = previous_stored_names
 | 
						|
 | 
						|
    def _process_compare(self, node, **kwargs):
 | 
						|
 | 
						|
        if len(node.ops) > 1:
 | 
						|
            raise Exception("Multiple operands are not supported.")
 | 
						|
 | 
						|
        operand = node.ops[0]
 | 
						|
        is_equality = operand.op in ("eq", "ne")
 | 
						|
        left_hand_is_const = isinstance(node.expr, nodes.Const)
 | 
						|
        right_hand_is_const = isinstance(operand.expr, nodes.Const)
 | 
						|
 | 
						|
        # If the operand is equality and neither the left or right hand side are constants then we
 | 
						|
        # will need to use the JavaScript deep equals function. Ideally we want to avoid using this
 | 
						|
        # as it is quite a big function.
 | 
						|
        use_is_equal_function = is_equality and not (
 | 
						|
            left_hand_is_const or right_hand_is_const
 | 
						|
        )
 | 
						|
 | 
						|
        with option(kwargs, use_python_bool_wrapper=False):
 | 
						|
            if operand.op == "in" or operand.op == "notin":
 | 
						|
                # Special case for "in" operator
 | 
						|
                if operand.op == "notin":
 | 
						|
                    self.output.write("!")
 | 
						|
                self._process_node(operand.expr, **kwargs)
 | 
						|
                self.output.write(".includes(")
 | 
						|
                self._process_node(node.expr, **kwargs)
 | 
						|
                self.output.write(")")
 | 
						|
            else:
 | 
						|
                if use_is_equal_function:
 | 
						|
                    if operand.op == "ne":
 | 
						|
                        self.output.write("!")
 | 
						|
                    self.output.write("__runtime.isEqual(")
 | 
						|
 | 
						|
                self._process_node(node.expr, **kwargs)
 | 
						|
 | 
						|
                if use_is_equal_function:
 | 
						|
                    self.output.write(",")
 | 
						|
                else:
 | 
						|
                    self.output.write(OPERANDS.get(operand.op))
 | 
						|
 | 
						|
                self._process_node(operand.expr, **kwargs)
 | 
						|
 | 
						|
                if use_is_equal_function:
 | 
						|
                    self.output.write(")")
 | 
						|
 | 
						|
    def _process_operand(self, node, **kwargs):
 | 
						|
        self.output.write(OPERANDS.get(node.op))
 | 
						|
        self._process_node(node.expr, **kwargs)
 | 
						|
 | 
						|
    def _process_const(self, node, **_):
 | 
						|
        with self._interpolation():
 | 
						|
            self.output.write(json.dumps(node.value))
 | 
						|
 | 
						|
    def _process_nonetype(self, node, **_):
 | 
						|
        with self._interpolation():
 | 
						|
            self.output.write("null")
 | 
						|
 | 
						|
    def _process_neg(self, node, **kwargs):
 | 
						|
        with self._interpolation():
 | 
						|
            self.output.write("-")
 | 
						|
            self._process_node(node.node, **kwargs)
 | 
						|
 | 
						|
    def _process_list(self, node, **kwargs):
 | 
						|
        self.output.write("[")
 | 
						|
        for i, item in enumerate(node.items):
 | 
						|
            self._process_node(item, **kwargs)
 | 
						|
            if i < len(node.items) - 1:
 | 
						|
                self.output.write(",")
 | 
						|
        self.output.write("]")
 | 
						|
 | 
						|
    def _process_test(self, node, **kwargs):
 | 
						|
        with option(kwargs, use_python_bool_wrapper=False):
 | 
						|
            method_name = getattr(self, "_process_test_%s" % node.name, None)
 | 
						|
            if callable(method_name):
 | 
						|
                method_name(node, **kwargs)
 | 
						|
            else:
 | 
						|
                raise Exception("Unsupported test: %s" % node.name)
 | 
						|
 | 
						|
    def _process_test_defined(self, node, **kwargs):
 | 
						|
        self.output.write("(typeof ")
 | 
						|
        self._process_node(node.node, **kwargs)
 | 
						|
        self.output.write(' !== "undefined")')
 | 
						|
 | 
						|
    def _process_test_undefined(self, node, **kwargs):
 | 
						|
        self._process_node(node.node, **kwargs)
 | 
						|
        self.output.write(" === undefined")
 | 
						|
 | 
						|
    def _process_test_callable(self, node, **kwargs):
 | 
						|
        self.output.write("__runtime.type(")
 | 
						|
        self._process_node(node.node, **kwargs)
 | 
						|
        self.output.write(') === "Function"')
 | 
						|
 | 
						|
    def _process_test_divisibleby(self, node, **kwargs):
 | 
						|
        self._process_node(node.node, **kwargs)
 | 
						|
        self.output.write(" % ")
 | 
						|
        self._process_node(node.args[0], **kwargs)
 | 
						|
        self.output.write(" === 0")
 | 
						|
 | 
						|
    def _process_test_even(self, node, **kwargs):
 | 
						|
        self._process_node(node.node, **kwargs)
 | 
						|
        self.output.write(" % 2 === 0")
 | 
						|
 | 
						|
    def _process_test_odd(self, node, **kwargs):
 | 
						|
        self._process_node(node.node, **kwargs)
 | 
						|
        self.output.write(" % 2 === 1")
 | 
						|
 | 
						|
    def _process_test_none(self, node, **kwargs):
 | 
						|
        self._process_node(node.node, **kwargs)
 | 
						|
        self.output.write(" === null")
 | 
						|
 | 
						|
    def _process_test_upper(self, node, **kwargs):
 | 
						|
        self._process_node(node.node, **kwargs)
 | 
						|
        self.output.write(".toUpperCase() === ")
 | 
						|
        self._process_node(node.node, **kwargs)
 | 
						|
 | 
						|
    def _process_test_lower(self, node, **kwargs):
 | 
						|
        self._process_node(node.node, **kwargs)
 | 
						|
        self.output.write(".toLowerCase() === ")
 | 
						|
        self._process_node(node.node, **kwargs)
 | 
						|
 | 
						|
    def _process_test_string(self, node, **kwargs):
 | 
						|
        self.output.write("__runtime.type(")
 | 
						|
        self._process_node(node.node, **kwargs)
 | 
						|
        self.output.write(') === "String"')
 | 
						|
 | 
						|
    def _process_test_mapping(self, node, **kwargs):
 | 
						|
        self.output.write("__runtime.type(")
 | 
						|
        self._process_node(node.node, **kwargs)
 | 
						|
        self.output.write(') === "Object"')
 | 
						|
 | 
						|
    def _process_test_number(self, node, **kwargs):
 | 
						|
        self.output.write("(__runtime.type(")
 | 
						|
        self._process_node(node.node, **kwargs)
 | 
						|
        self.output.write(') === "Number" && !isNaN(')
 | 
						|
        self._process_node(node.node, **kwargs)
 | 
						|
        self.output.write("))")
 | 
						|
 | 
						|
    def _process_include(self, node, **kwargs):
 | 
						|
        with self._interpolation(safe=True):
 | 
						|
            include_path = node.template.value
 | 
						|
 | 
						|
            if include_path == self.template_name:
 | 
						|
                # template is including itself
 | 
						|
                include_var_name = self.js_function_name
 | 
						|
            else:
 | 
						|
                if self.include_prefix:
 | 
						|
                    include_path = self.include_prefix + node.template.value
 | 
						|
                elif (
 | 
						|
                    self.js_module_format in ("es6", "commonjs",) and self.template_name
 | 
						|
                ):
 | 
						|
                    _, absolute_include_path, _ = self.environment.loader.get_source(
 | 
						|
                        self.environment, node.template.value
 | 
						|
                    )
 | 
						|
                    include_path = os.path.relpath(
 | 
						|
                        absolute_include_path, os.path.dirname(self.template_path)
 | 
						|
                    )
 | 
						|
                    if not include_path.startswith("."):
 | 
						|
                        include_path = "./" + include_path
 | 
						|
 | 
						|
                # Jinja2 doesn't accept Windows filepaths (but does output them!)
 | 
						|
                if os.name == "nt":
 | 
						|
                    include_path = include_path.replace(os.pathsep, "/")
 | 
						|
 | 
						|
                include_path = path.splitext(include_path)[0] + self.include_ext
 | 
						|
                include_var_name = self._get_depencency_var_name(include_path)
 | 
						|
 | 
						|
                if not include_var_name:
 | 
						|
                    include_var_name = self._add_dependency(include_path)
 | 
						|
 | 
						|
            if self.js_module_format is None:
 | 
						|
                self.output.write('jinjaToJS.include("')
 | 
						|
                self.output.write(include_path)
 | 
						|
                self.output.write('");')
 | 
						|
            else:
 | 
						|
                self.output.write(include_var_name)
 | 
						|
 | 
						|
            self.output.write("(")
 | 
						|
            self.output.write(self.context_name)
 | 
						|
            self.output.write(")")
 | 
						|
 | 
						|
    def _process_add(self, node, **kwargs):
 | 
						|
        # Handle + operator for lists, which behaves differently in JS. Currently
 | 
						|
        # only works if we have an explicit list node on either side (in which
 | 
						|
        # case we assume both are lists).
 | 
						|
        if isinstance(node.left, nodes.List) or isinstance(node.right, nodes.List):
 | 
						|
            with self._interpolation():
 | 
						|
                with self._python_bool_wrapper(**kwargs) as new_kwargs:
 | 
						|
                    self._process_node(node.left, **new_kwargs)
 | 
						|
                    self.output.write(".concat(")
 | 
						|
                    self._process_node(node.right, **new_kwargs)
 | 
						|
                    self.output.write(")")
 | 
						|
        else:
 | 
						|
            self._process_math(node, math_operator=" + ", **kwargs)
 | 
						|
 | 
						|
    def _process_sub(self, node, **kwargs):
 | 
						|
        self._process_math(node, math_operator=" - ", **kwargs)
 | 
						|
 | 
						|
    def _process_div(self, node, **kwargs):
 | 
						|
        self._process_math(node, math_operator=" / ", **kwargs)
 | 
						|
 | 
						|
    def _process_floordiv(self, node, **kwargs):
 | 
						|
        self._process_math(node, math_operator=" / ", function="Math.floor", **kwargs)
 | 
						|
 | 
						|
    def _process_mul(self, node, **kwargs):
 | 
						|
        self._process_math(node, math_operator=" * ", **kwargs)
 | 
						|
 | 
						|
    def _process_mod(self, node, **kwargs):
 | 
						|
        self._process_math(node, math_operator=" % ", **kwargs)
 | 
						|
 | 
						|
    def _process_math(self, node, math_operator=None, function=None, **kwargs):
 | 
						|
        """
 | 
						|
        Processes a math node e.g. `Div`, `Sub`, `Add`, `Mul` etc...
 | 
						|
        If `function` is provided the expression is wrapped in a call to that function.
 | 
						|
        """
 | 
						|
 | 
						|
        with self._interpolation():
 | 
						|
            if function:
 | 
						|
                self.output.write(function)
 | 
						|
                self.output.write("(")
 | 
						|
 | 
						|
            self._process_node(node.left, **kwargs)
 | 
						|
            self.output.write(math_operator)
 | 
						|
            self._process_node(node.right, **kwargs)
 | 
						|
 | 
						|
            if function:
 | 
						|
                self.output.write(")")
 | 
						|
 | 
						|
    def _process_loop_helper(self, node, **kwargs):
 | 
						|
        """
 | 
						|
        Processes a loop helper e.g. {{ loop.first }} or {{ loop.index }}
 | 
						|
        """
 | 
						|
 | 
						|
        if node.attr == LOOP_HELPER_INDEX:
 | 
						|
            self.output.write("(arguments[1] + 1)")
 | 
						|
        elif node.attr == LOOP_HELPER_INDEX_0:
 | 
						|
            self.output.write("arguments[1]")
 | 
						|
        elif node.attr == LOOP_HELPER_FIRST:
 | 
						|
            self.output.write("(arguments[1] == 0)")
 | 
						|
        elif node.attr == LOOP_HELPER_LAST:
 | 
						|
            self.output.write("(arguments[1] == arguments[2].length - 1)")
 | 
						|
        elif node.attr == LOOP_HELPER_LENGTH:
 | 
						|
            self.output.write("arguments[2].length")
 | 
						|
 | 
						|
    def _process_args(self, node, **kwargs):
 | 
						|
        args = getattr(node, "args", None)
 | 
						|
        if not args:
 | 
						|
            return
 | 
						|
        for i, item in enumerate(args):
 | 
						|
            self._process_node(item, **kwargs)
 | 
						|
            if i < len(node.args) - 1:
 | 
						|
                self.output.write(",")
 | 
						|
 | 
						|
    @contextlib.contextmanager
 | 
						|
    def _execution(self):
 | 
						|
        """
 | 
						|
        Context manager for executing some JavaScript inside a template.
 | 
						|
        """
 | 
						|
 | 
						|
        did_start_executing = False
 | 
						|
 | 
						|
        if self.state == STATE_DEFAULT:
 | 
						|
            did_start_executing = True
 | 
						|
            self.state = STATE_EXECUTING
 | 
						|
 | 
						|
        def close():
 | 
						|
            if did_start_executing and self.state == STATE_EXECUTING:
 | 
						|
                self.state = STATE_DEFAULT
 | 
						|
 | 
						|
        yield close
 | 
						|
        close()
 | 
						|
 | 
						|
    @contextlib.contextmanager
 | 
						|
    def _interpolation(self, safe=False):
 | 
						|
 | 
						|
        did_start_interpolating = False
 | 
						|
 | 
						|
        if self.state == STATE_DEFAULT:
 | 
						|
            did_start_interpolating = True
 | 
						|
            self.output.write('__result += "" + ')
 | 
						|
            if safe is not True:
 | 
						|
                self.output.write("__runtime.escape")
 | 
						|
            self.output.write("((__tmp = (")
 | 
						|
            self.state = STATE_INTERPOLATING
 | 
						|
 | 
						|
        def close():
 | 
						|
            if did_start_interpolating and self.state == STATE_INTERPOLATING:
 | 
						|
                self.output.write(')) == null ? "" : __tmp);')
 | 
						|
                self.state = STATE_DEFAULT
 | 
						|
 | 
						|
        yield close
 | 
						|
        close()
 | 
						|
 | 
						|
    @contextlib.contextmanager
 | 
						|
    def _scoped_variables(self, nodes_list, **kwargs):
 | 
						|
        """
 | 
						|
        Context manager for creating scoped variables defined by the nodes in `nodes_list`.
 | 
						|
        These variables will be added to the context, and when the context manager exits the
 | 
						|
        context object will be restored to it's previous state.
 | 
						|
        """
 | 
						|
 | 
						|
        tmp_vars = []
 | 
						|
        for node in nodes_list:
 | 
						|
 | 
						|
            is_assign_node = isinstance(node, nodes.Assign)
 | 
						|
            name = node.target.name if is_assign_node else node.name
 | 
						|
 | 
						|
            # create a temp variable name
 | 
						|
            tmp_var = next(self.temp_var_names)
 | 
						|
 | 
						|
            # save previous context value
 | 
						|
            with self._execution():
 | 
						|
 | 
						|
                # save the current value of this name
 | 
						|
                self.output.write(
 | 
						|
                    "var %s = %s.%s;" % (tmp_var, self.context_name, name)
 | 
						|
                )
 | 
						|
 | 
						|
                # add new value to context
 | 
						|
                self.output.write("%s.%s = " % (self.context_name, name))
 | 
						|
 | 
						|
                if is_assign_node:
 | 
						|
                    self._process_node(node.node, **kwargs)
 | 
						|
                else:
 | 
						|
                    self.output.write(node.name)
 | 
						|
 | 
						|
                self.output.write(";")
 | 
						|
 | 
						|
            tmp_vars.append((tmp_var, name))
 | 
						|
 | 
						|
        yield
 | 
						|
 | 
						|
        # restore context
 | 
						|
        for tmp_var, name in tmp_vars:
 | 
						|
            with self._execution():
 | 
						|
                self.output.write("%s.%s = %s;" % (self.context_name, name, tmp_var))
 | 
						|
 | 
						|
    @contextlib.contextmanager
 | 
						|
    def _python_bool_wrapper(self, **kwargs):
 | 
						|
 | 
						|
        use_python_bool_wrapper = kwargs.get("use_python_bool_wrapper")
 | 
						|
 | 
						|
        if use_python_bool_wrapper:
 | 
						|
            self.output.write("__runtime.boolean(")
 | 
						|
 | 
						|
        with option(kwargs, use_python_bool_wrapper=False):
 | 
						|
            yield kwargs
 | 
						|
 | 
						|
        if use_python_bool_wrapper:
 | 
						|
            self.output.write(")")
 | 
						|
 | 
						|
 | 
						|
def main(template_path, output=None, data_path=None):
 | 
						|
    """Convert a jinja2 template to a JavaScript module.
 | 
						|
 | 
						|
    template_path (Path): Path to .jijna file.
 | 
						|
    output (Optional[Path]): Path to output .js module (stdout if unset).
 | 
						|
    data_path (Optional[Path]): Optional JSON or YAML file with additional data
 | 
						|
        to be included in the JS module as the exported variable DATA.
 | 
						|
    """
 | 
						|
    data = "{}"
 | 
						|
    if data_path is not None:
 | 
						|
        if data_path.suffix in (".yml", ".yaml"):
 | 
						|
            data = srsly.read_yaml(data_path)
 | 
						|
        else:
 | 
						|
            data = srsly.read_json(data_path)
 | 
						|
        data = srsly.json_dumps(data)  # dump and load for compactness
 | 
						|
    template_path = Path(template_path)
 | 
						|
    tpl_file = template_path.parts[-1]
 | 
						|
    compiler = JinjaToJS(template_path.parent, tpl_file, js_module_format="es6")
 | 
						|
    header = f"// This file was auto-generated by {__file__} based on {tpl_file}"
 | 
						|
    data_str = f"export const DATA = {data}"
 | 
						|
    result = compiler.get_output()
 | 
						|
    if output is not None:
 | 
						|
        with output.open("w", encoding="utf8") as f:
 | 
						|
            f.write(f"{header}\n{result}\n{data_str}")
 | 
						|
        print(f"Updated {output.parts[-1]}")
 | 
						|
    else:
 | 
						|
        print(result)
 | 
						|
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    args = sys.argv[1:]
 | 
						|
    if not len(args):
 | 
						|
        raise ValueError("Need at least one argument: path to .jinja template")
 | 
						|
    template_path = Path(args[0])
 | 
						|
    output = Path(args[1]) if len(args) > 1 else None
 | 
						|
    data_path = Path(args[2]) if len(args) > 2 else None
 | 
						|
    main(template_path, output, data_path)
 |