Skip to content

Commit 2027ab5

Browse files
Merge pull request #302 from adamtheturtle/rest-example-support
Support rest example extension
2 parents 940fe87 + 22eedc6 commit 2027ab5

File tree

5 files changed

+191
-4
lines changed

5 files changed

+191
-4
lines changed

README.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,17 @@ For mathematical equation support, also add the ``sphinx.ext.mathjax`` extension
9090
"sphinx_notion",
9191
]
9292
93+
For rest-example blocks support, also add the `sphinx-toolbox <https://sphinx-toolbox.readthedocs.io/>`_ rest-example extension:
94+
95+
.. code-block:: python
96+
97+
"""Configuration for Sphinx."""
98+
99+
extensions = [
100+
"sphinx_toolbox.rest_example",
101+
"sphinx_notion",
102+
]
103+
93104
PDF support is included by default with the sphinx-notionbuilder extension.
94105

95106
Supported markup
@@ -105,6 +116,7 @@ The following syntax is supported:
105116
- Block quotes
106117
- All standard admonitions (note, warning, tip, attention, caution, danger, error, hint, important)
107118
- Collapsible sections (using sphinx-toolbox collapse directive)
119+
- Rest-example blocks (using sphinx-toolbox rest-example directive)
108120
- Images (with URLs or local paths)
109121
- Videos (with URLs or local paths)
110122
- Audio (with URLs or local paths)
@@ -254,6 +266,11 @@ Block-level equations can be written using the ``.. math::`` directive:
254266
255267
The equations will be rendered as proper mathematical notation in the generated Notion page, with inline equations appearing within the text flow and block equations appearing as separate equation blocks.
256268

269+
Using Rest-Example Blocks
270+
-------------------------
271+
272+
Rest-example blocks can be created using the `sphinx_toolbox.rest_example <https://sphinx-toolbox.readthedocs.io/en/stable/extensions/rest_example.html>`_ extension to create example blocks that show both source code and expected output. These are rendered as callout blocks in Notion with nested code blocks:
273+
257274
Unsupported Notion Block Types
258275
------------------------------
259276

sample/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"sphinx_notion",
1010
"sphinx_simplepdf",
1111
"sphinx_toolbox.collapse",
12+
"sphinx_toolbox.rest_example",
1213
"atsphinx.audioplayer",
1314
"sphinx_immaterial.task_lists",
1415
"sphinx.ext.mathjax",

sample/index.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,3 +420,23 @@ The Schrödinger equation:
420420
.. math::
421421
422422
i\hbar\frac{\partial}{\partial t}\Psi(\mathbf{r},t) = \hat{H}\Psi(\mathbf{r},t)
423+
424+
Rest Examples
425+
~~~~~~~~~~~~~
426+
427+
The `sphinx-toolbox rest_example extension <https://sphinx-toolbox.readthedocs.io/en/stable/extensions/rest_example.html>`_ allows you to show both the reStructuredText source code and its rendered output side by side.
428+
This is useful for documentation that demonstrates how to write reStructuredText directives.
429+
430+
.. rest-example::
431+
432+
.. code-block:: python
433+
434+
"""Python code."""
435+
436+
437+
def greet(name: str) -> str:
438+
"""Return a greeting message."""
439+
return f"Hello, {name}!"
440+
441+
442+
greet(name="World")

