Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

⬆️ UPGRADE: sphinx v4 (& drop sphinx v2, change mathjax override) #390

Merged
merged 4 commits into from
Jun 12, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ jobs:
strategy:
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
sphinx: [">=3,<4"]
sphinx: [">=4,<5"]
os: [ubuntu-latest]
include:
- os: ubuntu-latest
python-version: 3.8
sphinx: ">=2,<3"
sphinx: ">=3,<4"
- os: windows-latest
python-version: 3.8
sphinx: ">=3,<4"
sphinx: ">=4,<5"

runs-on: ${{ matrix.os }}

Expand All @@ -46,8 +46,8 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install "sphinx${{ matrix.sphinx }}"
pip install -e .[linkify,testing]
pip install --upgrade-strategy "only-if-needed" "sphinx${{ matrix.sphinx }}"
- name: Run pytest
run: pytest

Expand Down
24 changes: 13 additions & 11 deletions docs/using/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -810,22 +810,24 @@ See [the extended syntax option](syntax/amsmath).
(syntax/mathjax)=
### Mathjax and math parsing

When building HTML using the [sphinx.ext.mathjax](https://www.sphinx-doc.org/en/master/usage/extensions/math.html#module-sphinx.ext.mathjax) extension (enabled by default), its default configuration is to also search for `$` delimiters and LaTeX environments (see [the tex2jax preprocessor](https://docs.mathjax.org/en/v2.7-latest/options/preprocessors/tex2jax.html#configure-tex2jax)).
When building HTML using the [sphinx.ext.mathjax](https://www.sphinx-doc.org/en/master/usage/extensions/math.html#module-sphinx.ext.mathjax) extension (enabled by default),
Myst-Parser injects the `tex2jax_ignore` (MathJax v2) and `mathjax_ignore` (MathJax v3) classes in to the top-level section of each MyST document, and adds the following default MathJax configuration:

Since such parsing is already covered by the plugins above, MyST-Parser disables this behaviour by overriding the `mathjax_config['tex2jax']` option with:
MathJax v2 (see [the tex2jax preprocessor](https://docs.mathjax.org/en/v2.7-latest/options/preprocessors/tex2jax.html#configure-tex2jax):

```python
mathjax_config["tex2jax"] = {
"inlineMath": [["\\(", "\\)"]],
"displayMath": [["\\[", "\\]"]],
"processRefs": False,
"processEnvironments": False,
}
```javascript
MathJax.Hub.Config({"tex2jax": {"processClass": "tex2jax_process|mathjax_process|math"}})
```

MathJax v3 (see [the document options](https://docs.mathjax.org/en/latest/options/document.html?highlight=ignoreHtmlClass#the-configuration-block)):

```javascript
window.MathJax = {"options": {"processHtmlClass": "tex2jax_process|mathjax_process|math"}}
```

Since these delimiters are how `sphinx.ext.mathjax` wraps the math content in the built HTML documents.
This is to ensure that MathJax processes only math, identified by the `dollarmath` and `amsmath` extensions, or specified in `math` directives.

To inhibit this override, set `myst_update_mathjax=False`.
To change this behaviour, set a custom regex like `myst_mathjax_classes="math|myclass"`, or `update_mathjax=False` to inhibit the override and process all HTML elements.

(syntax/frontmatter)=

Expand Down
40 changes: 24 additions & 16 deletions myst_parser/docutils_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,14 @@ def setup_render(
)
self._level_to_elem: Dict[int, nodes.Element] = {0: self.document}

@property
def sphinx_env(self) -> Optional[Any]:
"""Return the sphinx env, if using Sphinx."""
try:
return self.document.settings.env
except AttributeError:
return None

def create_warning(
self,
message: str,
Expand Down Expand Up @@ -232,12 +240,8 @@ def add_document_wordcount(self) -> None:
return

# save the wordcount to the sphinx BuildEnvironment metadata
try:
sphinx_env = self.document.settings.env
except AttributeError:
pass # if not sphinx renderer
else:
meta = sphinx_env.metadata.setdefault(sphinx_env.docname, {})
if self.sphinx_env is not None:
meta = self.sphinx_env.metadata.setdefault(self.sphinx_env.docname, {})
meta["wordcount"] = wordcount_metadata

# now add the wordcount as substitution definitions,
Expand Down Expand Up @@ -455,13 +459,11 @@ def render_fence(self, token: SyntaxTreeNode) -> None:
return self.render_directive(token)

if not language:
try:
sphinx_env = self.document.settings.env
language = sphinx_env.temp_data.get(
"highlight_language", sphinx_env.config.highlight_language
if self.sphinx_env is not None:
language = self.sphinx_env.temp_data.get(
"highlight_language", self.sphinx_env.config.highlight_language
)
except AttributeError:
pass

if not language:
language = self.config.get("highlight_language", "")
node = nodes.literal_block(text, text, language=language)
Expand All @@ -488,6 +490,14 @@ def render_heading(self, token: SyntaxTreeNode) -> None:
self.add_line_and_source_path(title_node, token)

new_section = nodes.section()
if level == 1 and (
self.sphinx_env is None
or (
"myst_update_mathjax" in self.sphinx_env.config
and self.sphinx_env.config.myst_update_mathjax
)
):
new_section["classes"].extend(["tex2jax_ignore", "mathjax_ignore"])
self.add_line_and_source_path(new_section, token)
new_section.append(title_node)

Expand Down Expand Up @@ -1060,10 +1070,8 @@ def render_substitution(self, token: SyntaxTreeNode, inline: bool) -> None:
**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
if self.sphinx_env is not None:
variable_context["env"] = self.sphinx_env

# fail on undefined variables
env = jinja2.Environment(undefined=jinja2.StrictUndefined)
Expand Down
11 changes: 10 additions & 1 deletion myst_parser/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,19 @@ class MdParserConfig:
default="sphinx", validator=in_(["sphinx", "html", "docutils"])
)
commonmark_only: bool = attr.ib(default=False, validator=instance_of(bool))
enable_extensions: Iterable[str] = attr.ib(factory=lambda: ["dollarmath"])

dmath_allow_labels: bool = attr.ib(default=True, validator=instance_of(bool))
dmath_allow_space: bool = attr.ib(default=True, validator=instance_of(bool))
dmath_allow_digits: bool = attr.ib(default=True, validator=instance_of(bool))
dmath_double_inline: bool = attr.ib(default=False, validator=instance_of(bool))

update_mathjax: bool = attr.ib(default=True, validator=instance_of(bool))

enable_extensions: Iterable[str] = attr.ib(factory=lambda: ["dollarmath"])
mathjax_classes: str = attr.ib(
default="tex2jax_process|mathjax_process|math",
validator=instance_of(str),
)

@enable_extensions.validator
def check_extensions(self, attribute, value):
Expand Down Expand Up @@ -118,6 +123,10 @@ def check_sub_delimiters(self, attribute, value):
f"myst_sub_delimiters does not contain strings of length 1: {value}"
)

@classmethod
def get_fields(cls) -> Tuple[attr.Attribute, ...]:
return attr.fields(cls)

def as_dict(self, dict_factory=dict) -> dict:
return attr.asdict(self, dict_factory=dict_factory)

Expand Down
56 changes: 31 additions & 25 deletions myst_parser/mathjax.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@


def override_mathjax(app: Sphinx):
"""Override aspects of the mathjax extension, but only if necessary."""
"""Override aspects of the mathjax extension.

MyST-Parser parses dollar and latex math, via markdown-it plugins.
Therefore, we tell Mathjax to only render these HTML elements.
This is accompanied by setting the `ignoreClass` on the top-level section of each MyST document.
"""
if (
"amsmath" in app.config["myst_enable_extensions"]
and "mathjax" in app.registry.html_block_math_renderers
Expand All @@ -30,32 +34,34 @@ def override_mathjax(app: Sphinx):
html_visit_displaymath, # type: ignore[assignment]
None,
)
# https://docs.mathjax.org/en/v2.7-latest/options/preprocessors/tex2jax.html#configure-tex2jax
if (
app.config.mathjax_config is None
and app.env.myst_config.update_mathjax # type: ignore[attr-defined]
):
app.config.mathjax_config = { # type: ignore[attr-defined]
"tex2jax": {
"inlineMath": [["\\(", "\\)"]],
"displayMath": [["\\[", "\\]"]],
"processRefs": False,
"processEnvironments": False,
}
}
elif app.env.myst_config.update_mathjax: # type: ignore[attr-defined]
if "tex2jax" in app.config.mathjax_config:

if not app.env.myst_config.update_mathjax: # type: ignore[attr-defined]
return

mjax_classes = app.env.myst_config.mathjax_classes # type: ignore[attr-defined]

if "mathjax3_config" in app.config:
# sphinx 4 + mathjax 3
app.config.mathjax3_config = app.config.mathjax3_config or {} # type: ignore[attr-defined]
app.config.mathjax3_config.setdefault("options", {})
if "processHtmlClass" in app.config.mathjax3_config["options"]:
logger.warning(
"`mathjax3_config['options']['processHtmlClass']` is being overridden "
f"by myst-parser to {mjax_classes}. "
"Set `myst_mathjax_classes = None` if this is undesirable."
)
app.config.mathjax3_config["options"]["processHtmlClass"] = mjax_classes
else:
# sphinx 3 + mathjax 2
chrisjsewell marked this conversation as resolved.
Show resolved Hide resolved
app.config.mathjax_config = app.config.mathjax_config or {} # type: ignore[attr-defined]
app.config.mathjax_config.setdefault("tex2jax", {})
if "processClass" in app.config.mathjax_config["tex2jax"]:
logger.warning(
"`mathjax_config['tex2jax']` is set, but `myst_update_mathjax = True`, "
"and so this will be overridden. "
"Set `myst_update_mathjax = False` if you wish to use your own config"
"`mathjax_config['tex2jax']['processClass']` is being overridden by "
f"myst-parser to {mjax_classes}. "
"Set `myst_mathjax_classes = None` if this is undesirable."
)
app.config.mathjax_config["tex2jax"] = {
"inlineMath": [["\\(", "\\)"]],
"displayMath": [["\\[", "\\]"]],
"processRefs": False,
"processEnvironments": False,
}
app.config.mathjax_config["tex2jax"]["processClass"] = mjax_classes


def html_visit_displaymath(self: HTMLTranslator, node: nodes.math_block) -> None:
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ install_requires =
markdown-it-py>=1.0.0,<2.0.0
mdit-py-plugins~=0.2.8
pyyaml
sphinx>=2.1,<4
sphinx>=3,<5
python_requires = >=3.6
include_package_data = True
zip_safe = True
Expand Down
36 changes: 18 additions & 18 deletions tests/test_renderers/fixtures/sphinx_directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@ default-domain (`sphinx.directives.DefaultDomain`):
.

--------------------------------
SPHINX3 object (`sphinx.directives.ObjectDescription`):
SPHINX4 object (`sphinx.directives.ObjectDescription`):
.
```{object} something
```
.
<document source="notset">
<index entries="">
<desc desctype="object" domain="" noindex="False" objtype="object">
<desc_signature>
<desc_name xml:space="preserve">
<desc classes="object" desctype="object" domain="" noindex="False" objtype="object">
<desc_signature classes="sig sig-object">
<desc_name classes="sig-name descname" xml:space="preserve">
something
<desc_content>
.
Expand Down Expand Up @@ -178,7 +178,7 @@ acks (`sphinx.directives.other.Acks`):
.

--------------------------------
SPHINX3.5 hlist (`sphinx.directives.other.HList`):
SPHINX4 hlist (`sphinx.directives.other.HList`):
.
```{hlist}

Expand Down Expand Up @@ -430,47 +430,47 @@ SPHINX3 productionlist (`sphinx.domains.std.ProductionList`):
.

--------------------------------
SPHINX3 cmdoption (`sphinx.domains.std.Cmdoption`):
SPHINX4 cmdoption (`sphinx.domains.std.Cmdoption`):
.
```{cmdoption} a
```
.
<document source="notset">
<index entries="('pair',\ 'command\ line\ option;\ a',\ 'cmdoption-arg-a',\ '',\ None)">
<desc classes="std" desctype="cmdoption" domain="std" noindex="False" objtype="cmdoption">
<desc_signature allnames="a" ids="cmdoption-arg-a">
<desc_name xml:space="preserve">
<desc classes="std cmdoption" desctype="cmdoption" domain="std" noindex="False" objtype="cmdoption">
<desc_signature allnames="a" classes="sig sig-object" ids="cmdoption-arg-a">
<desc_name classes="sig-name descname" xml:space="preserve">
a
<desc_addname xml:space="preserve">
<desc_addname classes="sig-prename descclassname" xml:space="preserve">
<desc_content>
.

--------------------------------
SPHINX3 rst:directive (`sphinx.domains.rst.ReSTDirective`):
SPHINX4 rst:directive (`sphinx.domains.rst.ReSTDirective`):
.
```{rst:directive} a
```
.
<document source="notset">
<index entries="('single',\ 'a\ (directive)',\ 'directive-a',\ '',\ None)">
<desc classes="rst" desctype="directive" domain="rst" noindex="False" objtype="directive">
<desc_signature ids="directive-a">
<desc_name xml:space="preserve">
<desc classes="rst directive" desctype="directive" domain="rst" noindex="False" objtype="directive">
<desc_signature classes="sig sig-object" ids="directive-a">
<desc_name classes="sig-name descname" xml:space="preserve">
.. a::
<desc_content>
.

--------------------------------
SPHINX3 rst:directive:option (`sphinx.domains.rst.ReSTDirectiveOption`):
SPHINX4 rst:directive:option (`sphinx.domains.rst.ReSTDirectiveOption`):
.
```{rst:directive:option} a
```
.
<document source="notset">
<index entries="('single',\ ':a:\ (directive\ option)',\ 'directive-option-a',\ '',\ 'A')">
<desc classes="rst" desctype="directive:option" domain="rst" noindex="False" objtype="directive:option">
<desc_signature ids="directive-option-a directive:option--a">
<desc_name xml:space="preserve">
<desc classes="rst directive:option" desctype="directive:option" domain="rst" noindex="False" objtype="directive:option">
<desc_signature classes="sig sig-object" ids="directive-option-a directive:option--a">
<desc_name classes="sig-name descname" xml:space="preserve">
:a:
<desc_content>
.
18 changes: 10 additions & 8 deletions tests/test_renderers/fixtures/sphinx_roles.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,27 +191,29 @@ cpp:enumerator (`sphinx.domains.cpp.CPPEnumeratorObject`):
.

--------------------------------
cpp:expr (`sphinx.domains.cpp.CPPExprRole`):
SKIP cpp:expr (`sphinx.domains.cpp.CPPExprRole`):
.
{cpp:expr}`a`
.
<document source="notset">
<paragraph>
<literal classes="xref cpp cpp-expr">
<pending_xref classname="True" cpp:parent_key="" modname="True" refdomain="cpp" reftarget="a" reftype="identifier">
a
<desc_inline classes="cpp-expr sig sig-inline cpp">
<pending_xref classname="True" cpp:parent_key="<sphinx.domains.cpp.LookupKey object at 0x7f948a6a73d0>" modname="True" refdomain="cpp" reftarget="a" reftype="identifier">
<desc_sig_name classes="n">
a
.

--------------------------------
cpp:texpr (`sphinx.domains.cpp.CPPExprRole`):
SKIP cpp:texpr (`sphinx.domains.cpp.CPPExprRole`):
.
{cpp:texpr}`a`
.
<document source="notset">
<paragraph>
<inline classes="xref cpp cpp-texpr">
<pending_xref classname="True" cpp:parent_key="" modname="True" refdomain="cpp" reftarget="a" reftype="identifier">
a
<desc_inline classes="cpp-texpr sig sig-inline cpp">
<pending_xref classname="True" cpp:parent_key="<sphinx.domains.cpp.LookupKey object at 0x7fac40b5f950>" modname="True" refdomain="cpp" reftarget="a" reftype="identifier">
<desc_sig_name classes="n">
a
.

--------------------------------
Expand Down