Skip to content

Commit

Permalink
馃敡 MAINTAIN: Replace DocutilsRenderer.config with md_config
Browse files Browse the repository at this point in the history
It makes it clearer what the attribute is,
and allows for type checking / autocompletion
  • Loading branch information
chrisjsewell committed Jan 9, 2022
1 parent ffdd3ad commit f5f0b40
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 105 deletions.
23 changes: 15 additions & 8 deletions myst_parser/directives.py
@@ -1,11 +1,14 @@
"""MyST specific directives"""
from typing import List, Tuple
from copy import copy
from typing import List, Tuple, cast

from docutils import nodes
from docutils.parsers.rst import directives
from sphinx.directives import SphinxDirective
from sphinx.util.docutils import SphinxRole

from myst_parser.mocking import MockState


def align(argument):
return directives.choice(argument, ("left", "center", "right"))
Expand Down Expand Up @@ -61,16 +64,20 @@ def run(self) -> List[nodes.Node]:
figclasses = self.options.pop("class", None)
align = self.options.pop("align", None)

node = nodes.Element()
# TODO test that we are using myst parser
if not isinstance(self.state, MockState):
return [self.figure_error("Directive is only supported in myst parser")]
state = cast(MockState, self.state)

# ensure html image enabled
myst_extensions = self.state._renderer.config.get("myst_extensions", set())
myst_extensions = copy(state._renderer.md_config.enable_extensions)
node = nodes.Element()
try:
self.state._renderer.config.setdefault("myst_extensions", set())
self.state._renderer.config["myst_extensions"].add("html_image")
self.state.nested_parse(self.content, self.content_offset, node)
state._renderer.md_config.enable_extensions = list(
state._renderer.md_config.enable_extensions
) + ["html_image"]
state.nested_parse(self.content, self.content_offset, node)
finally:
self.state._renderer.config["myst_extensions"] = myst_extensions
state._renderer.md_config.enable_extensions = myst_extensions