src/sphinx_notion/__init__.py

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,9 @@ def _map_pygments_to_notion_language(*, pygments_lang: str) -> CodeLang:
578578
"reason": CodeLang.REASON,
579579
"ruby": CodeLang.RUBY,
580580
"rb": CodeLang.RUBY,
581+
# This is not a perfect match, but at least rest-example will
582+
# be rendered.
583+
"rest": CodeLang.PLAIN_TEXT,
581584
"rust": CodeLang.RUST,
582585
"rs": CodeLang.RUST,
583586
"sass": CodeLang.SASS,
@@ -711,10 +714,25 @@ def _(
711714
*,
712715
section_level: int,
713716
) -> list[Block]:
717+
"""Process paragraph nodes by creating Notion Paragraph blocks.
718+
719+
Special case: if the paragraph contains only a container a
720+
``rest-example`` class, process the container directly instead of
721+
trying to process it as rich text.
714722
"""
715-
Process paragraph nodes by creating Notion Paragraph blocks.
716-
"""
717-
del section_level
723+
if (
724+
len(node.children) == 1
725+
and isinstance(
726+
node.children[0],
727+
nodes.container,
728+
)
729+
and node.children[0].attributes.get("classes", []) == ["rest-example"]
730+
):
731+
return _process_node_to_blocks(
732+
node.children[0],
733+
section_level=section_level,
734+
)
735+
718736
rich_text = _create_rich_text_from_children(node=node)
719737
return [UnoParagraph(text=rich_text)]
720738

@@ -1293,7 +1311,7 @@ def _(
12931311
section_level: int,
12941312
) -> list[Block]:
12951313
"""
1296-
Process container nodes, especially for ``literalinclude`` with captions.
1314+
Process container nodes.
12971315
"""
12981316
num_children_for_captioned_literalinclude = 2
12991317
if (
@@ -1320,6 +1338,13 @@ def _(
13201338
)
13211339
]
13221340

1341+
classes = node.attributes.get("classes", [])
1342+
if classes == ["rest-example"]:
1343+
return _process_rest_example_container(
1344+
node=node,
1345+
section_level=section_level,
1346+
)
1347+
13231348
blocks: list[Block] = []
13241349
for child in node.children:
13251350
child_blocks = _process_node_to_blocks(
@@ -1329,6 +1354,38 @@ def _(
13291354
return blocks
13301355

13311356

1357+
@beartype
1358+
def _process_rest_example_container(
1359+
*,
1360+
node: nodes.container,
1361+
section_level: int,
1362+
) -> list[Block]:
1363+
"""
1364+
Process a ``rest-example`` container by creating nested callout blocks.
1365+
"""
1366+
rst_source_node = node.children[0]
1367+
assert isinstance(rst_source_node, nodes.literal_block)
1368+
output_nodes = node.children[1:]
1369+
code_blocks = _process_node_to_blocks(rst_source_node, section_level=1)
1370+
1371+
output_blocks: list[Block] = []
1372+
for output_node in output_nodes:
1373+
output_blocks.extend(
1374+
_process_node_to_blocks(output_node, section_level=section_level)
1375+
)
1376+
1377+
code_callout = UnoCallout(text=text(text="Code"))
1378+
code_callout.append(blocks=code_blocks)
1379+
1380+
output_callout = UnoCallout(text=text(text="Output"))
1381+
output_callout.append(blocks=output_blocks)
1382+
1383+
main_callout = UnoCallout(text=text(text="Example"))
1384+
main_callout.append(blocks=[code_callout, output_callout])
1385+
1386+
return [main_callout]
1387+
1388+
13321389
@beartype
13331390
@_process_node_to_blocks.register
13341391
def _(
@@ -1360,6 +1417,21 @@ def _(
13601417
return [UnoEquation(latex=latex_content)]
13611418

13621419

1420+
@beartype
1421+
@_process_node_to_blocks.register
1422+
def _(
1423+
node: nodes.target,
1424+
*,
1425+
section_level: int,
1426+
) -> list[Block]:
1427+
"""
1428+
Process target nodes by ignoring them completely.
1429+
"""
1430+
del node
1431+
del section_level
1432+
return []
1433+
1434+
13631435
@beartype
13641436
@_process_node_to_blocks.register
13651437
def _(

tests/test_integration.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2935,3 +2935,80 @@ def test_block_equation(
29352935
tmp_path=tmp_path,
29362936
extensions=("sphinx_notion", "sphinx.ext.mathjax"),
29372937
)
2938+
2939+
2940+
def test_rest_example_block(
2941+
*,
2942+
make_app: Callable[..., SphinxTestApp],
2943+
tmp_path: Path,
2944+
) -> None:
2945+
"""
2946+
Rest example blocks become Notion callout blocks with nested code and
2947+
description.
2948+
"""
2949+
rst_content = """
2950+
.. rest-example::
2951+
2952+
.. code-block:: python
2953+
2954+
def hello_world():
2955+
print("Hello, World!")
2956+
2957+
Rendered output shows what the code does.
2958+
"""
2959+
2960+
code_callout = UnoCallout(
2961+
text=text(text="Code"),
2962+
)
2963+
code_callout.append(
2964+
blocks=[
2965+
UnoCode(
2966+
text=text(
2967+
text=textwrap.dedent(
2968+
text="""\
2969+
.. code-block:: python
2970+
2971+
def hello_world():
2972+
print("Hello, World!")
2973+
2974+
Rendered output shows what the code does."""
2975+
)
2976+
),
2977+
language="plain text",
2978+
),
2979+
]
2980+
)
2981+
2982+
output_callout = UnoCallout(
2983+
text=text(text="Output"),
2984+
)
2985+
output_callout.append(
2986+
blocks=[
2987+
UnoCode(
2988+
text=text(
2989+
text=textwrap.dedent(
2990+
text="""\
2991+
def hello_world():
2992+
print("Hello, World!")""",
2993+
)
2994+
),
2995+
language="python",
2996+
),
2997+
UnoParagraph(
2998+
text=text(text="Rendered output shows what the code does.")
2999+
),
3000+
]
3001+
)
3002+
3003+
main_callout = UnoCallout(text=text(text="Example"))
3004+
main_callout.append(blocks=[code_callout, output_callout])
3005+
3006+
expected_objects: list[Block] = [main_callout]
3007+
3008+
_assert_rst_converts_to_notion_objects(
3009+
rst_content=rst_content,
3010+
expected_objects=expected_objects,
3011+
make_app=make_app,
3012+
tmp_path=tmp_path,
3013+
extensions=("sphinx_notion", "sphinx_toolbox.rest_example"),
3014+
)

0 commit comments

Comments
 (0)