Skip to content

Commit

Permalink
👌 IMPROVE: Substitution extension (#777)
Browse files Browse the repository at this point in the history
Allow any value type (including dict, list, datetime, ...) and emit a `myst.substitution` warning for errors in resolving the substitution.
  • Loading branch information
chrisjsewell authored Jun 6, 2023
1 parent b043351 commit 3a6bbb1
Show file tree
Hide file tree
Showing 9 changed files with 56 additions and 11 deletions.
6 changes: 6 additions & 0 deletions myst_parser/config/dc_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ def __call__(
...


def any_(inst, field, value, suffix=""):
"""
A validator that does not perform any validation.
"""


def instance_of(type_: type[Any] | tuple[type[Any], ...]) -> ValidatorType:
"""
A validator that raises a `TypeError` if the initializer is called
Expand Down
7 changes: 3 additions & 4 deletions myst_parser/config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

from myst_parser.warnings_ import MystWarnings
from .dc_validators import (
any_,
deep_iterable,
deep_mapping,
in_,
Expand Down Expand Up @@ -335,12 +336,10 @@ def __repr__(self) -> str:

# Extension specific

substitutions: Dict[str, Union[str, int, float]] = dc.field(
substitutions: Dict[str, Any] = dc.field(
default_factory=dict,
metadata={
"validator": deep_mapping(
instance_of(str), instance_of((str, int, float)), instance_of(dict)
),
"validator": deep_mapping(instance_of(str), any_, instance_of(dict)),
"merge_topmatter": True,
"help": "Substitutions mapping",
"extension": "substitutions",
Expand Down
10 changes: 6 additions & 4 deletions myst_parser/mdit_to_docutils/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1854,11 +1854,12 @@ def render_substitution(self, token: SyntaxTreeNode, inline: bool) -> None:
variable_context
)
except Exception as error:
error_msg = self.reporter.error(
self.create_warning(
f"Substitution error:{error.__class__.__name__}: {error}",
MystWarnings.SUBSTITUTION,
line=position,
append_to=self.current_node,
)
self.current_node += [error_msg]
return

# handle circular references
Expand All @@ -1869,11 +1870,12 @@ def render_substitution(self, token: SyntaxTreeNode, inline: bool) -> None:
self.document.sub_references = getattr(self.document, "sub_references", set())
cyclic = references.intersection(self.document.sub_references)
if cyclic:
error_msg = self.reporter.error(
self.create_warning(
f"circular substitution reference: {cyclic}",
MystWarnings.SUBSTITUTION,
line=position,
append_to=self.current_node,
)
self.current_node += [error_msg]
return

# TODO improve error reporting;
Expand Down
2 changes: 2 additions & 0 deletions myst_parser/warnings_.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ class MystWarnings(Enum):
"""HTML could not be parsed."""
INVALID_ATTRIBUTE = "attribute"
"""Invalid attribute value."""
SUBSTITUTION = "substitution"
"""Substitution could not be resolved."""


def _is_suppressed_warning(
Expand Down
15 changes: 13 additions & 2 deletions tests/test_renderers/fixtures/myst-config.txt
Original file line number Diff line number Diff line change
Expand Up @@ -234,15 +234,26 @@ text
text
.

[substitutions] --myst-enable-extensions=substitution --myst-substitutions='{"a": "b", "c": "d"}'
[substitutions] --myst-enable-extensions=substitution --myst-substitutions='{"a": "b", "c": "d", "e": "{{f}}", "f": "{{e}}"}'
.
{{a}} {{c}}
{{a}} {{c}} {{x}} {{e}}
.
<document source="<string>">
<paragraph>
b

d

<system_message level="2" line="1" source="<string>" type="WARNING">
<paragraph>
Substitution error:UndefinedError: 'x' is undefined [myst.substitution]

<system_message level="2" line="3" source="<string>" type="WARNING">
<paragraph>
circular substitution reference: {'e'} [myst.substitution]

<string>:1: (WARNING/2) Substitution error:UndefinedError: 'x' is undefined [myst.substitution]
<string>:3: (WARNING/2) circular substitution reference: {'e'} [myst.substitution]
.

[attrs_image] --myst-enable-extensions=attrs_image
Expand Down
1 change: 0 additions & 1 deletion tests/test_renderers/fixtures/reporter_warnings.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ myst:
.
<string>:1: (WARNING/2) 'title_to_header' must be of type <class 'bool'> (got 1 that is a <class 'int'>). [myst.topmatter]
<string>:1: (WARNING/2) 'url_schemes' is not a list of strings: [1] [myst.topmatter]
<string>:1: (WARNING/2) 'substitutions['key']' must be of type (<class 'str'>, <class 'int'>, <class 'float'>) (got [] that is a <class 'list'>). [myst.topmatter]
.

Bad HTML Meta
Expand Down
11 changes: 11 additions & 0 deletions tests/test_sphinx/sourcedirs/substitutions/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ myst:
Inline note
```
override: Overridden by front matter
date: 2020-01-01
nested_list:
- item1
nested_dict:
key1: value1

---

Expand Down Expand Up @@ -54,3 +59,9 @@ Using env and filters:
```{toctree}
other.md
```

{{ date.strftime("%b %d, %Y") }}

{{ nested_list.0 }}

{{ nested_dict.key1 }}
9 changes: 9 additions & 0 deletions tests/test_sphinx/test_sphinx_builds/test_substitutions.html
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,15 @@
</li>
</ul>
</div>
<p>
Jan 01, 2020
</p>
<p>
item1
</p>
<p>
value1
</p>
</div>
</div>
</div>
6 changes: 6 additions & 0 deletions tests/test_sphinx/test_sphinx_builds/test_substitutions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,9 @@
INDEX
<compound classes="toctree-wrapper">
<toctree caption="True" entries="(None,\ 'other')" glob="False" hidden="False" includefiles="other" includehidden="False" maxdepth="-1" numbered="0" parent="index" rawentries="" titlesonly="False">
<paragraph>
Jan 01, 2020
<paragraph>
item1
<paragraph>
value1

0 comments on commit 3a6bbb1

Please sign in to comment.