Skip to content

Commit c2d78de

Browse files
committed
formatting inline doubles as math blocks
1 parent 69e2def commit c2d78de

File tree

1 file changed

+138
-133
lines changed

1 file changed

+138
-133
lines changed

mdformat_myst/plugin.py

Lines changed: 138 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -1,133 +1,138 @@
1-
from __future__ import annotations
2-
3-
import re
4-
5-
from markdown_it import MarkdownIt
6-
import mdformat.plugins
7-
from mdformat.renderer import RenderContext, RenderTreeNode
8-
from mdit_py_plugins.dollarmath import dollarmath_plugin
9-
from mdit_py_plugins.myst_blocks import myst_block_plugin
10-
from mdit_py_plugins.myst_role import myst_role_plugin
11-
12-
from mdformat_myst._directives import fence, render_fence_html
13-
14-
_TARGET_PATTERN = re.compile(r"^\s*\(.+\)=\s*$")
15-
_ROLE_NAME_PATTERN = re.compile(r"({[a-zA-Z0-9_\-+:]+})")
16-
17-
18-
def update_mdit(mdit: MarkdownIt) -> None:
19-
# Enable mdformat-tables plugin
20-
tables_plugin = mdformat.plugins.PARSER_EXTENSIONS["tables"]
21-
if tables_plugin not in mdit.options["parser_extension"]:
22-
mdit.options["parser_extension"].append(tables_plugin)
23-
tables_plugin.update_mdit(mdit)
24-
25-
# Enable mdformat-frontmatter plugin
26-
frontmatter_plugin = mdformat.plugins.PARSER_EXTENSIONS["frontmatter"]
27-
if frontmatter_plugin not in mdit.options["parser_extension"]:
28-
mdit.options["parser_extension"].append(frontmatter_plugin)
29-
frontmatter_plugin.update_mdit(mdit)
30-
31-
# Enable mdformat-footnote plugin
32-
footnote_plugin = mdformat.plugins.PARSER_EXTENSIONS["footnote"]
33-
if footnote_plugin not in mdit.options["parser_extension"]:
34-
mdit.options["parser_extension"].append(footnote_plugin)
35-
footnote_plugin.update_mdit(mdit)
36-
37-
# Enable MyST role markdown-it extension
38-
mdit.use(myst_role_plugin)
39-
40-
# Enable MyST block markdown-it extension (including "LineComment",
41-
# "BlockBreak" and "Target" syntaxes)
42-
mdit.use(myst_block_plugin)
43-
44-
# Enable dollarmath markdown-it extension
45-
mdit.use(dollarmath_plugin, double_inline=True)
46-
47-
# Trick `mdformat`s AST validation by removing HTML rendering of code
48-
# blocks and fences. Directives are parsed as code fences and we
49-
# modify them in ways that don't break MyST AST but do break
50-
# CommonMark AST, so we need to do this to make validation pass.
51-
mdit.add_render_rule("fence", render_fence_html)
52-
mdit.add_render_rule("code_block", render_fence_html)
53-
54-
55-
def _role_renderer(node: RenderTreeNode, context: RenderContext) -> str:
56-
role_name = "{" + node.meta["name"] + "}"
57-
role_content = f"`{node.content}`"
58-
return role_name + role_content
59-
60-
61-
def _comment_renderer(node: RenderTreeNode, context: RenderContext) -> str:
62-
return "%" + node.content.replace("\n", "\n%")
63-
64-
65-
def _blockbreak_renderer(node: RenderTreeNode, context: RenderContext) -> str:
66-
text = "+++"
67-
if node.content:
68-
text += f" {node.content}"
69-
return text
70-
71-
72-
def _target_renderer(node: RenderTreeNode, context: RenderContext) -> str:
73-
return f"({node.content})="
74-
75-
76-
def _math_inline_renderer(node: RenderTreeNode, context: RenderContext) -> str:
77-
return f"${node.content}$"
78-
79-
80-
def _math_block_renderer(node: RenderTreeNode, context: RenderContext) -> str:
81-
return f"$${node.content}$$"
82-
83-
84-
def _math_block_label_renderer(node: RenderTreeNode, context: RenderContext) -> str:
85-
return f"$${node.content}$$ ({node.info})"
86-
87-
88-
def _render_children(node: RenderTreeNode, context: RenderContext) -> str:
89-
return "\n\n".join(child.render(context) for child in node.children)
90-
91-
92-
def _escape_paragraph(text: str, node: RenderTreeNode, context: RenderContext) -> str:
93-
lines = text.split("\n")
94-
95-
for i in range(len(lines)):
96-
# Three or more "+" chars are interpreted as a block break. Escape them.
97-
space_removed = lines[i].replace(" ", "")
98-
if space_removed.startswith("+++"):
99-
lines[i] = lines[i].replace("+", "\\+", 1)
100-
101-
# A line starting with "%" is a comment. Escape.
102-
if lines[i].startswith("%"):
103-
lines[i] = f"\\{lines[i]}"
104-
105-
# Escape lines that look like targets
106-
if _TARGET_PATTERN.search(lines[i]):
107-
lines[i] = lines[i].replace("(", "\\(", 1)
108-
109-
return "\n".join(lines)
110-
111-
112-
def _escape_text(text: str, node: RenderTreeNode, context: RenderContext) -> str:
113-
# Escape MyST role names
114-
text = _ROLE_NAME_PATTERN.sub(r"\\\1", text)
115-
116-
# Escape inline and block dollarmath
117-
text = text.replace("$", "\\$")
118-
119-
return text
120-
121-
122-
RENDERERS = {
123-
"myst_role": _role_renderer,
124-
"myst_line_comment": _comment_renderer,
125-
"myst_block_break": _blockbreak_renderer,
126-
"myst_target": _target_renderer,
127-
"math_inline": _math_inline_renderer,
128-
"math_inline_double": _math_block_renderer,
129-
"math_block_label": _math_block_label_renderer,
130-
"math_block": _math_block_renderer,
131-
"fence": fence,
132-
}
133-
POSTPROCESSORS = {"paragraph": _escape_paragraph, "text": _escape_text}
1+
from __future__ import annotations
2+
3+
import re
4+
5+
from markdown_it import MarkdownIt
6+
import mdformat.plugins
7+
from mdformat.renderer import RenderContext, RenderTreeNode
8+
from mdit_py_plugins.dollarmath import dollarmath_plugin
9+
from mdit_py_plugins.myst_blocks import myst_block_plugin
10+
from mdit_py_plugins.myst_role import myst_role_plugin
11+
12+
from mdformat_myst._directives import fence, render_fence_html
13+
14+
_TARGET_PATTERN = re.compile(r"^\s*\(.+\)=\s*$")
15+
_ROLE_NAME_PATTERN = re.compile(r"({[a-zA-Z0-9_\-+:]+})")
16+
17+
18+
def update_mdit(mdit: MarkdownIt) -> None:
19+
# Enable mdformat-tables plugin
20+
tables_plugin = mdformat.plugins.PARSER_EXTENSIONS["tables"]
21+
if tables_plugin not in mdit.options["parser_extension"]:
22+
mdit.options["parser_extension"].append(tables_plugin)
23+
tables_plugin.update_mdit(mdit)
24+
25+
# Enable mdformat-frontmatter plugin
26+
frontmatter_plugin = mdformat.plugins.PARSER_EXTENSIONS["frontmatter"]
27+
if frontmatter_plugin not in mdit.options["parser_extension"]:
28+
mdit.options["parser_extension"].append(frontmatter_plugin)
29+
frontmatter_plugin.update_mdit(mdit)
30+
31+
# Enable mdformat-footnote plugin
32+
footnote_plugin = mdformat.plugins.PARSER_EXTENSIONS["footnote"]
33+
if footnote_plugin not in mdit.options["parser_extension"]:
34+
mdit.options["parser_extension"].append(footnote_plugin)
35+
footnote_plugin.update_mdit(mdit)
36+
37+
# Enable MyST role markdown-it extension
38+
mdit.use(myst_role_plugin)
39+
40+
# Enable MyST block markdown-it extension (including "LineComment",
41+
# "BlockBreak" and "Target" syntaxes)
42+
mdit.use(myst_block_plugin)
43+
44+
# Enable dollarmath markdown-it extension
45+
mdit.use(dollarmath_plugin, double_inline=True)
46+
47+
# Trick `mdformat`s AST validation by removing HTML rendering of code
48+
# blocks and fences. Directives are parsed as code fences and we
49+
# modify them in ways that don't break MyST AST but do break
50+
# CommonMark AST, so we need to do this to make validation pass.
51+
mdit.add_render_rule("fence", render_fence_html)
52+
mdit.add_render_rule("code_block", render_fence_html)
53+
54+
55+
def _role_renderer(node: RenderTreeNode, context: RenderContext) -> str:
56+
role_name = "{" + node.meta["name"] + "}"
57+
role_content = f"`{node.content}`"
58+
return role_name + role_content
59+
60+
61+
def _comment_renderer(node: RenderTreeNode, context: RenderContext) -> str:
62+
return "%" + node.content.replace("\n", "\n%")
63+
64+
65+
def _blockbreak_renderer(node: RenderTreeNode, context: RenderContext) -> str:
66+
text = "+++"
67+
if node.content:
68+
text += f" {node.content}"
69+
return text
70+
71+
72+
def _target_renderer(node: RenderTreeNode, context: RenderContext) -> str:
73+
return f"({node.content})="
74+
75+
76+
def _math_inline_renderer(node: RenderTreeNode, context: RenderContext) -> str:
77+
return f"${node.content}$"
78+
79+
80+
def _math_block_renderer(node: RenderTreeNode, context: RenderContext) -> str:
81+
return f"$${node.content}$$"
82+
83+
84+
def _math_inline_double_renderer(node: RenderTreeNode, context: RenderContext) -> str:
85+
# formats the inline doubles as math blocks
86+
return f"\n$$\n{node.content.strip()}\n$$"
87+
88+
89+
def _math_block_label_renderer(node: RenderTreeNode, context: RenderContext) -> str:
90+
return f"$${node.content}$$ ({node.info})"
91+
92+
93+
def _render_children(node: RenderTreeNode, context: RenderContext) -> str:
94+
return "\n\n".join(child.render(context) for child in node.children)
95+
96+
97+
def _escape_paragraph(text: str, node: RenderTreeNode, context: RenderContext) -> str:
98+
lines = text.split("\n")
99+
100+
for i in range(len(lines)):
101+
# Three or more "+" chars are interpreted as a block break. Escape them.
102+
space_removed = lines[i].replace(" ", "")
103+
if space_removed.startswith("+++"):
104+
lines[i] = lines[i].replace("+", "\\+", 1)
105+
106+
# A line starting with "%" is a comment. Escape.
107+
if lines[i].startswith("%"):
108+
lines[i] = f"\\{lines[i]}"
109+
110+
# Escape lines that look like targets
111+
if _TARGET_PATTERN.search(lines[i]):
112+
lines[i] = lines[i].replace("(", "\\(", 1)
113+
114+
return "\n".join(lines)
115+
116+
117+
def _escape_text(text: str, node: RenderTreeNode, context: RenderContext) -> str:
118+
# Escape MyST role names
119+
text = _ROLE_NAME_PATTERN.sub(r"\\\1", text)
120+
121+
# Escape inline and block dollarmath
122+
text = text.replace("$", "\\$")
123+
124+
return text
125+
126+
127+
RENDERERS = {
128+
"myst_role": _role_renderer,
129+
"myst_line_comment": _comment_renderer,
130+
"myst_block_break": _blockbreak_renderer,
131+
"myst_target": _target_renderer,
132+
"math_inline": _math_inline_renderer,
133+
"math_inline_double": _math_inline_double_renderer,
134+
"math_block_label": _math_block_label_renderer,
135+
"math_block": _math_block_renderer,
136+
"fence": fence,
137+
}
138+
POSTPROCESSORS = {"paragraph": _escape_paragraph, "text": _escape_text}

0 commit comments

Comments
 (0)