sphinx-examples-as-code 0.4.1__tar.gz → 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/.github/workflows/ci.yml +4 -4
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/PKG-INFO +14 -20
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/README.md +13 -19
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/pyproject.toml +1 -1
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/sphinx_examples_as_code/__init__.py +170 -91
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/sphinx_examples_as_code/_version.py +3 -3
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/sphinx_examples_as_code.egg-info/PKG-INFO +14 -20
- sphinx_examples_as_code-0.6.0/sphinx_examples_as_code.egg-info/scm_version.json +8 -0
- sphinx_examples_as_code-0.6.0/tests/gallery_fixture/index.rst +17 -0
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/tests/test_single_function_page.py +0 -34
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/tests/test_sphinx_examples_as_code.py +255 -284
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/tests/test_tinypages.py +73 -108
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/tests/tinypages/docstring_cases.py +42 -6
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/tests/tinypages/docstring_cases.rst +7 -3
- sphinx_examples_as_code-0.4.1/sphinx_examples_as_code.egg-info/scm_version.json +0 -8
- sphinx_examples_as_code-0.4.1/tests/gallery_fixture/index.rst +0 -7
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/.github/dependabot.yml +0 -0
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/.github/release.yml +0 -0
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/.gitignore +0 -0
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/.pre-commit-config.yaml +0 -0
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/LICENSE +0 -0
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/setup.cfg +0 -0
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/sphinx_examples_as_code.egg-info/SOURCES.txt +0 -0
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/sphinx_examples_as_code.egg-info/dependency_links.txt +0 -0
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/sphinx_examples_as_code.egg-info/requires.txt +0 -0
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/sphinx_examples_as_code.egg-info/scm_file_list.json +0 -0
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/sphinx_examples_as_code.egg-info/top_level.txt +0 -0
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/tests/gallery_fixture/conf.py +0 -0
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/tests/gallery_fixture/examples/GALLERY_HEADER.rst +0 -0
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/tests/gallery_fixture/examples/plot_minimal.py +0 -0
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/tests/heading_level_fixture/conf.py +0 -0
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/tests/heading_level_fixture/examples/GALLERY_HEADER.rst +0 -0
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/tests/heading_level_fixture/examples/plot_reused_level.py +0 -0
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/tests/heading_level_fixture/examples/plot_three_levels.py +0 -0
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/tests/heading_level_fixture/index.rst +0 -0
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/tests/single_function_fixture/conf.py +0 -0
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/tests/single_function_fixture/index.rst +0 -0
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/tests/single_function_fixture/mymodule.py +0 -0
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/tests/test_gallery_downloads.py +0 -0
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/tests/test_heading_level_reuse.py +0 -0
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/tests/tinypages/conf.py +0 -0
- {sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/tests/tinypages/index.rst +0 -0
|
@@ -22,7 +22,7 @@ jobs:
|
|
|
22
22
|
with:
|
|
23
23
|
persist-credentials: false
|
|
24
24
|
fetch-depth: 0
|
|
25
|
-
- uses: astral-sh/setup-uv@
|
|
25
|
+
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
26
26
|
with:
|
|
27
27
|
enable-cache: false
|
|
28
28
|
- run: uv sync --group dev
|
|
@@ -43,10 +43,10 @@ jobs:
|
|
|
43
43
|
with:
|
|
44
44
|
persist-credentials: false
|
|
45
45
|
fetch-depth: 0
|
|
46
|
-
- uses: astral-sh/setup-uv@
|
|
46
|
+
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
47
47
|
with:
|
|
48
48
|
enable-cache: false
|
|
49
|
-
- uses: pyvista/setup-headless-display-action@
|
|
49
|
+
- uses: pyvista/setup-headless-display-action@c103a2ff45650d38cb71684b5dc6cdfeb9442c79 # v5.1.0
|
|
50
50
|
with:
|
|
51
51
|
pyvista: true
|
|
52
52
|
- run: uv sync --group dev --python ${{ matrix.python-version }}
|
|
@@ -65,7 +65,7 @@ jobs:
|
|
|
65
65
|
with:
|
|
66
66
|
persist-credentials: false
|
|
67
67
|
fetch-depth: 0
|
|
68
|
-
- uses: astral-sh/setup-uv@
|
|
68
|
+
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
69
69
|
with:
|
|
70
70
|
enable-cache: false
|
|
71
71
|
- run: uv build
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sphinx-examples-as-code
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Sphinx extension for converting docstring examples into downloadable code.
|
|
5
5
|
Author-email: The PyVista Developers <info@pyvista.org>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -66,7 +66,6 @@ sphinx_examples_as_code_conf = {
|
|
|
66
66
|
'py': 'Download Python source code',
|
|
67
67
|
'ipynb': 'Download Jupyter notebook',
|
|
68
68
|
},
|
|
69
|
-
'include_see_also': True,
|
|
70
69
|
}
|
|
71
70
|
```
|
|
72
71
|
|
|
@@ -76,13 +75,6 @@ sphinx_examples_as_code_conf = {
|
|
|
76
75
|
(default). Always offered in that order regardless of how the list is written.
|
|
77
76
|
- `link_labels`: the text of the download link(s) themselves, per format. Set only the
|
|
78
77
|
format(s) you want to change; any left unset keep reading their own default shown above.
|
|
79
|
-
- `include_see_also`: whether "See Also" content is included in the generated file.
|
|
80
|
-
`True` (default) includes it, in any of its forms: a `.. seealso::` admonition, a bare
|
|
81
|
-
`.. rubric:: See Also`, a hand-written `See Also` heading, or numpydoc's own "See Also"
|
|
82
|
-
field (including when it's been reordered outside the Examples section itself, or the
|
|
83
|
-
Examples section has been hoisted to a heading of its own — a setup some projects use to
|
|
84
|
-
get "Examples" listed in the page's own navigation). `False` excludes "See Also" content
|
|
85
|
-
in every one of those forms.
|
|
86
78
|
- `gallery_downloads`: opt-in takeover of
|
|
87
79
|
[sphinx-gallery](https://sphinx-gallery.github.io)'s own per-example downloads.
|
|
88
80
|
`False` (default) leaves sphinx-gallery pages untouched. See
|
|
@@ -114,11 +106,8 @@ What happens to the content of an Examples section:
|
|
|
114
106
|
real Python source. Doctest *output* lines are dropped — only the input code matters.
|
|
115
107
|
- `.. code-block:: python` (or `py`) blocks are kept as-is; other languages become
|
|
116
108
|
comments, set off with blank lines on both sides like any other directive.
|
|
117
|
-
- Admonitions (`.. note::`, `.. warning::`,
|
|
118
|
-
|
|
119
|
-
`.py` only. "See Also" is recognized in any of its three forms (`.. seealso::`, a bare
|
|
120
|
-
`.. rubric:: See Also`, or a hand-written `See Also` heading) and always renders the
|
|
121
|
-
same way.
|
|
109
|
+
- Admonitions (`.. note::`, `.. warning::`, ...) become a `# LABEL:` comment followed by
|
|
110
|
+
their content as comments, indented one level under the label in `.py` only.
|
|
122
111
|
- A bullet/numbered list becomes a `-`/`N.`-marked line per item, set off with a blank
|
|
123
112
|
line on both sides in either format — a bare `#` instead of a real blank line when
|
|
124
113
|
that falls inside an admonition or a definition's own body in `.py`, so the whole
|
|
@@ -128,15 +117,19 @@ What happens to the content of an Examples section:
|
|
|
128
117
|
- Cross-references and inline code (`:class:`, `:meth:`, `:func:`, `:attr:`,
|
|
129
118
|
double-backtick literals, ...) keep their display text, wrapped in backticks (e.g.
|
|
130
119
|
``:class:`pyvista.Plotter` `` -> `` `pyvista.Plotter` ``). If `html_baseurl` is set and
|
|
131
|
-
the reference resolves
|
|
132
|
-
|
|
133
|
-
else in `.py` the link is simply omitted.
|
|
120
|
+
the reference resolves, `.ipynb` turns it into a clickable link; `.py` never writes the
|
|
121
|
+
link, only the display text.
|
|
134
122
|
- Plain prose-style references (`:ref:`, `:doc:`) are treated the same way, minus the
|
|
135
123
|
backticks.
|
|
136
124
|
- Everything else text-bearing (prose, captions, other non-Python code) becomes a plain
|
|
137
125
|
`#` comment.
|
|
126
|
+
- A table becomes an aligned text table, its caption on the line above: an RST simple
|
|
127
|
+
table in `.py`, a Markdown pipe table in `.ipynb`. Each cell is flattened to a single
|
|
128
|
+
line, and a column left entirely empty -- one holding only images, say -- is dropped
|
|
129
|
+
along with its heading.
|
|
138
130
|
- Figures/images, raw HTML, sphinx-design dropdowns/tab-sets, and sphinx-tags' `.. tags::`
|
|
139
131
|
line are dropped entirely.
|
|
132
|
+
- "See Also" content is always dropped.
|
|
140
133
|
|
|
141
134
|
Generated `.py` files start with a `# Examples from <qualified name>` title header
|
|
142
135
|
(gallery mode uses the page's own title instead -- see below), with a few whitespace
|
|
@@ -186,9 +179,10 @@ styles:
|
|
|
186
179
|
|
|
187
180
|
Two things worth knowing before turning this on:
|
|
188
181
|
|
|
189
|
-
- It's built against sphinx-gallery's own
|
|
190
|
-
|
|
191
|
-
sphinx-gallery release could shift
|
|
182
|
+
- It's built against sphinx-gallery's own doctree output — the `sphx-glr-*` CSS classes
|
|
183
|
+
its own theming depends on, and the private node type holding a highlighted code
|
|
184
|
+
block — not a documented extension API. A future sphinx-gallery release could shift
|
|
185
|
+
that structure without warning. Tested against sphinx-gallery 0.22 and later.
|
|
192
186
|
- sphinx-gallery's own `.py`/`.ipynb`/`.zip` downloads still end up copied into
|
|
193
187
|
`_downloads/`, even though nothing on the page links to them anymore.
|
|
194
188
|
|
|
@@ -39,7 +39,6 @@ sphinx_examples_as_code_conf = {
|
|
|
39
39
|
'py': 'Download Python source code',
|
|
40
40
|
'ipynb': 'Download Jupyter notebook',
|
|
41
41
|
},
|
|
42
|
-
'include_see_also': True,
|
|
43
42
|
}
|
|
44
43
|
```
|
|
45
44
|
|
|
@@ -49,13 +48,6 @@ sphinx_examples_as_code_conf = {
|
|
|
49
48
|
(default). Always offered in that order regardless of how the list is written.
|
|
50
49
|
- `link_labels`: the text of the download link(s) themselves, per format. Set only the
|
|
51
50
|
format(s) you want to change; any left unset keep reading their own default shown above.
|
|
52
|
-
- `include_see_also`: whether "See Also" content is included in the generated file.
|
|
53
|
-
`True` (default) includes it, in any of its forms: a `.. seealso::` admonition, a bare
|
|
54
|
-
`.. rubric:: See Also`, a hand-written `See Also` heading, or numpydoc's own "See Also"
|
|
55
|
-
field (including when it's been reordered outside the Examples section itself, or the
|
|
56
|
-
Examples section has been hoisted to a heading of its own — a setup some projects use to
|
|
57
|
-
get "Examples" listed in the page's own navigation). `False` excludes "See Also" content
|
|
58
|
-
in every one of those forms.
|
|
59
51
|
- `gallery_downloads`: opt-in takeover of
|
|
60
52
|
[sphinx-gallery](https://sphinx-gallery.github.io)'s own per-example downloads.
|
|
61
53
|
`False` (default) leaves sphinx-gallery pages untouched. See
|
|
@@ -87,11 +79,8 @@ What happens to the content of an Examples section:
|
|
|
87
79
|
real Python source. Doctest *output* lines are dropped — only the input code matters.
|
|
88
80
|
- `.. code-block:: python` (or `py`) blocks are kept as-is; other languages become
|
|
89
81
|
comments, set off with blank lines on both sides like any other directive.
|
|
90
|
-
- Admonitions (`.. note::`, `.. warning::`,
|
|
91
|
-
|
|
92
|
-
`.py` only. "See Also" is recognized in any of its three forms (`.. seealso::`, a bare
|
|
93
|
-
`.. rubric:: See Also`, or a hand-written `See Also` heading) and always renders the
|
|
94
|
-
same way.
|
|
82
|
+
- Admonitions (`.. note::`, `.. warning::`, ...) become a `# LABEL:` comment followed by
|
|
83
|
+
their content as comments, indented one level under the label in `.py` only.
|
|
95
84
|
- A bullet/numbered list becomes a `-`/`N.`-marked line per item, set off with a blank
|
|
96
85
|
line on both sides in either format — a bare `#` instead of a real blank line when
|
|
97
86
|
that falls inside an admonition or a definition's own body in `.py`, so the whole
|
|
@@ -101,15 +90,19 @@ What happens to the content of an Examples section:
|
|
|
101
90
|
- Cross-references and inline code (`:class:`, `:meth:`, `:func:`, `:attr:`,
|
|
102
91
|
double-backtick literals, ...) keep their display text, wrapped in backticks (e.g.
|
|
103
92
|
``:class:`pyvista.Plotter` `` -> `` `pyvista.Plotter` ``). If `html_baseurl` is set and
|
|
104
|
-
the reference resolves
|
|
105
|
-
|
|
106
|
-
else in `.py` the link is simply omitted.
|
|
93
|
+
the reference resolves, `.ipynb` turns it into a clickable link; `.py` never writes the
|
|
94
|
+
link, only the display text.
|
|
107
95
|
- Plain prose-style references (`:ref:`, `:doc:`) are treated the same way, minus the
|
|
108
96
|
backticks.
|
|
109
97
|
- Everything else text-bearing (prose, captions, other non-Python code) becomes a plain
|
|
110
98
|
`#` comment.
|
|
99
|
+
- A table becomes an aligned text table, its caption on the line above: an RST simple
|
|
100
|
+
table in `.py`, a Markdown pipe table in `.ipynb`. Each cell is flattened to a single
|
|
101
|
+
line, and a column left entirely empty -- one holding only images, say -- is dropped
|
|
102
|
+
along with its heading.
|
|
111
103
|
- Figures/images, raw HTML, sphinx-design dropdowns/tab-sets, and sphinx-tags' `.. tags::`
|
|
112
104
|
line are dropped entirely.
|
|
105
|
+
- "See Also" content is always dropped.
|
|
113
106
|
|
|
114
107
|
Generated `.py` files start with a `# Examples from <qualified name>` title header
|
|
115
108
|
(gallery mode uses the page's own title instead -- see below), with a few whitespace
|
|
@@ -159,9 +152,10 @@ styles:
|
|
|
159
152
|
|
|
160
153
|
Two things worth knowing before turning this on:
|
|
161
154
|
|
|
162
|
-
- It's built against sphinx-gallery's own
|
|
163
|
-
|
|
164
|
-
sphinx-gallery release could shift
|
|
155
|
+
- It's built against sphinx-gallery's own doctree output — the `sphx-glr-*` CSS classes
|
|
156
|
+
its own theming depends on, and the private node type holding a highlighted code
|
|
157
|
+
block — not a documented extension API. A future sphinx-gallery release could shift
|
|
158
|
+
that structure without warning. Tested against sphinx-gallery 0.22 and later.
|
|
165
159
|
- sphinx-gallery's own `.py`/`.ipynb`/`.zip` downloads still end up copied into
|
|
166
160
|
`_downloads/`, even though nothing on the page links to them anymore.
|
|
167
161
|
|
{sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/sphinx_examples_as_code/__init__.py
RENAMED
|
@@ -7,7 +7,6 @@ from __future__ import annotations
|
|
|
7
7
|
|
|
8
8
|
import ast
|
|
9
9
|
from dataclasses import dataclass
|
|
10
|
-
from dataclasses import replace
|
|
11
10
|
import hashlib
|
|
12
11
|
import json
|
|
13
12
|
from pathlib import Path
|
|
@@ -92,6 +91,22 @@ _ADMONITION_LABELS = {
|
|
|
92
91
|
|
|
93
92
|
_PYTHON_LANGUAGES = ('python', 'py', 'python3')
|
|
94
93
|
|
|
94
|
+
# sphinx-gallery's own node type for a gallery example's Python code, in
|
|
95
|
+
# place of a ``literal_block``: one child per pygments token, so that names
|
|
96
|
+
# in the code can carry links to their documentation. Its language is spelled
|
|
97
|
+
# ``lang``, and its source has to be read back off its children. Code in any
|
|
98
|
+
# other language stays an ordinary ``literal_block``.
|
|
99
|
+
#
|
|
100
|
+
# Matched by tag name rather than by ``isinstance``: sphinx-gallery is not a
|
|
101
|
+
# dependency of this extension (gallery support is opt-in via
|
|
102
|
+
# ``gallery_downloads``), so the class itself can't be imported here.
|
|
103
|
+
_GALLERY_CODE_BLOCK_TAGNAME = 'code_links_block'
|
|
104
|
+
|
|
105
|
+
# Class on the ``inline`` nodes sphinx-gallery interleaves into the above when
|
|
106
|
+
# the block has line numbers turned on. Rendering furniture, not source, so
|
|
107
|
+
# they're dropped rather than baked into the generated file.
|
|
108
|
+
_GALLERY_LINENOS_CLASS = 'linenos'
|
|
109
|
+
|
|
95
110
|
# A chunk of generated lines tagged with how it should be spaced relative to
|
|
96
111
|
# its neighbors when segments are joined (see ``_join_segments``):
|
|
97
112
|
# 'code' real Python source
|
|
@@ -154,9 +169,7 @@ class _RenderContext:
|
|
|
154
169
|
app: Sphinx
|
|
155
170
|
docname: str
|
|
156
171
|
fmt: str # 'py' or 'ipynb'
|
|
157
|
-
in_see_also: bool = False
|
|
158
172
|
in_footer: bool = False
|
|
159
|
-
show_see_also: bool = True
|
|
160
173
|
|
|
161
174
|
|
|
162
175
|
def _normalized_html_baseurl(app: Sphinx) -> str | None:
|
|
@@ -233,9 +246,8 @@ def _render_reference(node: nodes.reference, ctx: _RenderContext) -> str:
|
|
|
233
246
|
"""Render a resolved or unresolved cross-reference/hyperlink.
|
|
234
247
|
|
|
235
248
|
The target URL, if resolved, is used as a clickable markdown link in
|
|
236
|
-
notebooks (always), and as literal URL text in ``.py`` (only within
|
|
237
|
-
|
|
238
|
-
omitted).
|
|
249
|
+
notebooks (always), and as literal URL text in ``.py`` (only within the
|
|
250
|
+
footer -- elsewhere in ``.py`` the link is simply omitted).
|
|
239
251
|
"""
|
|
240
252
|
display = node.astext()
|
|
241
253
|
is_code = any(isinstance(child, nodes.literal) for child in node.children)
|
|
@@ -247,11 +259,6 @@ def _render_reference(node: nodes.reference, ctx: _RenderContext) -> str:
|
|
|
247
259
|
if ctx.fmt == 'ipynb':
|
|
248
260
|
return f'[`{display}`]({url})' if is_code else f'[{display}]({url})'
|
|
249
261
|
|
|
250
|
-
if ctx.in_see_also:
|
|
251
|
-
# Surrounding newlines set this off on its own line; _add_comment
|
|
252
|
-
# splits on them like any other multi-line text.
|
|
253
|
-
return f'\n{display} {url}\n'
|
|
254
|
-
|
|
255
262
|
if ctx.in_footer:
|
|
256
263
|
# Inline, not surrounded by newlines -- "text url" in place, not a
|
|
257
264
|
# markdown link.
|
|
@@ -328,16 +335,34 @@ def _convert_doctest_block(node: nodes.doctest_block) -> list[Segment]:
|
|
|
328
335
|
return [('code', lines)]
|
|
329
336
|
|
|
330
337
|
|
|
331
|
-
def
|
|
338
|
+
def _is_gallery_code_block(node: nodes.Node) -> bool:
|
|
339
|
+
"""Check whether ``node`` is the node type sphinx-gallery puts example code in."""
|
|
340
|
+
return getattr(node, 'tagname', None) == _GALLERY_CODE_BLOCK_TAGNAME
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _gallery_code_block_source(node: nodes.Element) -> str:
|
|
344
|
+
"""Reassemble the source text sphinx-gallery split across per-token nodes."""
|
|
345
|
+
# One child per pygments token (some wrapped in a reference node), which
|
|
346
|
+
# ``astext()`` joins back together with no separator -- apart from the
|
|
347
|
+
# line-number nodes, which were never part of the source.
|
|
348
|
+
return ''.join(
|
|
349
|
+
child.astext() for child in node.children if not _has_class(child, _GALLERY_LINENOS_CLASS)
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _convert_literal_block(node: nodes.Element) -> list[Segment]:
|
|
332
354
|
"""Convert a ``.. code-block::``. Python blocks stay code, others become comments."""
|
|
333
|
-
|
|
355
|
+
if _is_gallery_code_block(node):
|
|
356
|
+
language, source = node.get('lang', ''), _gallery_code_block_source(node)
|
|
357
|
+
else:
|
|
358
|
+
language, source = node.get('language', ''), node.astext()
|
|
334
359
|
# Case-insensitive: sphinx-gallery emits ``.. code-block:: Python`` (capitalized).
|
|
335
360
|
if language.lower() in _PYTHON_LANGUAGES:
|
|
336
|
-
lines = [_clean_code_comment(line) for line in
|
|
361
|
+
lines = [_clean_code_comment(line) for line in source.splitlines()]
|
|
337
362
|
while lines and not lines[-1].strip():
|
|
338
363
|
lines.pop()
|
|
339
364
|
return [('code', lines)] if lines else []
|
|
340
|
-
text =
|
|
365
|
+
text = source.strip()
|
|
341
366
|
if not text:
|
|
342
367
|
return []
|
|
343
368
|
comment_lines: list[str] = []
|
|
@@ -483,16 +508,125 @@ def _list_segment(
|
|
|
483
508
|
return [('directive', lines)] if lines else []
|
|
484
509
|
|
|
485
510
|
|
|
511
|
+
#: Narrowest a generated table column may be, per format.
|
|
512
|
+
_TABLE_MIN_WIDTH = {'py': 1, 'ipynb': 3}
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def _table_cell_text(entry: nodes.entry, ctx: _RenderContext) -> str:
|
|
516
|
+
"""Render one cell's content as a single line, list items separated by ``;``."""
|
|
517
|
+
parts: list[str] = []
|
|
518
|
+
for child in entry.children:
|
|
519
|
+
if isinstance(child, (nodes.bullet_list, nodes.enumerated_list)):
|
|
520
|
+
items = (_render_inline(item, ctx).strip() for item in child.children)
|
|
521
|
+
parts.append('; '.join(item for item in items if item))
|
|
522
|
+
else:
|
|
523
|
+
parts.append(_render_inline(child, ctx).strip())
|
|
524
|
+
return ' '.join(part for part in parts if part)
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def _table_rows(group: nodes.Element, ctx: _RenderContext) -> list[list[str]]:
|
|
528
|
+
"""Render a ``thead``/``tbody``'s rows, padding a column-spanning cell with empties."""
|
|
529
|
+
rows: list[list[str]] = []
|
|
530
|
+
for row in group.children:
|
|
531
|
+
cells: list[str] = []
|
|
532
|
+
for entry in row.children:
|
|
533
|
+
cells.append(_table_cell_text(entry, ctx))
|
|
534
|
+
cells.extend([''] * entry.get('morecols', 0))
|
|
535
|
+
if any(cells):
|
|
536
|
+
rows.append(cells)
|
|
537
|
+
return rows
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def _table_grid(node: nodes.table, ctx: _RenderContext) -> tuple[list[list[str]], list[list[str]]]:
|
|
541
|
+
"""Split a table into header rows and body rows, dropping any column left empty."""
|
|
542
|
+
head: list[list[str]] = []
|
|
543
|
+
body: list[list[str]] = []
|
|
544
|
+
for tgroup in node.children:
|
|
545
|
+
if not isinstance(tgroup, nodes.tgroup):
|
|
546
|
+
continue
|
|
547
|
+
for group in tgroup.children:
|
|
548
|
+
if isinstance(group, nodes.thead):
|
|
549
|
+
head.extend(_table_rows(group, ctx))
|
|
550
|
+
elif isinstance(group, nodes.tbody):
|
|
551
|
+
body.extend(_table_rows(group, ctx))
|
|
552
|
+
columns = max((len(row) for row in [*head, *body]), default=0)
|
|
553
|
+
for row in [*head, *body]:
|
|
554
|
+
row.extend([''] * (columns - len(row)))
|
|
555
|
+
filled = [index for index in range(columns) if any(row[index] for row in body)]
|
|
556
|
+
if not filled or len(filled) == columns:
|
|
557
|
+
return head, body
|
|
558
|
+
return (
|
|
559
|
+
[[row[index] for index in filled] for row in head],
|
|
560
|
+
[[row[index] for index in filled] for row in body],
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
def _table_widths(rows: list[list[str]], minimum: int) -> list[int]:
|
|
565
|
+
"""Measure each column, never narrower than ``minimum``."""
|
|
566
|
+
return [max(minimum, *map(len, column)) for column in zip(*rows, strict=True)]
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
def _markdown_table_lines(head: list[list[str]], body: list[list[str]]) -> list[str]:
|
|
570
|
+
"""Render rows as a Markdown pipe table, with a blank header row if there is none."""
|
|
571
|
+
head = [[cell.replace('|', r'\|') for cell in row] for row in head]
|
|
572
|
+
body = [[cell.replace('|', r'\|') for cell in row] for row in body]
|
|
573
|
+
widths = _table_widths([*head, *body], _TABLE_MIN_WIDTH['ipynb'])
|
|
574
|
+
|
|
575
|
+
def render(cells: list[str]) -> str:
|
|
576
|
+
padded = (cell.ljust(width) for cell, width in zip(cells, widths, strict=True))
|
|
577
|
+
return f'| {" | ".join(padded)} |'
|
|
578
|
+
|
|
579
|
+
header, *extra = head or [[''] * len(widths)]
|
|
580
|
+
divider = f'| {" | ".join("-" * width for width in widths)} |'
|
|
581
|
+
return [render(header), divider, *(render(cells) for cells in [*extra, *body])]
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def _simple_table_lines(head: list[list[str]], body: list[list[str]]) -> list[str]:
|
|
585
|
+
"""Render rows as an RST simple table, bordered above, below and under its header."""
|
|
586
|
+
widths = _table_widths([*head, *body], _TABLE_MIN_WIDTH['py'])
|
|
587
|
+
border = ' '.join('=' * width for width in widths)
|
|
588
|
+
|
|
589
|
+
def render(cells: list[str]) -> str:
|
|
590
|
+
return ' '.join(
|
|
591
|
+
cell.ljust(width) for cell, width in zip(cells, widths, strict=True)
|
|
592
|
+
).rstrip()
|
|
593
|
+
|
|
594
|
+
lines = [border]
|
|
595
|
+
for rows in (head, body):
|
|
596
|
+
if rows:
|
|
597
|
+
lines.extend(render(cells) for cells in rows)
|
|
598
|
+
lines.append(border)
|
|
599
|
+
return lines
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
def _table_segment(node: nodes.table, ctx: _RenderContext) -> list[Segment]:
|
|
603
|
+
"""Convert a table to an RST simple table in ``.py``, a Markdown one in ``.ipynb``.
|
|
604
|
+
|
|
605
|
+
Each cell is flattened to a single line, the caption sits above.
|
|
606
|
+
"""
|
|
607
|
+
head, body = _table_grid(node, ctx)
|
|
608
|
+
if not head and not body:
|
|
609
|
+
return []
|
|
610
|
+
lines: list[str] = []
|
|
611
|
+
caption = next((child for child in node.children if isinstance(child, nodes.title)), None)
|
|
612
|
+
if caption is not None and (text := _render_inline(caption, ctx).strip()):
|
|
613
|
+
_add_comment(lines, text)
|
|
614
|
+
lines.append('#')
|
|
615
|
+
render = _markdown_table_lines if ctx.fmt == 'ipynb' else _simple_table_lines
|
|
616
|
+
for line in render(head, body):
|
|
617
|
+
_add_comment(lines, line)
|
|
618
|
+
return [('directive', lines)]
|
|
619
|
+
|
|
620
|
+
|
|
486
621
|
def _convert_admonition(
|
|
487
622
|
node: nodes.Element, label: str, ctx: _RenderContext, *, skip_first_title: bool = False
|
|
488
623
|
) -> list[Segment]:
|
|
489
624
|
"""Convert an admonition-like container to a ``# LABEL:`` directive segment."""
|
|
490
|
-
inner_ctx = replace(ctx, in_see_also=True) if label.upper() == 'SEE ALSO' else ctx
|
|
491
625
|
inner: list[Segment] = [('text', [f'# {label}:'])]
|
|
492
626
|
for child in node.children:
|
|
493
627
|
if skip_first_title and isinstance(child, nodes.title):
|
|
494
628
|
continue
|
|
495
|
-
inner.extend(_convert_node(child,
|
|
629
|
+
inner.extend(_convert_node(child, ctx))
|
|
496
630
|
lines = _indent_label_content(_join_segments(inner), ctx.fmt)
|
|
497
631
|
return [('directive', lines)]
|
|
498
632
|
|
|
@@ -505,19 +639,20 @@ def _convert_node(node: nodes.Node, ctx: _RenderContext) -> list[Segment]:
|
|
|
505
639
|
return []
|
|
506
640
|
if isinstance(node, nodes.doctest_block):
|
|
507
641
|
return _convert_doctest_block(node)
|
|
508
|
-
if isinstance(node, nodes.literal_block):
|
|
642
|
+
if isinstance(node, nodes.literal_block) or _is_gallery_code_block(node):
|
|
509
643
|
return _convert_literal_block(node)
|
|
510
644
|
if type(node) in _ADMONITION_LABELS:
|
|
511
|
-
if _is_see_also_type(node)
|
|
645
|
+
if _is_see_also_type(node):
|
|
646
|
+
# "See Also" content is always dropped -- see _build_segments
|
|
647
|
+
# for the bare-rubric form and _is_see_also_section below for
|
|
648
|
+
# the hand-written-heading form.
|
|
512
649
|
return []
|
|
513
650
|
return _convert_admonition(node, _ADMONITION_LABELS[type(node)], ctx)
|
|
514
651
|
if _is_see_also_section(node):
|
|
515
652
|
# a hand-written "See Also\n--------" heading nests as a full
|
|
516
|
-
# section rather than a flat sibling -
|
|
517
|
-
#
|
|
518
|
-
|
|
519
|
-
return []
|
|
520
|
-
return _convert_admonition(node, 'SEE ALSO', ctx, skip_first_title=True)
|
|
653
|
+
# section rather than a flat sibling - dropped the same as any
|
|
654
|
+
# other "See Also" form.
|
|
655
|
+
return []
|
|
521
656
|
if isinstance(node, nodes.admonition):
|
|
522
657
|
# generic ``.. admonition:: Custom Title`` - use its own title as the label
|
|
523
658
|
title_node = node.next_node(nodes.title)
|
|
@@ -536,6 +671,8 @@ def _convert_node(node: nodes.Node, ctx: _RenderContext) -> list[Segment]:
|
|
|
536
671
|
return _definition_segment(node, ctx)
|
|
537
672
|
if isinstance(node, (nodes.bullet_list, nodes.enumerated_list)):
|
|
538
673
|
return _list_segment(node, ctx)
|
|
674
|
+
if isinstance(node, nodes.table):
|
|
675
|
+
return _table_segment(node, ctx)
|
|
539
676
|
if isinstance(node, (*_CONTAINER_TYPES, nodes.section)):
|
|
540
677
|
# nodes.section (gallery mode only): a sphinx-gallery ``# %%`` cell
|
|
541
678
|
# with its own RST heading is a *sibling* section at the document
|
|
@@ -594,42 +731,6 @@ def _is_see_also_type(node: nodes.Node) -> bool:
|
|
|
594
731
|
return type(node) in _ADMONITION_LABELS and _ADMONITION_LABELS[type(node)] == 'SEE ALSO'
|
|
595
732
|
|
|
596
733
|
|
|
597
|
-
def _see_also_in_desc_content(desc: addnodes.desc) -> nodes.Node | None:
|
|
598
|
-
"""Find a "See Also" admonition-type node among a ``desc`` node's own ``desc_content``."""
|
|
599
|
-
content = next(
|
|
600
|
-
(child for child in desc.children if isinstance(child, addnodes.desc_content)), None
|
|
601
|
-
)
|
|
602
|
-
if content is None:
|
|
603
|
-
return None
|
|
604
|
-
return next((child for child in content.children if _is_see_also_type(child)), None)
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
def _find_external_see_also(
|
|
608
|
-
parent: nodes.Element, start: int, end: int, heading: nodes.Node
|
|
609
|
-
) -> nodes.Node | None:
|
|
610
|
-
"""Find a "See Also" part sited outside the normal ``[start, end)`` span.
|
|
611
|
-
|
|
612
|
-
numpydoc's own "See Also" field is canonically reordered to sit before
|
|
613
|
-
"Examples", as a sibling under ``parent``. If "Examples" was written as
|
|
614
|
-
a real RST heading and hoisted out of its own ``desc_content`` to page
|
|
615
|
-
level (see ``_enclosing_descs``), "See Also" -- not a ``nodes.section``,
|
|
616
|
-
so untouched by that hoisting -- stays behind there instead, in the
|
|
617
|
-
``desc_content`` of the one ``desc`` that used to contain this heading.
|
|
618
|
-
Only that nearest enclosing ``desc`` is checked -- not every one
|
|
619
|
-
``_enclosing_descs`` yields -- or a page listing several documented
|
|
620
|
-
objects in a row would leak one object's "See Also" onto another's.
|
|
621
|
-
"""
|
|
622
|
-
for i, child in enumerate(parent.children):
|
|
623
|
-
if start <= i < end:
|
|
624
|
-
continue
|
|
625
|
-
if _is_see_also_type(child):
|
|
626
|
-
return child
|
|
627
|
-
desc = next(_enclosing_descs(heading), None)
|
|
628
|
-
if desc is not None:
|
|
629
|
-
return _see_also_in_desc_content(desc)
|
|
630
|
-
return None
|
|
631
|
-
|
|
632
|
-
|
|
633
734
|
def _examples_spans(doctree: nodes.document) -> list[tuple[nodes.Element, int, int, nodes.Node]]:
|
|
634
735
|
"""Find every "Examples" heading's content span.
|
|
635
736
|
|
|
@@ -868,20 +969,14 @@ def _build_segments(nodes_in_span: list[nodes.Node], ctx: _RenderContext) -> lis
|
|
|
868
969
|
"""Convert a span's nodes into segments.
|
|
869
970
|
|
|
870
971
|
A bare ``.. rubric:: See Also`` heading isn't wrapped in a container the
|
|
871
|
-
way ``.. seealso::`` or a nested section are
|
|
872
|
-
|
|
972
|
+
way ``.. seealso::`` or a nested section are, so everything after it is
|
|
973
|
+
just as much "See Also" content as the heading itself, with no node of
|
|
974
|
+
its own marking where it ends -- dropping the heading and stopping
|
|
975
|
+
there drops all of it.
|
|
873
976
|
"""
|
|
874
977
|
segments: list[Segment] = []
|
|
875
|
-
for
|
|
978
|
+
for node in nodes_in_span:
|
|
876
979
|
if isinstance(node, nodes.rubric) and _is_see_also_heading(node):
|
|
877
|
-
if not ctx.show_see_also:
|
|
878
|
-
break
|
|
879
|
-
inner_ctx = replace(ctx, in_see_also=True)
|
|
880
|
-
inner: list[Segment] = [('text', ['# SEE ALSO:'])]
|
|
881
|
-
for later_node in nodes_in_span[i + 1 :]:
|
|
882
|
-
inner.extend(_convert_node(later_node, inner_ctx))
|
|
883
|
-
lines = _indent_label_content(_join_segments(inner), ctx.fmt)
|
|
884
|
-
segments.append(('directive', lines))
|
|
885
980
|
break
|
|
886
981
|
segments.extend(_convert_node(node, ctx))
|
|
887
982
|
return segments
|
|
@@ -896,7 +991,6 @@ def _build_download_entries(
|
|
|
896
991
|
formats: list[str],
|
|
897
992
|
footer: str | None,
|
|
898
993
|
link_labels: dict[str, str],
|
|
899
|
-
include_see_also: bool,
|
|
900
994
|
) -> list[tuple[str, str]]:
|
|
901
995
|
"""Convert a span of nodes into written ``.py``/``.ipynb`` files, per ``formats``.
|
|
902
996
|
|
|
@@ -909,7 +1003,7 @@ def _build_download_entries(
|
|
|
909
1003
|
file's own header is the one heading every generated file has exactly
|
|
910
1004
|
one of (see ``_title_underline_segment``).
|
|
911
1005
|
"""
|
|
912
|
-
py_ctx = _RenderContext(app=app, docname=docname, fmt='py'
|
|
1006
|
+
py_ctx = _RenderContext(app=app, docname=docname, fmt='py')
|
|
913
1007
|
py_segments = _build_segments(nodes_in_span, py_ctx)
|
|
914
1008
|
|
|
915
1009
|
if not any(kind == 'code' for kind, _lines in py_segments):
|
|
@@ -930,9 +1024,7 @@ def _build_download_entries(
|
|
|
930
1024
|
if fmt == 'py':
|
|
931
1025
|
rel_path = _write_source(app, name, source)
|
|
932
1026
|
else:
|
|
933
|
-
ipynb_ctx = _RenderContext(
|
|
934
|
-
app=app, docname=docname, fmt='ipynb', show_see_also=include_see_also
|
|
935
|
-
)
|
|
1027
|
+
ipynb_ctx = _RenderContext(app=app, docname=docname, fmt='ipynb')
|
|
936
1028
|
ipynb_segments = _build_segments(nodes_in_span, ipynb_ctx)
|
|
937
1029
|
ipynb_header = _title_underline_segment(header_title, 1, 'ipynb')
|
|
938
1030
|
# Converted separately from the footer and concatenated, rather
|
|
@@ -958,14 +1050,9 @@ def _process_span(
|
|
|
958
1050
|
formats: list[str],
|
|
959
1051
|
footer: str | None,
|
|
960
1052
|
link_labels: dict[str, str],
|
|
961
|
-
include_see_also: bool,
|
|
962
1053
|
) -> None:
|
|
963
1054
|
"""Convert one Examples span and insert download link(s) if it has real code."""
|
|
964
1055
|
nodes_in_span = list(parent.children[start:end])
|
|
965
|
-
if include_see_also:
|
|
966
|
-
external_see_also = _find_external_see_also(parent, start, end, heading)
|
|
967
|
-
if external_see_also is not None:
|
|
968
|
-
nodes_in_span.append(external_see_also)
|
|
969
1056
|
|
|
970
1057
|
name = _qualified_name_for(heading, docname, counter)
|
|
971
1058
|
entries = _build_download_entries(
|
|
@@ -977,7 +1064,6 @@ def _process_span(
|
|
|
977
1064
|
formats,
|
|
978
1065
|
footer,
|
|
979
1066
|
link_labels,
|
|
980
|
-
include_see_also,
|
|
981
1067
|
)
|
|
982
1068
|
if not entries:
|
|
983
1069
|
return
|
|
@@ -1058,7 +1144,6 @@ def _process_gallery_page(
|
|
|
1058
1144
|
formats: list[str],
|
|
1059
1145
|
footer: str | None,
|
|
1060
1146
|
link_labels: dict[str, str],
|
|
1061
|
-
include_see_also: bool,
|
|
1062
1147
|
) -> None:
|
|
1063
1148
|
"""Replace a sphinx-gallery page's own download footer with converted downloads.
|
|
1064
1149
|
|
|
@@ -1101,7 +1186,6 @@ def _process_gallery_page(
|
|
|
1101
1186
|
formats,
|
|
1102
1187
|
footer,
|
|
1103
1188
|
link_labels,
|
|
1104
|
-
include_see_also,
|
|
1105
1189
|
)
|
|
1106
1190
|
if not entries:
|
|
1107
1191
|
return
|
|
@@ -1127,12 +1211,9 @@ def _process_doctree(app: Sphinx, doctree: nodes.document, docname: str) -> None
|
|
|
1127
1211
|
formats = conf['formats']
|
|
1128
1212
|
footer = conf['footer']
|
|
1129
1213
|
link_labels = conf['link_labels']
|
|
1130
|
-
include_see_also = conf['include_see_also']
|
|
1131
1214
|
|
|
1132
1215
|
if conf['gallery_downloads']:
|
|
1133
|
-
_process_gallery_page(
|
|
1134
|
-
app, docname, doctree, position, formats, footer, link_labels, include_see_also
|
|
1135
|
-
)
|
|
1216
|
+
_process_gallery_page(app, docname, doctree, position, formats, footer, link_labels)
|
|
1136
1217
|
|
|
1137
1218
|
# Process spans per shared parent, last to first.
|
|
1138
1219
|
spans = _examples_spans(doctree)
|
|
@@ -1152,7 +1233,6 @@ def _process_doctree(app: Sphinx, doctree: nodes.document, docname: str) -> None
|
|
|
1152
1233
|
formats,
|
|
1153
1234
|
footer,
|
|
1154
1235
|
link_labels,
|
|
1155
|
-
include_see_also,
|
|
1156
1236
|
)
|
|
1157
1237
|
|
|
1158
1238
|
|
|
@@ -1170,7 +1250,6 @@ _CONF_DEFAULTS: dict[str, object] = {
|
|
|
1170
1250
|
'gallery_downloads': False,
|
|
1171
1251
|
'footer': _DEFAULT_FOOTER,
|
|
1172
1252
|
'link_labels': _DEFAULT_LINK_LABELS,
|
|
1173
|
-
'include_see_also': True,
|
|
1174
1253
|
}
|
|
1175
1254
|
|
|
1176
1255
|
|
|
@@ -1188,7 +1267,7 @@ def _coerce_conf_value(key: str, value: object) -> object:
|
|
|
1188
1267
|
return value
|
|
1189
1268
|
if key == 'formats':
|
|
1190
1269
|
return value.split(',')
|
|
1191
|
-
if key
|
|
1270
|
+
if key == 'gallery_downloads':
|
|
1192
1271
|
if value == '0':
|
|
1193
1272
|
return False
|
|
1194
1273
|
if value == '1':
|
{sphinx_examples_as_code-0.4.1 → sphinx_examples_as_code-0.6.0}/sphinx_examples_as_code/_version.py
RENAMED
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.
|
|
22
|
-
__version_tuple__ = version_tuple = (0,
|
|
21
|
+
__version__ = version = '0.6.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 6, 0)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g7280fbdd2'
|