def render_substitution(self, token, inline: bool):
        """Substitutions are rendered by:

        1. Combining global substitutions with front-matter substitutions
           to create a variable context (front-matter takes priority)
        2. Add the sphinx `env` to the variable context (if available)
        3. Create the string content with Jinja2 (passing it the variable context)
        4. If the substitution is inline and not a directive,
           parse to nodes ignoring block syntaxes (like lists or block-quotes),
           otherwise parse to nodes with all syntax rules.

        """
        position = token.map[0]

        # front-matter substitutions take priority over config ones
        variable_context = {
            **self.config.get("myst_substitutions", {}),
            **getattr(self.document, "fm_substitutions", {}),
        }
        try:
            variable_context["env"] = self.document.settings.env
        except AttributeError:
            pass  # if not sphinx renderer

        # fail on undefined variables
        env = jinja2.Environment(undefined=jinja2.StrictUndefined)

        # try rendering
        try:
            rendered = env.from_string(f"{{{{{token.content}}}}}").render(
                variable_context)
        except Exception as error:
            error_msg = self.reporter.error(
                f"Substitution error:{error.__class__.__name__}: {error}",
                line=position,
            )
            self.current_node += [error_msg]
            return

        # handle circular references
        ast = env.parse(f"{{{{{token.content}}}}}")
        references = {
            n.name
            for n in ast.find_all(jinja2.nodes.Name) if n.name != "env"
        }
        self.document.sub_references = getattr(self.document, "sub_references",
                                               set())
        cyclic = references.intersection(self.document.sub_references)
        if cyclic:
            error_msg = self.reporter.error(
                f"circular substitution reference: {cyclic}",
                line=position,
            )
            self.current_node += [error_msg]
            return

        # parse rendered text
        state_machine = MockStateMachine(self, position)
        state = MockState(self, state_machine, position)

        # TODO improve error reporting;
        # at present, for a multi-line substitution,
        # an error may point to a line lower than the substitution
        # should it point to the source of the substitution?
        # or the error message should at least indicate that its a substitution

        # we record used references before nested parsing, then remove them after
        self.document.sub_references.update(references)

        try:
            if inline and not REGEX_DIRECTIVE_START.match(rendered):
                sub_nodes, _ = state.inline_text(rendered, position)
            else:
                base_node = nodes.Element()
                state.nested_parse(
                    StringList(rendered.splitlines(), self.document["source"]),
                    0,
                    base_node,
                )
                sub_nodes = base_node.children
        finally:
            self.document.sub_references.difference_update(references)

        self.current_node.extend(sub_nodes)
示例#2
0
    def render_substitution(self, token, inline: bool):

        import jinja2

        # TODO token.map was None when substituting an image in a table
        position = token.map[0] if token.map else 9999

        # front-matter substitutions take priority over config ones
        context = {
            **self.config.get("substitutions", {}),
            **getattr(self.document, "fm_substitutions", {}),
        }
        try:
            context["env"] = self.document.settings.env
        except AttributeError:
            pass  # if not sphinx renderer

        # fail on undefined variables
        env = jinja2.Environment(undefined=jinja2.StrictUndefined)

        # try rendering
        try:
            rendered = env.from_string(f"{{{{{token.content}}}}}").render(
                context)
        except Exception as error:
            error = self.reporter.error(
                f"Substitution error:{error.__class__.__name__}: {error}",
                line=position,
            )
            self.current_node += [error]
            return

        # handle circular references
        ast = env.parse(f"{{{{{token.content}}}}}")
        references = {
            n.name
            for n in ast.find_all(jinja2.nodes.Name) if n.name != "env"
        }
        self.document.sub_references = getattr(self.document, "sub_references",
                                               set())
        cyclic = references.intersection(self.document.sub_references)
        if cyclic:
            error = self.reporter.error(
                f"circular substitution reference: {cyclic}",
                line=position,
            )
            self.current_node += [error]
            return

        # parse rendered text
        state_machine = MockStateMachine(self, position)
        state = MockState(self, state_machine, position)
        # TODO improve error reporting;
        # at present, for a multi-line substitution,
        # an error may point to a line lower than the substitution
        # should it point to the source of the substitution?
        # or the error message should at least indicate that its a substitution
        self.document.sub_references.update(references)
        base_node = nodes.Element()
        try:
            state.nested_parse(
                StringList(rendered.splitlines(), self.document["source"]),
                0,
                base_node,
            )
        finally:
            self.document.sub_references.difference_update(references)

        sub_nodes = base_node.children
        if (inline and len(base_node.children) == 1
                and isinstance(base_node.children[0], nodes.paragraph)):
            # just add the contents of the paragraph
            sub_nodes = base_node.children[0].children

        # TODO add more checking for inline compatibility?

        self.current_node.extend(sub_nodes)