if not len(node.children) == 2:
return [
Expand Down
52 changes: 26 additions & 26 deletions myst_parser/docutils_renderer.py
Expand Up @@ -8,6 +8,7 @@
from datetime import date, datetime
from types import ModuleType
from typing import (
TYPE_CHECKING,
Any,
Dict,
Iterator,
Expand Down Expand Up @@ -42,6 +43,7 @@
from markdown_it.token import Token
from markdown_it.tree import SyntaxTreeNode

from myst_parser.main import MdParserConfig
from myst_parser.mocking import (
MockIncludeDirective,
MockingError,
Expand All @@ -54,6 +56,9 @@
from .parse_directives import DirectiveParsingError, parse_directive_text
from .utils import is_external_url

if TYPE_CHECKING:
from sphinx.environment import BuildEnvironment


def make_document(source_path="notset", parser_cls=RSTParser) -> nodes.document:
"""Create a new docutils document, with the parser classes' default settings."""
Expand Down Expand Up @@ -94,7 +99,8 @@ def __getattr__(self, name: str):
"""Warn when the renderer has not been setup yet."""
if name in (
"md_env",
"config",
"md_config",
"md_options",
"document",
"current_node",
"reporter",
Expand All @@ -113,11 +119,10 @@ def setup_render(
) -> None:
"""Setup the renderer with per render variables."""
self.md_env = env
self.config: Dict[str, Any] = options
self.document: nodes.document = self.config.get("document", make_document())
self.current_node: nodes.Element = self.config.get(
"current_node", self.document
)
self.md_options = options
self.md_config: MdParserConfig = options["myst_config"]
self.document: nodes.document = options.get("document", make_document())
self.current_node: nodes.Element = options.get("current_node", self.document)
self.reporter: Reporter = self.document.reporter
# note there are actually two possible language modules:
# one from docutils.languages, and one from docutils.parsers.rst.languages
Expand All @@ -130,7 +135,7 @@ def setup_render(
}

@property
def sphinx_env(self) -> Optional[Any]:
def sphinx_env(self) -> Optional["BuildEnvironment"]:
"""Return the sphinx env, if using Sphinx."""
try:
return self.document.settings.env
Expand Down Expand Up @@ -221,9 +226,6 @@ def render(
append_to=self.document,
)

if not self.config.get("output_footnotes", True):
return self.document

# we don't use the foot_references stored in the env
# since references within directives/roles will have been added after
# those from the initial markdown parse
Expand All @@ -233,7 +235,7 @@ def render(
if refnode["refname"] not in foot_refs:
foot_refs[refnode["refname"]] = True

if foot_refs and self.config.get("myst_footnote_transition", False):
if foot_refs and self.md_config.footnote_transition:
self.current_node.append(nodes.transition(classes=["footnotes"]))
for footref in foot_refs:
foot_ref_tokens = self.md_env["foot_refs"].get(footref, [])
Expand Down Expand Up @@ -522,9 +524,7 @@ def create_highlighted_code_block(
lex_tokens = Lexer(
text,
lexer_name or "",
"short"
if self.config.get("myst_highlight_code_blocks", True)
else "none",
"short" if self.md_config.highlight_code_blocks else "none",
)
except LexerError as err:
self.reporter.warning(
Expand Down Expand Up @@ -571,7 +571,7 @@ def render_fence(self, token: SyntaxTreeNode) -> None:
info = token.info.strip() if token.info else token.info
language = info.split()[0] if info else ""

if not self.config.get("commonmark_only", False) and language == "{eval-rst}":
if not self.md_config.commonmark_only and language == "{eval-rst}":
# copy necessary elements (source, line no, env, reporter)
newdoc = make_document()
newdoc["source"] = self.document["source"]
Expand All @@ -587,7 +587,7 @@ def render_fence(self, token: SyntaxTreeNode) -> None:
self.current_node.extend(newdoc[:])
return
elif (
not self.config.get("commonmark_only", False)
not self.md_config.commonmark_only
and language.startswith("{")
and language.endswith("}")
):
Expand All @@ -603,7 +603,7 @@ def render_fence(self, token: SyntaxTreeNode) -> None:
node = self.create_highlighted_code_block(
text,
language,
number_lines=language in self.config.get("myst_number_code_blocks", ()),
number_lines=language in self.md_config.number_code_blocks,
source=self.document["source"],
line=token_line(token, 0) or None,
)
Expand All @@ -615,7 +615,7 @@ def blocks_mathjax_processing(self) -> bool:
return (
self.sphinx_env is not None
and "myst_update_mathjax" in self.sphinx_env.config
and self.sphinx_env.config.myst_update_mathjax
and self.md_config.update_mathjax
)

def render_heading(self, token: SyntaxTreeNode) -> None:
Expand Down Expand Up @@ -680,14 +680,14 @@ def render_link(self, token: SyntaxTreeNode) -> None:
if token.markup == "autolink":
return self.render_autolink(token)

if self.config.get("myst_all_links_external", False):
if self.md_config.all_links_external:
return self.render_external_url(token)

# Check for external URL
url_scheme = urlparse(cast(str, token.attrGet("href") or "")).scheme
allowed_url_schemes = self.config.get("myst_url_schemes", None)
allowed_url_schemes = self.md_config.url_schemes
if (allowed_url_schemes is None and url_scheme) or (
url_scheme in allowed_url_schemes
allowed_url_schemes is not None and url_scheme in allowed_url_schemes
):
return self.render_external_url(token)

Expand Down Expand Up @@ -750,14 +750,14 @@ def render_image(self, token: SyntaxTreeNode) -> None:
self.add_line_and_source_path(img_node, token)
destination = cast(str, token.attrGet("src") or "")

if self.config.get("relative-images", None) is not None and not is_external_url(
if self.md_env.get("relative-images", None) is not None and not is_external_url(
destination, None, True
):
# make the path relative to an "including" document
# this is set when using the `relative-images` option of the MyST `include` directive
destination = os.path.normpath(
os.path.join(
self.config.get("relative-images", ""),
self.md_env.get("relative-images", ""),
os.path.normpath(destination),
)
)
Expand Down Expand Up @@ -822,7 +822,7 @@ def render_front_matter(self, token: SyntaxTreeNode) -> None:
self.current_node.extend(
html_meta_to_nodes(
{
**self.config.get("myst_html_meta", {}),
**self.md_config.html_meta,
**html_meta,
},
document=self.document,
Expand Down Expand Up @@ -1287,8 +1287,8 @@ def render_substitution(self, token: SyntaxTreeNode, inline: bool) -> None:
position = token_line(token)

# front-matter substitutions take priority over config ones
variable_context = {
**self.config.get("myst_substitutions", {}),
variable_context: Dict[str, Any] = {
**self.md_config.substitutions,
**getattr(self.document, "fm_substitutions", {}),
}
if self.sphinx_env is not None:
Expand Down
6 changes: 2 additions & 4 deletions myst_parser/html_to_nodes.py
Expand Up @@ -35,10 +35,8 @@ def html_to_nodes(
text: str, line_number: int, renderer: "DocutilsRenderer"
) -> List[nodes.Element]:
"""Convert HTML to docutils nodes."""
enable_html_img = "html_image" in renderer.config.get("myst_extensions", [])
enable_html_admonition = "html_admonition" in renderer.config.get(
"myst_extensions", []
)
enable_html_img = "html_image" in renderer.md_config.enable_extensions
enable_html_admonition = "html_admonition" in renderer.md_config.enable_extensions

if not (enable_html_img or enable_html_admonition):
return default_html(text, renderer.document["source"], line_number)
Expand Down
19 changes: 3 additions & 16 deletions myst_parser/main.py
Expand Up @@ -40,7 +40,7 @@ class MdParserConfig:
validator=instance_of(bool),
metadata={"help": "Use strict CommonMark parser"},
)
enable_extensions: Iterable[str] = attr.ib(
enable_extensions: Sequence[str] = attr.ib(
factory=lambda: ["dollarmath"], metadata={"help": "Enable extensions"}
)

Expand Down Expand Up @@ -232,7 +232,7 @@ def create_md_parser(
md = MarkdownIt("commonmark", renderer_cls=renderer).use(
wordcount_plugin, per_minute=config.words_per_minute
)
md.options.update({"commonmark_only": True})
md.options.update({"myst_config": config})
return md

md = (
Expand Down Expand Up @@ -291,22 +291,9 @@ def create_md_parser(

md.options.update(
{
# standard options
"typographer": typographer,
"linkify": "linkify" in config.enable_extensions,
# myst options
"commonmark_only": False,
"myst_extensions": set(
list(config.enable_extensions)
+ (["heading_anchors"] if config.heading_anchors is not None else [])
),
"myst_all_links_external": config.all_links_external,
"myst_url_schemes": config.url_schemes,
"myst_substitutions": config.substitutions,
"myst_html_meta": config.html_meta,
"myst_footnote_transition": config.footnote_transition,
"myst_number_code_blocks": config.number_code_blocks,
"myst_highlight_code_blocks": config.highlight_code_blocks,
"myst_config": config,
}
)

Expand Down
8 changes: 4 additions & 4 deletions myst_parser/mocking.py
Expand Up @@ -441,11 +441,11 @@ def run(self) -> List[nodes.Element]:
self.renderer.reporter.source = str(path)
self.renderer.reporter.get_source_and_line = lambda l: (str(path), l)
if "relative-images" in self.options:
self.renderer.config["relative-images"] = os.path.relpath(
self.renderer.md_env["relative-images"] = os.path.relpath(
path.parent, source_dir
)
if "relative-docs" in self.options:
self.renderer.config["relative-docs"] = (
self.renderer.md_env["relative-docs"] = (
self.options["relative-docs"],
source_dir,
path.parent,
Expand All @@ -456,8 +456,8 @@ def run(self) -> List[nodes.Element]:
finally:
self.renderer.document["source"] = source
self.renderer.reporter.source = rsource
self.renderer.config.pop("relative-images", None)
self.renderer.config.pop("relative-docs", None)
self.renderer.md_env.pop("relative-images", None)
self.renderer.md_env.pop("relative-docs", None)
if line_func is not None:
self.renderer.reporter.get_source_and_line = line_func
else:
Expand Down
2 changes: 1 addition & 1 deletion myst_parser/sphinx_renderer.py
Expand Up @@ -74,7 +74,7 @@ def render_internal_link(self, token: SyntaxTreeNode) -> None:

# make the path relative to an "including" document
# this is set when using the `relative-docs` option of the MyST `include` directive
relative_include = self.config.get("relative-docs", None)
relative_include = self.md_env.get("relative-docs", None)
if relative_include is not None and destination.startswith(relative_include[0]):
source_dir, include_dir = relative_include[1:]
destination = os.path.relpath(
Expand Down
23 changes: 7 additions & 16 deletions tests/test_html/test_html_to_nodes.py
Expand Up @@ -3,9 +3,10 @@

import pytest
from docutils import nodes
from markdown_it.utils import read_fixture_file
from pytest_param_files import with_parameters

from myst_parser.html_to_nodes import html_to_nodes
from myst_parser.main import MdParserConfig

FIXTURE_PATH = Path(__file__).parent

Expand All @@ -18,7 +19,7 @@ def _run_directive(name: str, first_line: str, content: str, position: int):
return [node]

return Mock(
config={"myst_extensions": ["html_image", "html_admonition"]},
md_config=MdParserConfig(enable_extensions=["html_image", "html_admonition"]),
document={"source": "source"},
reporter=Mock(
warning=Mock(return_value=nodes.system_message("warning")),
Expand All @@ -28,18 +29,8 @@ def _run_directive(name: str, first_line: str, content: str, position: int):
)


@pytest.mark.parametrize(
"line,title,text,expected",
read_fixture_file(FIXTURE_PATH / "html_to_nodes.md"),
ids=[
f"{i[0]}-{i[1]}" for i in read_fixture_file(FIXTURE_PATH / "html_to_nodes.md")
],
)
def test_html_to_nodes(line, title, text, expected, mock_renderer):
@with_parameters(FIXTURE_PATH / "html_to_nodes.md")
def test_html_to_nodes(file_params, mock_renderer):
output = nodes.container()
output += html_to_nodes(text, line_number=0, renderer=mock_renderer)
try:
assert output.pformat().rstrip() == expected.rstrip()
except AssertionError:
print(output.pformat())
raise
output += html_to_nodes(file_params.content, line_number=0, renderer=mock_renderer)
file_params.assert_expected(output.pformat(), rstrip=True)

0 comments on commit f5f0b40

Please sign in to comment.