sphinx-examples-as-code 0.3.1__tar.gz → 0.3.3__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.3.1 → sphinx_examples_as_code-0.3.3}/PKG-INFO +21 -12
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/README.md +20 -11
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/sphinx_examples_as_code/__init__.py +74 -37
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/sphinx_examples_as_code/_version.py +3 -3
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/sphinx_examples_as_code.egg-info/PKG-INFO +21 -12
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/sphinx_examples_as_code.egg-info/scm_version.json +2 -2
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/tests/test_gallery_downloads.py +26 -23
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/tests/test_heading_level_reuse.py +13 -4
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/tests/test_sphinx_examples_as_code.py +134 -32
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/tests/test_tinypages.py +26 -10
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/.github/dependabot.yml +0 -0
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/.github/release.yml +0 -0
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/.github/workflows/ci.yml +0 -0
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/.gitignore +0 -0
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/.pre-commit-config.yaml +0 -0
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/LICENSE +0 -0
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/pyproject.toml +0 -0
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/setup.cfg +0 -0
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/sphinx_examples_as_code.egg-info/SOURCES.txt +0 -0
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/sphinx_examples_as_code.egg-info/dependency_links.txt +0 -0
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/sphinx_examples_as_code.egg-info/requires.txt +0 -0
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/sphinx_examples_as_code.egg-info/scm_file_list.json +0 -0
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/sphinx_examples_as_code.egg-info/top_level.txt +0 -0
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/tests/gallery_fixture/conf.py +0 -0
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/tests/gallery_fixture/examples/GALLERY_HEADER.rst +0 -0
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/tests/gallery_fixture/examples/plot_minimal.py +0 -0
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/tests/gallery_fixture/index.rst +0 -0
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/tests/heading_level_fixture/conf.py +0 -0
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/tests/heading_level_fixture/examples/GALLERY_HEADER.rst +0 -0
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/tests/heading_level_fixture/examples/plot_reused_level.py +0 -0
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/tests/heading_level_fixture/examples/plot_three_levels.py +0 -0
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/tests/heading_level_fixture/index.rst +0 -0
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/tests/single_function_fixture/conf.py +0 -0
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/tests/single_function_fixture/index.rst +0 -0
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/tests/single_function_fixture/mymodule.py +0 -0
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/tests/test_single_function_page.py +0 -0
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/tests/tinypages/conf.py +0 -0
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/tests/tinypages/docstring_cases.py +0 -0
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/tests/tinypages/docstring_cases.rst +0 -0
- {sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/tests/tinypages/index.rst +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sphinx-examples-as-code
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
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
|
|
@@ -107,9 +107,11 @@ What happens to the content of an Examples section:
|
|
|
107
107
|
- `.. code-block:: python` (or `py`) blocks are kept as-is; other languages become
|
|
108
108
|
comments, set off with blank lines on both sides like any other directive.
|
|
109
109
|
- Admonitions (`.. note::`, `.. warning::`, `.. seealso::`, ...) become a `# LABEL:`
|
|
110
|
-
comment followed by their content as comments
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
comment followed by their content as comments, indented one level under the label in
|
|
111
|
+
`.py` (not `.ipynb`, where leading whitespace within a paragraph has no visible effect
|
|
112
|
+
once rendered). "See Also" is recognized in any of its three forms (`.. seealso::`, a
|
|
113
|
+
bare `.. rubric:: See Also`, or a hand-written `See Also` heading) and always renders
|
|
114
|
+
the same way.
|
|
113
115
|
- Cross-references and inline code (`:class:`, `:meth:`, `:func:`, `:attr:`,
|
|
114
116
|
double-backtick literals, ...) keep their display text, wrapped in backticks (e.g.
|
|
115
117
|
``:class:`pyvista.Plotter` `` -> `` `pyvista.Plotter` ``). If `html_baseurl` is set and
|
|
@@ -154,14 +156,21 @@ Detection is automatic and per-page — any page without a sphinx-gallery downlo
|
|
|
154
156
|
is left completely untouched. Gallery pages and ordinary docstring/prose pages (using
|
|
155
157
|
the Examples-section behavior above) can coexist on the same site.
|
|
156
158
|
|
|
157
|
-
A `# %%` cell with its own RST heading gets the same
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
159
|
+
A `# %%` cell with its own RST heading gets the same header treatment as the file's own
|
|
160
|
+
title, and renders as a real Markdown heading in `.ipynb`. Level is relative to actual
|
|
161
|
+
RST section nesting, not always one below the file's own header: a cell heading nested
|
|
162
|
+
under the page's own title (the common case) is one level below it; one that reuses the
|
|
163
|
+
page title's own underline character is level 1, the same as the file's own header; a
|
|
164
|
+
cell heading nested under *that* is level 2 relative to it, and so on.
|
|
165
|
+
|
|
166
|
+
Each format uses one heading style consistently across every level, rather than mixing
|
|
167
|
+
styles:
|
|
168
|
+
|
|
169
|
+
- `.py` uses an RST-style title + underline, one character per level -- the same
|
|
170
|
+
sequence Sphinx's own documentation uses for sections through sub-paragraphs: `=`, `-`,
|
|
171
|
+
`~`, `^`, `"`, `'` for levels 1 through 6.
|
|
172
|
+
- `.ipynb` always uses ATX syntax (`#`, `##`, `###`, ...), since that's a real Markdown
|
|
173
|
+
heading at any level, whereas an underline only reads as one for the first two.
|
|
165
174
|
|
|
166
175
|
Two things worth knowing before turning this on:
|
|
167
176
|
|
|
@@ -80,9 +80,11 @@ What happens to the content of an Examples section:
|
|
|
80
80
|
- `.. code-block:: python` (or `py`) blocks are kept as-is; other languages become
|
|
81
81
|
comments, set off with blank lines on both sides like any other directive.
|
|
82
82
|
- Admonitions (`.. note::`, `.. warning::`, `.. seealso::`, ...) become a `# LABEL:`
|
|
83
|
-
comment followed by their content as comments
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
comment followed by their content as comments, indented one level under the label in
|
|
84
|
+
`.py` (not `.ipynb`, where leading whitespace within a paragraph has no visible effect
|
|
85
|
+
once rendered). "See Also" is recognized in any of its three forms (`.. seealso::`, a
|
|
86
|
+
bare `.. rubric:: See Also`, or a hand-written `See Also` heading) and always renders
|
|
87
|
+
the same way.
|
|
86
88
|
- Cross-references and inline code (`:class:`, `:meth:`, `:func:`, `:attr:`,
|
|
87
89
|
double-backtick literals, ...) keep their display text, wrapped in backticks (e.g.
|
|
88
90
|
``:class:`pyvista.Plotter` `` -> `` `pyvista.Plotter` ``). If `html_baseurl` is set and
|
|
@@ -127,14 +129,21 @@ Detection is automatic and per-page — any page without a sphinx-gallery downlo
|
|
|
127
129
|
is left completely untouched. Gallery pages and ordinary docstring/prose pages (using
|
|
128
130
|
the Examples-section behavior above) can coexist on the same site.
|
|
129
131
|
|
|
130
|
-
A `# %%` cell with its own RST heading gets the same
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
132
|
+
A `# %%` cell with its own RST heading gets the same header treatment as the file's own
|
|
133
|
+
title, and renders as a real Markdown heading in `.ipynb`. Level is relative to actual
|
|
134
|
+
RST section nesting, not always one below the file's own header: a cell heading nested
|
|
135
|
+
under the page's own title (the common case) is one level below it; one that reuses the
|
|
136
|
+
page title's own underline character is level 1, the same as the file's own header; a
|
|
137
|
+
cell heading nested under *that* is level 2 relative to it, and so on.
|
|
138
|
+
|
|
139
|
+
Each format uses one heading style consistently across every level, rather than mixing
|
|
140
|
+
styles:
|
|
141
|
+
|
|
142
|
+
- `.py` uses an RST-style title + underline, one character per level -- the same
|
|
143
|
+
sequence Sphinx's own documentation uses for sections through sub-paragraphs: `=`, `-`,
|
|
144
|
+
`~`, `^`, `"`, `'` for levels 1 through 6.
|
|
145
|
+
- `.ipynb` always uses ATX syntax (`#`, `##`, `###`, ...), since that's a real Markdown
|
|
146
|
+
heading at any level, whereas an underline only reads as one for the first two.
|
|
138
147
|
|
|
139
148
|
Two things worth knowing before turning this on:
|
|
140
149
|
|
{sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/sphinx_examples_as_code/__init__.py
RENAMED
|
@@ -368,29 +368,31 @@ def _convert_literal_block(node: nodes.literal_block) -> list[Segment]:
|
|
|
368
368
|
return [('directive', comment_lines)]
|
|
369
369
|
|
|
370
370
|
|
|
371
|
-
#:
|
|
372
|
-
#:
|
|
373
|
-
#:
|
|
374
|
-
|
|
375
|
-
_TITLE_UNDERLINE_CHARS = {1: '=', 2: '-'}
|
|
371
|
+
#: RST underline character per heading level, for ``.py`` output -- the
|
|
372
|
+
#: same sequence Sphinx's own documentation uses for sections, subsections,
|
|
373
|
+
#: subsubsections, paragraphs, and sub-paragraphs.
|
|
374
|
+
_TITLE_UNDERLINE_CHARS = {1: '=', 2: '-', 3: '~', 4: '^', 5: '"', 6: "'"}
|
|
376
375
|
|
|
376
|
+
#: Deepest heading level either heading style below can represent.
|
|
377
|
+
_MAX_HEADING_LEVEL = 6
|
|
377
378
|
|
|
378
|
-
|
|
379
|
+
|
|
380
|
+
def _title_underline_segment(title: str, level: int, fmt: str) -> Segment:
|
|
379
381
|
"""Build a heading directive segment for ``title`` at the given level.
|
|
380
382
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
ATX ``#`` syntax instead (e.g. ``# ### Title``), since
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
text.
|
|
383
|
+
Consistent within each format rather than mixed: ``.py`` always uses an
|
|
384
|
+
RST-style title + underline (e.g. ``# Title`` + ``# =====``), one
|
|
385
|
+
character per level (see ``_TITLE_UNDERLINE_CHARS``); ``.ipynb`` always
|
|
386
|
+
uses ATX ``#`` syntax instead (e.g. ``# ### Title``), since that's a
|
|
387
|
+
real Markdown heading at any level once the ``#`` comment prefix is
|
|
388
|
+
stripped for markdown cells, whereas an RST-style underline only reads
|
|
389
|
+
as a heading there for the first two levels.
|
|
389
390
|
"""
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
return ('directive', [f'# {
|
|
393
|
-
|
|
391
|
+
level = min(level, _MAX_HEADING_LEVEL)
|
|
392
|
+
if fmt == 'ipynb':
|
|
393
|
+
return ('directive', [f'# {"#" * level} {title}'])
|
|
394
|
+
underline = _TITLE_UNDERLINE_CHARS[level] * len(title)
|
|
395
|
+
return ('directive', [f'# {title}', f'# {underline}'])
|
|
394
396
|
|
|
395
397
|
|
|
396
398
|
def _heading_level(title: nodes.title) -> int:
|
|
@@ -399,15 +401,44 @@ def _heading_level(title: nodes.title) -> int:
|
|
|
399
401
|
Counts the title's own section and each ancestor ``nodes.section``.
|
|
400
402
|
Running this on the file's own header would give level 1; a title
|
|
401
403
|
nested inside that comes out level 2, one nested inside that comes out
|
|
402
|
-
level 3, and so on -- clamped to
|
|
403
|
-
heading syntax can represent.
|
|
404
|
+
level 3, and so on -- clamped to ``_MAX_HEADING_LEVEL``.
|
|
404
405
|
"""
|
|
405
406
|
level = 0
|
|
406
407
|
node: nodes.Node | None = title.parent
|
|
407
408
|
while isinstance(node, nodes.section):
|
|
408
409
|
level += 1
|
|
409
410
|
node = node.parent
|
|
410
|
-
return min(level,
|
|
411
|
+
return min(level, _MAX_HEADING_LEVEL)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
#: Extra indent for an admonition's content, under its ``# LABEL:`` line --
|
|
415
|
+
#: ``.py`` only (see ``_convert_admonition``).
|
|
416
|
+
_ADMONITION_CONTENT_INDENT = ' '
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def _indent_comment_line(line: str) -> str:
|
|
420
|
+
"""Indent a ``# text`` comment line's content by ``_ADMONITION_CONTENT_INDENT``.
|
|
421
|
+
|
|
422
|
+
A bare ``#`` (blank line within a comment block) or a blank separator
|
|
423
|
+
line (``''``) has no content to indent, so both pass through unchanged
|
|
424
|
+
-- as does a raw code line (no ``#`` prefix at all), left alone so
|
|
425
|
+
indenting an admonition's content never touches real Python source.
|
|
426
|
+
"""
|
|
427
|
+
if not line.startswith('# '):
|
|
428
|
+
return line
|
|
429
|
+
return f'# {_ADMONITION_CONTENT_INDENT}{line.removeprefix("# ")}'
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def _indent_label_content(lines: list[str], fmt: str) -> list[str]:
|
|
433
|
+
"""Indent every line after the first (a ``# LABEL:`` line) under it.
|
|
434
|
+
|
|
435
|
+
``.py`` only: in ``.ipynb``, once rendered as markdown, leading
|
|
436
|
+
whitespace within a paragraph collapses to nothing (confirmed against a
|
|
437
|
+
real CommonMark parse), so it would be invisible there anyway.
|
|
438
|
+
"""
|
|
439
|
+
if fmt != 'py' or not lines:
|
|
440
|
+
return lines
|
|
441
|
+
return [lines[0], *(_indent_comment_line(line) for line in lines[1:])]
|
|
411
442
|
|
|
412
443
|
|
|
413
444
|
def _convert_admonition(
|
|
@@ -420,7 +451,8 @@ def _convert_admonition(
|
|
|
420
451
|
if skip_first_title and isinstance(child, nodes.title):
|
|
421
452
|
continue
|
|
422
453
|
inner.extend(_convert_node(child, inner_ctx))
|
|
423
|
-
|
|
454
|
+
lines = _indent_label_content(_join_segments(inner), ctx.fmt)
|
|
455
|
+
return [('directive', lines)]
|
|
424
456
|
|
|
425
457
|
|
|
426
458
|
def _convert_node(node: nodes.Node, ctx: _RenderContext) -> list[Segment]:
|
|
@@ -453,7 +485,7 @@ def _convert_node(node: nodes.Node, ctx: _RenderContext) -> list[Segment]:
|
|
|
453
485
|
# _heading_level).
|
|
454
486
|
title_text = _render_inline(node, ctx).strip()
|
|
455
487
|
level = _heading_level(node)
|
|
456
|
-
return [_title_underline_segment(title_text, level
|
|
488
|
+
return [_title_underline_segment(title_text, level, ctx.fmt)] if title_text else []
|
|
457
489
|
if isinstance(node, (*_CONTAINER_TYPES, nodes.section)):
|
|
458
490
|
# nodes.section (gallery mode only): a sphinx-gallery ``# %%`` cell
|
|
459
491
|
# with its own RST heading becomes a *sibling* section at the
|
|
@@ -577,9 +609,9 @@ def _qualified_name_for(node: nodes.Node, docname: str, counter: int) -> str:
|
|
|
577
609
|
return f'{base}-example-{counter}'
|
|
578
610
|
|
|
579
611
|
|
|
580
|
-
def
|
|
581
|
-
"""Build the
|
|
582
|
-
return
|
|
612
|
+
def _header_title(qualified_name: str) -> str:
|
|
613
|
+
"""Build the file's own header title text, e.g. ``Examples from pyvista.read``."""
|
|
614
|
+
return f'Examples from {qualified_name}'
|
|
583
615
|
|
|
584
616
|
|
|
585
617
|
# A plain divider, not tied to any specific text's length the way a title
|
|
@@ -596,8 +628,8 @@ def _footer_segment(footer: str | None, fmt: str, app: Sphinx, docname: str) ->
|
|
|
596
628
|
"""Build the footer directive segment, if a footer is configured.
|
|
597
629
|
|
|
598
630
|
Empty (0 or 1 elements, not a bare ``Segment | None``) so callers can
|
|
599
|
-
just splat it into a segment list
|
|
600
|
-
|
|
631
|
+
just splat it into a segment list. Starts with ``_FOOTER_SEPARATOR``
|
|
632
|
+
-- see its comment for why.
|
|
601
633
|
|
|
602
634
|
Parsed as RST -- the same way any other prose in this extension is --
|
|
603
635
|
rather than treated as a special case: a hyperlink written as
|
|
@@ -781,7 +813,8 @@ def _build_segments(nodes_in_span: list[nodes.Node], ctx: _RenderContext) -> lis
|
|
|
781
813
|
inner: list[Segment] = [('text', ['# SEE ALSO:'])]
|
|
782
814
|
for later_node in nodes_in_span[i + 1 :]:
|
|
783
815
|
inner.extend(_convert_node(later_node, inner_ctx))
|
|
784
|
-
|
|
816
|
+
lines = _indent_label_content(_join_segments(inner), ctx.fmt)
|
|
817
|
+
segments.append(('directive', lines))
|
|
785
818
|
break
|
|
786
819
|
segments.extend(_convert_node(node, ctx))
|
|
787
820
|
return segments
|
|
@@ -791,7 +824,7 @@ def _build_download_entries(
|
|
|
791
824
|
app: Sphinx,
|
|
792
825
|
docname: str,
|
|
793
826
|
name: str,
|
|
794
|
-
|
|
827
|
+
header_title: str,
|
|
795
828
|
nodes_in_span: list[nodes.Node],
|
|
796
829
|
formats: list[str],
|
|
797
830
|
footer: str | None,
|
|
@@ -803,8 +836,10 @@ def _build_download_entries(
|
|
|
803
836
|
requested -- empty if the span has no real code, or ``formats`` itself
|
|
804
837
|
is empty. Shared by the docstring-Examples path and gallery-page path;
|
|
805
838
|
the only differences between them are how ``nodes_in_span``/``name``
|
|
806
|
-
get built, and what ``
|
|
807
|
-
``_process_gallery_page``'s own title handling).
|
|
839
|
+
get built, and what ``header_title`` says (see ``_header_title`` vs.
|
|
840
|
+
``_process_gallery_page``'s own title handling). Always level 1 -- the
|
|
841
|
+
file's own header is the one heading every generated file has exactly
|
|
842
|
+
one of (see ``_title_underline_segment``).
|
|
808
843
|
"""
|
|
809
844
|
py_ctx = _RenderContext(app=app, docname=docname, fmt='py')
|
|
810
845
|
py_segments = _build_segments(nodes_in_span, py_ctx)
|
|
@@ -812,7 +847,8 @@ def _build_download_entries(
|
|
|
812
847
|
if not any(kind == 'code' for kind, _lines in py_segments):
|
|
813
848
|
return []
|
|
814
849
|
|
|
815
|
-
|
|
850
|
+
py_header = _title_underline_segment(header_title, 1, 'py')
|
|
851
|
+
py_segments_full = [py_header, *py_segments, *_footer_segment(footer, 'py', app, docname)]
|
|
816
852
|
source = '\n'.join(_join_segments(py_segments_full)).rstrip() + '\n\n'
|
|
817
853
|
|
|
818
854
|
if not _has_real_code(source):
|
|
@@ -828,6 +864,7 @@ def _build_download_entries(
|
|
|
828
864
|
else:
|
|
829
865
|
ipynb_ctx = _RenderContext(app=app, docname=docname, fmt='ipynb')
|
|
830
866
|
ipynb_segments = _build_segments(nodes_in_span, ipynb_ctx)
|
|
867
|
+
ipynb_header = _title_underline_segment(header_title, 1, 'ipynb')
|
|
831
868
|
# Converted separately from the footer and concatenated, rather
|
|
832
869
|
# than joined into one segment list and converted together:
|
|
833
870
|
# _segments_to_cells only starts a new cell when the kind
|
|
@@ -835,7 +872,7 @@ def _build_download_entries(
|
|
|
835
872
|
# following non-code content (e.g. a trailing admonition) would
|
|
836
873
|
# otherwise share its cell instead of always getting one of its
|
|
837
874
|
# own.
|
|
838
|
-
cells = _segments_to_cells([
|
|
875
|
+
cells = _segments_to_cells([ipynb_header, *ipynb_segments])
|
|
839
876
|
cells.extend(_segments_to_cells(_footer_segment(footer, 'ipynb', app, docname)))
|
|
840
877
|
rel_path = _write_notebook(app, name, _build_notebook(cells))
|
|
841
878
|
entries.append((label, rel_path))
|
|
@@ -864,7 +901,7 @@ def _process_span(
|
|
|
864
901
|
|
|
865
902
|
name = _qualified_name_for(heading, docname, counter)
|
|
866
903
|
entries = _build_download_entries(
|
|
867
|
-
app, docname, name,
|
|
904
|
+
app, docname, name, _header_title(name), nodes_in_span, formats, footer, link_labels
|
|
868
905
|
)
|
|
869
906
|
if not entries:
|
|
870
907
|
return
|
|
@@ -984,9 +1021,9 @@ def _process_gallery_page(
|
|
|
984
1021
|
# not an Examples section carved out of a larger docstring, so there's
|
|
985
1022
|
# no "from" framing to make. Falls back to the usual header on the off
|
|
986
1023
|
# chance the page has no title of its own.
|
|
987
|
-
|
|
1024
|
+
header_title = title if title else _header_title(name)
|
|
988
1025
|
entries = _build_download_entries(
|
|
989
|
-
app, docname, name,
|
|
1026
|
+
app, docname, name, header_title, nodes_in_span, formats, footer, link_labels
|
|
990
1027
|
)
|
|
991
1028
|
if not entries:
|
|
992
1029
|
return
|
{sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/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.3.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 3,
|
|
21
|
+
__version__ = version = '0.3.3'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 3, 3)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g2a42d27a0'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sphinx-examples-as-code
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
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
|
|
@@ -107,9 +107,11 @@ What happens to the content of an Examples section:
|
|
|
107
107
|
- `.. code-block:: python` (or `py`) blocks are kept as-is; other languages become
|
|
108
108
|
comments, set off with blank lines on both sides like any other directive.
|
|
109
109
|
- Admonitions (`.. note::`, `.. warning::`, `.. seealso::`, ...) become a `# LABEL:`
|
|
110
|
-
comment followed by their content as comments
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
comment followed by their content as comments, indented one level under the label in
|
|
111
|
+
`.py` (not `.ipynb`, where leading whitespace within a paragraph has no visible effect
|
|
112
|
+
once rendered). "See Also" is recognized in any of its three forms (`.. seealso::`, a
|
|
113
|
+
bare `.. rubric:: See Also`, or a hand-written `See Also` heading) and always renders
|
|
114
|
+
the same way.
|
|
113
115
|
- Cross-references and inline code (`:class:`, `:meth:`, `:func:`, `:attr:`,
|
|
114
116
|
double-backtick literals, ...) keep their display text, wrapped in backticks (e.g.
|
|
115
117
|
``:class:`pyvista.Plotter` `` -> `` `pyvista.Plotter` ``). If `html_baseurl` is set and
|
|
@@ -154,14 +156,21 @@ Detection is automatic and per-page — any page without a sphinx-gallery downlo
|
|
|
154
156
|
is left completely untouched. Gallery pages and ordinary docstring/prose pages (using
|
|
155
157
|
the Examples-section behavior above) can coexist on the same site.
|
|
156
158
|
|
|
157
|
-
A `# %%` cell with its own RST heading gets the same
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
159
|
+
A `# %%` cell with its own RST heading gets the same header treatment as the file's own
|
|
160
|
+
title, and renders as a real Markdown heading in `.ipynb`. Level is relative to actual
|
|
161
|
+
RST section nesting, not always one below the file's own header: a cell heading nested
|
|
162
|
+
under the page's own title (the common case) is one level below it; one that reuses the
|
|
163
|
+
page title's own underline character is level 1, the same as the file's own header; a
|
|
164
|
+
cell heading nested under *that* is level 2 relative to it, and so on.
|
|
165
|
+
|
|
166
|
+
Each format uses one heading style consistently across every level, rather than mixing
|
|
167
|
+
styles:
|
|
168
|
+
|
|
169
|
+
- `.py` uses an RST-style title + underline, one character per level -- the same
|
|
170
|
+
sequence Sphinx's own documentation uses for sections through sub-paragraphs: `=`, `-`,
|
|
171
|
+
`~`, `^`, `"`, `'` for levels 1 through 6.
|
|
172
|
+
- `.ipynb` always uses ATX syntax (`#`, `##`, `###`, ...), since that's a real Markdown
|
|
173
|
+
heading at any level, whereas an underline only reads as one for the first two.
|
|
165
174
|
|
|
166
175
|
Two things worth knowing before turning this on:
|
|
167
176
|
|
{sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/tests/test_gallery_downloads.py
RENAMED
|
@@ -68,26 +68,29 @@ def _is_ours(path: Path) -> bool:
|
|
|
68
68
|
"""Distinguish our generated file from sphinx-gallery's own same-named one.
|
|
69
69
|
|
|
70
70
|
Both write a ``plot_minimal.py``/``.ipynb`` under ``_downloads/<digest>/``
|
|
71
|
-
-- ours is identifiable structurally, by the
|
|
72
|
-
extension always starts a generated file with
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
-- ours is identifiable structurally, by the level-1 header this
|
|
72
|
+
extension always starts a generated file with: a title line followed by
|
|
73
|
+
a line of ``=`` matching that title's length in ``.py``, or a single
|
|
74
|
+
``# Title`` (ATX level 1) line as the first markdown cell's first line
|
|
75
|
+
in ``.ipynb`` (see ``_title_underline_segment``).
|
|
75
76
|
"""
|
|
76
77
|
if path.suffix == '.py':
|
|
77
78
|
lines = path.read_text(encoding='utf-8').splitlines()
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if len(lines) < 2:
|
|
79
|
+
if len(lines) < 2:
|
|
80
|
+
return False
|
|
81
|
+
title, underline = lines[0].removeprefix('# '), lines[1].removeprefix('# ')
|
|
82
|
+
return bool(title) and underline == '=' * len(title)
|
|
83
|
+
|
|
84
|
+
cells = json.loads(path.read_text(encoding='utf-8'))['cells']
|
|
85
|
+
if not cells or not cells[0]['source']:
|
|
86
86
|
return False
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
# sphinx-gallery's own native notebook also titles its first cell
|
|
88
|
+
# "# <title>" (plain ATX, no hard break) -- ours is only distinguishable
|
|
89
|
+
# by the trailing two-space hard break _cell_source always adds
|
|
90
|
+
raw_first_line = cells[0]['source'][0]
|
|
91
|
+
first_line = raw_first_line.rstrip()
|
|
92
|
+
has_hard_break = raw_first_line.rstrip('\n').endswith(' ')
|
|
93
|
+
return has_hard_break and first_line.startswith('# ') and not first_line.startswith('# #')
|
|
91
94
|
|
|
92
95
|
|
|
93
96
|
def test_gallery_downloads_disabled_by_default(tmp_path: Path):
|
|
@@ -221,7 +224,7 @@ def test_gallery_downloads_footer_is_its_own_dedicated_ipynb_cell(gallery_build:
|
|
|
221
224
|
def test_gallery_downloads_sibling_section_heading_is_markdown_in_ipynb(
|
|
222
225
|
gallery_build: tuple[Path, str],
|
|
223
226
|
):
|
|
224
|
-
"""The sibling section's heading is a level-2 (
|
|
227
|
+
"""The sibling section's heading is a level-2 (``##``) Markdown heading."""
|
|
225
228
|
html_dir, _html = gallery_build
|
|
226
229
|
notebook = json.loads(_generated(html_dir, '.ipynb').read_text(encoding='utf-8'))
|
|
227
230
|
heading_cell = next(
|
|
@@ -229,20 +232,20 @@ def test_gallery_downloads_sibling_section_heading_is_markdown_in_ipynb(
|
|
|
229
232
|
for c in notebook['cells']
|
|
230
233
|
if c['cell_type'] == 'markdown' and 'A headed cell' in ''.join(c['source'])
|
|
231
234
|
)
|
|
232
|
-
|
|
233
|
-
assert '
|
|
235
|
+
lines = [line.rstrip() for line in heading_cell['source']]
|
|
236
|
+
assert '## A headed cell' in lines
|
|
234
237
|
|
|
235
238
|
|
|
236
239
|
def test_gallery_downloads_page_title_is_level_1_sibling_heading_is_level_2(
|
|
237
240
|
gallery_build: tuple[Path, str],
|
|
238
241
|
):
|
|
239
|
-
"""The page's own title (
|
|
242
|
+
"""The page's own title (``#``) outranks a sibling section's heading (``##``)."""
|
|
240
243
|
html_dir, _html = gallery_build
|
|
241
244
|
notebook = json.loads(_generated(html_dir, '.ipynb').read_text(encoding='utf-8'))
|
|
242
245
|
title_cell = notebook['cells'][0]
|
|
243
|
-
|
|
244
|
-
assert '
|
|
245
|
-
assert '
|
|
246
|
+
lines = [line.rstrip() for line in title_cell['source']]
|
|
247
|
+
assert '# A minimal gallery example' in lines
|
|
248
|
+
assert '## A minimal gallery example' not in lines
|
|
246
249
|
|
|
247
250
|
|
|
248
251
|
def test_gallery_downloads_generated_py_executes(gallery_build: tuple[Path, str]):
|
{sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/tests/test_heading_level_reuse.py
RENAMED
|
@@ -58,8 +58,16 @@ def test_reused_underline_char_renders_as_level_1(built: Path):
|
|
|
58
58
|
assert lines[reused_idx + 1] == '# ' + '=' * len('Reused Top Level')
|
|
59
59
|
|
|
60
60
|
|
|
61
|
-
def
|
|
62
|
-
|
|
61
|
+
def test_reused_underline_char_renders_as_level_1_in_ipynb(built: Path):
|
|
62
|
+
nb_path = _generated(built, 'plot_reused_level.ipynb')
|
|
63
|
+
notebook = json.loads(nb_path.read_text(encoding='utf-8'))
|
|
64
|
+
all_lines = [line.rstrip() for cell in notebook['cells'] for line in cell['source']]
|
|
65
|
+
assert '## Nested Subsection' in all_lines
|
|
66
|
+
assert '# Reused Top Level' in all_lines
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_three_genuine_levels_py_uses_tilde_for_the_third(built: Path):
|
|
70
|
+
"""A subsection nested two sections deep gets a ``~`` underline, not another dash line.
|
|
63
71
|
|
|
64
72
|
Otherwise it would render identically to its own parent section's
|
|
65
73
|
heading -- indistinguishable from a sibling instead of a subsection.
|
|
@@ -70,11 +78,12 @@ def test_three_genuine_levels_uses_atx_for_the_third(built: Path):
|
|
|
70
78
|
section_idx = lines.index('# Section')
|
|
71
79
|
assert lines[section_idx + 1] == '# ' + '-' * len('Section')
|
|
72
80
|
|
|
73
|
-
|
|
74
|
-
assert
|
|
81
|
+
subsection_idx = lines.index('# Subsection')
|
|
82
|
+
assert lines[subsection_idx + 1] == '# ' + '~' * len('Subsection')
|
|
75
83
|
|
|
76
84
|
|
|
77
85
|
def test_three_genuine_levels_atx_renders_as_h3_in_ipynb(built: Path):
|
|
86
|
+
"""In .ipynb, every level uses ATX -- the third level is a plain ``###`` line."""
|
|
78
87
|
nb_path = _generated(built, 'plot_three_levels.ipynb')
|
|
79
88
|
notebook = json.loads(nb_path.read_text(encoding='utf-8'))
|
|
80
89
|
all_source = ''.join(''.join(cell['source']) for cell in notebook['cells'])
|
|
@@ -444,6 +444,47 @@ def test_clean_code_comment_replaces_non_ascii_chars(unicode_char: int, ascii_ch
|
|
|
444
444
|
assert seac._clean_code_comment(line) == f'# comment {ascii_char} text'
|
|
445
445
|
|
|
446
446
|
|
|
447
|
+
# ---------------------------------------------------------------------------
|
|
448
|
+
# _indent_comment_line / _indent_label_content
|
|
449
|
+
# ---------------------------------------------------------------------------
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def test_indent_comment_line_indents_content():
|
|
453
|
+
assert seac._indent_comment_line('# hello') == '# hello'
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def test_indent_comment_line_bare_hash_unchanged():
|
|
457
|
+
assert seac._indent_comment_line('#') == '#'
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def test_indent_comment_line_blank_separator_unchanged():
|
|
461
|
+
assert seac._indent_comment_line('') == ''
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def test_indent_comment_line_code_line_unchanged():
|
|
465
|
+
# no '#' prefix at all -- indenting it would change real Python source
|
|
466
|
+
assert seac._indent_comment_line('x = 1') == 'x = 1'
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def test_indent_label_content_py_indents_everything_after_the_first_line():
|
|
470
|
+
lines = ['# NOTE:', '# hello', '# world']
|
|
471
|
+
assert seac._indent_label_content(lines, 'py') == ['# NOTE:', '# hello', '# world']
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def test_indent_label_content_ipynb_unchanged():
|
|
475
|
+
lines = ['# NOTE:', '# hello', '# world']
|
|
476
|
+
assert seac._indent_label_content(lines, 'ipynb') == lines
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def test_indent_label_content_empty_list_unchanged():
|
|
480
|
+
assert seac._indent_label_content([], 'py') == []
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def test_indent_label_content_skips_only_the_first_line():
|
|
484
|
+
# a lone label with no content: nothing after it to indent
|
|
485
|
+
assert seac._indent_label_content(['# NOTE:'], 'py') == ['# NOTE:']
|
|
486
|
+
|
|
487
|
+
|
|
447
488
|
# ---------------------------------------------------------------------------
|
|
448
489
|
# _convert_admonition / _convert_node dispatch
|
|
449
490
|
# ---------------------------------------------------------------------------
|
|
@@ -451,7 +492,19 @@ def test_clean_code_comment_replaces_non_ascii_chars(unicode_char: int, ascii_ch
|
|
|
451
492
|
|
|
452
493
|
def test_convert_node_note():
|
|
453
494
|
doctree = _parse('.. note::\n\n hello')
|
|
454
|
-
assert seac._convert_node(doctree[0], _ctx()) == [
|
|
495
|
+
assert seac._convert_node(doctree[0], _ctx(fmt='py')) == [
|
|
496
|
+
('directive', ['# NOTE:', '# hello'])
|
|
497
|
+
]
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def test_convert_node_note_ipynb_not_indented():
|
|
501
|
+
# unlike .py, .ipynb content isn't indented under the label -- once
|
|
502
|
+
# rendered as markdown, leading whitespace within a paragraph collapses
|
|
503
|
+
# to nothing anyway
|
|
504
|
+
doctree = _parse('.. note::\n\n hello')
|
|
505
|
+
assert seac._convert_node(doctree[0], _ctx(fmt='ipynb')) == [
|
|
506
|
+
('directive', ['# NOTE:', '# hello'])
|
|
507
|
+
]
|
|
455
508
|
|
|
456
509
|
|
|
457
510
|
def test_convert_node_seealso():
|
|
@@ -462,8 +515,8 @@ def test_convert_node_seealso():
|
|
|
462
515
|
p2 += nodes.Text('More info')
|
|
463
516
|
node += p1
|
|
464
517
|
node += p2
|
|
465
|
-
assert seac._convert_node(node, _ctx()) == [
|
|
466
|
-
('directive', ['# SEE ALSO:', '#
|
|
518
|
+
assert seac._convert_node(node, _ctx(fmt='py')) == [
|
|
519
|
+
('directive', ['# SEE ALSO:', '# See X', '# More info'])
|
|
467
520
|
]
|
|
468
521
|
|
|
469
522
|
|
|
@@ -478,7 +531,10 @@ def test_convert_node_seealso_propagates_in_see_also_to_references():
|
|
|
478
531
|
ctx = _ctx(fmt='py', base_url='https://docs.pyvista.org/')
|
|
479
532
|
segments = seac._convert_node(node, ctx)
|
|
480
533
|
assert segments == [
|
|
481
|
-
(
|
|
534
|
+
(
|
|
535
|
+
'directive',
|
|
536
|
+
['# SEE ALSO:', '# pyvista.Plotter https://docs.pyvista.org/plotter.html'],
|
|
537
|
+
)
|
|
482
538
|
]
|
|
483
539
|
|
|
484
540
|
|
|
@@ -490,8 +546,8 @@ def test_convert_node_see_also_section_treated_like_admonition():
|
|
|
490
546
|
|
|
491
547
|
def test_convert_node_generic_admonition_uses_title():
|
|
492
548
|
doctree = _parse('.. admonition:: Custom Title\n\n body text')
|
|
493
|
-
assert seac._convert_node(doctree[0], _ctx()) == [
|
|
494
|
-
('directive', ['# Custom Title:', '#
|
|
549
|
+
assert seac._convert_node(doctree[0], _ctx(fmt='py')) == [
|
|
550
|
+
('directive', ['# Custom Title:', '# body text'])
|
|
495
551
|
]
|
|
496
552
|
|
|
497
553
|
|
|
@@ -500,7 +556,7 @@ def test_convert_node_generic_admonition_no_title_defaults_to_note():
|
|
|
500
556
|
p = nodes.paragraph()
|
|
501
557
|
p += nodes.Text('body')
|
|
502
558
|
node += p
|
|
503
|
-
assert seac._convert_node(node, _ctx()) == [('directive', ['# NOTE:', '#
|
|
559
|
+
assert seac._convert_node(node, _ctx(fmt='py')) == [('directive', ['# NOTE:', '# body'])]
|
|
504
560
|
|
|
505
561
|
|
|
506
562
|
def test_convert_node_admonition_with_no_body_keeps_label():
|
|
@@ -508,6 +564,16 @@ def test_convert_node_admonition_with_no_body_keeps_label():
|
|
|
508
564
|
assert seac._convert_node(node, _ctx()) == [('directive', ['# NOTE:'])]
|
|
509
565
|
|
|
510
566
|
|
|
567
|
+
def test_convert_node_note_with_code_indents_text_not_code():
|
|
568
|
+
# a doctest example inside a note: the surrounding prose is indented
|
|
569
|
+
# under the label, but the code itself is left flush -- indenting it
|
|
570
|
+
# would be invalid Python (it isn't actually nested inside anything)
|
|
571
|
+
doctree = _parse('.. note::\n\n See below.\n\n >>> x = 1')
|
|
572
|
+
assert seac._convert_node(doctree[0], _ctx(fmt='py')) == [
|
|
573
|
+
('directive', ['# NOTE:', '# See below.', 'x = 1'])
|
|
574
|
+
]
|
|
575
|
+
|
|
576
|
+
|
|
511
577
|
def test_convert_node_skip_subtree_class():
|
|
512
578
|
node = nodes.container(classes=['sd-dropdown'])
|
|
513
579
|
p = nodes.paragraph()
|
|
@@ -747,38 +813,43 @@ def test_qualified_name_for_examples_section_sibling_no_desc_falls_back():
|
|
|
747
813
|
# ---------------------------------------------------------------------------
|
|
748
814
|
|
|
749
815
|
|
|
750
|
-
def
|
|
751
|
-
|
|
752
|
-
assert kind == 'directive'
|
|
753
|
-
assert lines[0] == '# Examples from pyvista.read'
|
|
754
|
-
assert lines[1] == '# ' + '=' * len('Examples from pyvista.read')
|
|
816
|
+
def test_header_title_format():
|
|
817
|
+
assert seac._header_title('pyvista.read') == 'Examples from pyvista.read'
|
|
755
818
|
|
|
756
819
|
|
|
757
820
|
# ---------------------------------------------------------------------------
|
|
758
821
|
# _title_underline_segment
|
|
759
822
|
# ---------------------------------------------------------------------------
|
|
760
823
|
|
|
824
|
+
# The full level -> underline character convention, for .py -- same
|
|
825
|
+
# sequence Sphinx's own documentation uses for sections through
|
|
826
|
+
# sub-paragraphs.
|
|
827
|
+
_LEVEL_UNDERLINE_CHARS = {1: '=', 2: '-', 3: '~', 4: '^', 5: '"', 6: "'"}
|
|
828
|
+
|
|
761
829
|
|
|
762
|
-
|
|
763
|
-
|
|
830
|
+
@pytest.mark.parametrize('level', [1, 2, 3, 4, 5, 6])
|
|
831
|
+
def test_title_underline_segment_py_uses_level_specific_underline(level):
|
|
832
|
+
kind, lines = seac._title_underline_segment('A Title', level, 'py')
|
|
764
833
|
assert kind == 'directive'
|
|
765
|
-
|
|
834
|
+
char = _LEVEL_UNDERLINE_CHARS[level]
|
|
835
|
+
assert lines == ['# A Title', '# ' + char * len('A Title')]
|
|
766
836
|
|
|
767
837
|
|
|
768
|
-
|
|
769
|
-
|
|
838
|
+
@pytest.mark.parametrize('level', [1, 2, 3, 4, 5, 6])
|
|
839
|
+
def test_title_underline_segment_ipynb_always_uses_atx(level):
|
|
840
|
+
kind, lines = seac._title_underline_segment('A Title', level, 'ipynb')
|
|
770
841
|
assert kind == 'directive'
|
|
771
|
-
assert lines == ['# A Title'
|
|
842
|
+
assert lines == [f'# {"#" * level} A Title']
|
|
772
843
|
|
|
773
844
|
|
|
774
|
-
def
|
|
775
|
-
kind, lines = seac._title_underline_segment('A Title',
|
|
845
|
+
def test_title_underline_segment_py_level_beyond_6_clamped():
|
|
846
|
+
kind, lines = seac._title_underline_segment('A Title', 9, 'py')
|
|
776
847
|
assert kind == 'directive'
|
|
777
|
-
assert lines == ['#
|
|
848
|
+
assert lines == ['# A Title', "# '''''''"]
|
|
778
849
|
|
|
779
850
|
|
|
780
|
-
def
|
|
781
|
-
kind, lines = seac._title_underline_segment('A Title',
|
|
851
|
+
def test_title_underline_segment_ipynb_level_beyond_6_clamped():
|
|
852
|
+
kind, lines = seac._title_underline_segment('A Title', 9, 'ipynb')
|
|
782
853
|
assert kind == 'directive'
|
|
783
854
|
assert lines == ['# ###### A Title']
|
|
784
855
|
|
|
@@ -841,39 +912,70 @@ def test_heading_level_reused_top_level_char_then_nested_subsection():
|
|
|
841
912
|
assert seac._heading_level(title) == 2
|
|
842
913
|
|
|
843
914
|
|
|
844
|
-
def
|
|
915
|
+
def _nest_title_one_deep(text: str) -> nodes.title:
|
|
845
916
|
# nested one section inside a page's own title section -- the common
|
|
846
917
|
# case for a gallery sibling section's heading
|
|
847
918
|
page_section = nodes.section()
|
|
848
919
|
section = nodes.section()
|
|
849
|
-
title = nodes.title('',
|
|
920
|
+
title = nodes.title('', text)
|
|
850
921
|
section += title
|
|
851
922
|
page_section += section
|
|
852
|
-
|
|
923
|
+
return title
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
def test_convert_node_title_uses_underline_treatment():
|
|
927
|
+
title = _nest_title_one_deep('A subsection')
|
|
928
|
+
assert seac._convert_node(title, _ctx(fmt='py')) == [
|
|
853
929
|
('directive', ['# A subsection', '# ------------'])
|
|
854
930
|
]
|
|
855
931
|
|
|
856
932
|
|
|
933
|
+
def test_convert_node_title_one_deep_ipynb_uses_atx():
|
|
934
|
+
title = _nest_title_one_deep('A subsection')
|
|
935
|
+
assert seac._convert_node(title, _ctx(fmt='ipynb')) == [('directive', ['# ## A subsection'])]
|
|
936
|
+
|
|
937
|
+
|
|
857
938
|
def test_convert_node_title_not_nested_uses_level_1():
|
|
858
939
|
section = nodes.section()
|
|
859
940
|
title = nodes.title('', 'A subsection')
|
|
860
941
|
section += title
|
|
861
|
-
assert seac._convert_node(title, _ctx()) == [
|
|
942
|
+
assert seac._convert_node(title, _ctx(fmt='py')) == [
|
|
862
943
|
('directive', ['# A subsection', '# ============'])
|
|
863
944
|
]
|
|
864
945
|
|
|
865
946
|
|
|
866
|
-
def
|
|
947
|
+
def test_convert_node_title_not_nested_ipynb_uses_atx():
|
|
948
|
+
section = nodes.section()
|
|
949
|
+
title = nodes.title('', 'A subsection')
|
|
950
|
+
section += title
|
|
951
|
+
assert seac._convert_node(title, _ctx(fmt='ipynb')) == [('directive', ['# # A subsection'])]
|
|
952
|
+
|
|
953
|
+
|
|
954
|
+
def _nest_title_three_deep(text: str) -> nodes.title:
|
|
867
955
|
# e.g. a "# %%" cell's own heading nested inside another sibling
|
|
868
956
|
# section's heading, rather than directly under the page's own title
|
|
869
957
|
page_section = nodes.section()
|
|
870
958
|
section = nodes.section()
|
|
871
959
|
subsection = nodes.section()
|
|
872
|
-
title = nodes.title('',
|
|
960
|
+
title = nodes.title('', text)
|
|
873
961
|
subsection += title
|
|
874
962
|
section += subsection
|
|
875
963
|
page_section += section
|
|
876
|
-
|
|
964
|
+
return title
|
|
965
|
+
|
|
966
|
+
|
|
967
|
+
def test_convert_node_title_two_sections_deep_py_uses_tilde():
|
|
968
|
+
title = _nest_title_three_deep('A sub-subsection')
|
|
969
|
+
assert seac._convert_node(title, _ctx(fmt='py')) == [
|
|
970
|
+
('directive', ['# A sub-subsection', '# ~~~~~~~~~~~~~~~~'])
|
|
971
|
+
]
|
|
972
|
+
|
|
973
|
+
|
|
974
|
+
def test_convert_node_title_two_sections_deep_ipynb_uses_atx():
|
|
975
|
+
title = _nest_title_three_deep('A sub-subsection')
|
|
976
|
+
assert seac._convert_node(title, _ctx(fmt='ipynb')) == [
|
|
977
|
+
('directive', ['# ### A sub-subsection'])
|
|
978
|
+
]
|
|
877
979
|
|
|
878
980
|
|
|
879
981
|
def test_convert_node_empty_title_returns_empty():
|
|
@@ -1190,7 +1292,7 @@ def test_build_segments_bare_rubric_see_also_affects_only_what_follows():
|
|
|
1190
1292
|
'directive',
|
|
1191
1293
|
[
|
|
1192
1294
|
'# SEE ALSO:',
|
|
1193
|
-
'#
|
|
1295
|
+
'# pyvista.Plotter https://docs.pyvista.org/plotter.html',
|
|
1194
1296
|
'x = 1',
|
|
1195
1297
|
],
|
|
1196
1298
|
)
|
|
@@ -1316,7 +1418,7 @@ def test_process_span_includes_external_see_also(tmp_path: Path):
|
|
|
1316
1418
|
written = next((tmp_path / '_downloads').rglob('*.py'))
|
|
1317
1419
|
content = written.read_text()
|
|
1318
1420
|
assert '# SEE ALSO:' in content
|
|
1319
|
-
assert '#
|
|
1421
|
+
assert '# related info' in content
|
|
1320
1422
|
|
|
1321
1423
|
|
|
1322
1424
|
def test_process_span_appends_footer_with_blank_line_before(tmp_path: Path):
|
|
@@ -177,14 +177,14 @@ def test_docstring_examples_conversion(built: tuple[Path, list[Path]]):
|
|
|
177
177
|
|
|
178
178
|
note_src = _read(examples, 'case_note')
|
|
179
179
|
assert '# NOTE:' in note_src
|
|
180
|
-
assert '#
|
|
180
|
+
assert '# This is a note' in note_src # indented under the label
|
|
181
181
|
|
|
182
182
|
warning_src = _read(examples, 'case_warning')
|
|
183
183
|
assert '# WARNING:' in warning_src
|
|
184
184
|
|
|
185
185
|
multi_note_src = _read(examples, 'case_multi_paragraph_note')
|
|
186
|
-
assert '#
|
|
187
|
-
assert '#
|
|
186
|
+
assert '# First paragraph of the note.' in multi_note_src
|
|
187
|
+
assert '# Second paragraph of the note.' in multi_note_src
|
|
188
188
|
|
|
189
189
|
admonition_src = _read(examples, 'case_generic_admonition')
|
|
190
190
|
assert '# Custom Title:' in admonition_src
|
|
@@ -249,6 +249,15 @@ def test_header_format(built: tuple[Path, list[Path]]):
|
|
|
249
249
|
assert lines[2] == ''
|
|
250
250
|
|
|
251
251
|
|
|
252
|
+
def test_header_format_ipynb_uses_atx(built_notebooks: list[Path]):
|
|
253
|
+
"""The same header, in .ipynb, is a single ATX level-1 line -- no underline."""
|
|
254
|
+
nb_path = next(p for p in built_notebooks if p.stem == 'docstring_cases_case_note')
|
|
255
|
+
notebook = json.loads(nb_path.read_text(encoding='utf-8'))
|
|
256
|
+
header_cell = notebook['cells'][0]
|
|
257
|
+
lines = [line.rstrip() for line in header_cell['source']]
|
|
258
|
+
assert lines[0] == '# Examples from docstring_cases.case_note'
|
|
259
|
+
|
|
260
|
+
|
|
252
261
|
def test_whitespace_conventions(built: tuple[Path, list[Path]]):
|
|
253
262
|
"""Check the spacing rules.
|
|
254
263
|
|
|
@@ -298,10 +307,10 @@ def test_seealso_admonition(built: tuple[Path, list[Path]]):
|
|
|
298
307
|
"""
|
|
299
308
|
src = _read(built[1], 'case_seealso')
|
|
300
309
|
assert '# SEE ALSO:' in src
|
|
301
|
-
assert '#
|
|
302
|
-
assert '#
|
|
310
|
+
assert '# Some Target' in src
|
|
311
|
+
assert '# See this in the gallery for more info.' in src
|
|
303
312
|
# each paragraph must be its own line -- not concatenated together
|
|
304
|
-
assert 'Some Target\n#
|
|
313
|
+
assert 'Some Target\n# See' in src or 'Some TargetSee' not in src
|
|
305
314
|
|
|
306
315
|
|
|
307
316
|
def test_stray_markup_in_doctest_comment_cleaned(built: tuple[Path, list[Path]]):
|
|
@@ -521,7 +530,7 @@ def test_base_url_set_resolves_links(tmp_path: Path):
|
|
|
521
530
|
):
|
|
522
531
|
py_src = _read(py_files, name)
|
|
523
532
|
assert '# SEE ALSO:' in py_src
|
|
524
|
-
assert '#
|
|
533
|
+
assert '# docstring_cases.Sample https://docs.example.com/' in py_src
|
|
525
534
|
assert '`docstring_cases.Sample`' not in py_src # no backticks in See Also url lines
|
|
526
535
|
|
|
527
536
|
nb_path = next(p for p in ipynb_files if p.stem == f'docstring_cases_{name}')
|
|
@@ -533,12 +542,19 @@ def test_markdown_cells_use_hard_line_breaks(built_notebooks: list[Path]):
|
|
|
533
542
|
"""Adjacent lines within one markdown cell need an explicit hard break.
|
|
534
543
|
|
|
535
544
|
Without it, markdown treats a single newline as whitespace and runs
|
|
536
|
-
everything in the cell together into one paragraph.
|
|
545
|
+
everything in the cell together into one paragraph. Checked on the
|
|
546
|
+
note's own "# NOTE:" label line, immediately followed by its content
|
|
547
|
+
with no blank line between -- not the header cell, which is a single
|
|
548
|
+
ATX heading line with nothing else to connect to.
|
|
537
549
|
"""
|
|
538
550
|
nb_path = next(p for p in built_notebooks if p.stem == 'docstring_cases_case_note')
|
|
539
551
|
notebook = json.loads(nb_path.read_text(encoding='utf-8'))
|
|
540
|
-
|
|
541
|
-
|
|
552
|
+
note_cell = next(
|
|
553
|
+
c
|
|
554
|
+
for c in notebook['cells']
|
|
555
|
+
if c['cell_type'] == 'markdown' and 'useful information' in ''.join(c['source'])
|
|
556
|
+
)
|
|
557
|
+
assert note_cell['source'][0].endswith(' \n')
|
|
542
558
|
|
|
543
559
|
|
|
544
560
|
def test_html_baseurl_missing_scheme_disables_links_without_failing_build(tmp_path: Path):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/tests/gallery_fixture/conf.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/tests/gallery_fixture/index.rst
RENAMED
|
File without changes
|
{sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/tests/heading_level_fixture/conf.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/tests/test_single_function_page.py
RENAMED
|
File without changes
|
|
File without changes
|
{sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/tests/tinypages/docstring_cases.py
RENAMED
|
File without changes
|
{sphinx_examples_as_code-0.3.1 → sphinx_examples_as_code-0.3.3}/tests/tinypages/docstring_cases.rst
RENAMED
|
File without changes
|
|
File without changes
|