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)
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)