Skip to content

Plot directive: delegate file handling to Sphinx #24205

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 19 additions & 38 deletions lib/matplotlib/sphinxext/plot_directive.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,16 +369,16 @@ def _split_code_at_show(text):

.. only:: html

{% if source_link or (html_show_formats and not multi_image) %}
{% if src_name or (html_show_formats and not multi_image) %}
(
{%- if source_link -%}
`Source code <{{ source_link }}>`__
{%- if src_name -%}
:download:`Source code <{{ build_dir }}/{{ src_name }}>`
{%- endif -%}
{%- if html_show_formats and not multi_image -%}
{%- for img in images -%}
{%- for fmt in img.formats -%}
{%- if source_link or not loop.first -%}, {% endif -%}
`{{ fmt }} <{{ dest_dir }}/{{ img.basename }}.{{ fmt }}>`__
{%- if src_name or not loop.first -%}, {% endif -%}
:download:`{{ fmt }} <{{ build_dir }}/{{ img.basename }}.{{ fmt }}>`
{%- endfor -%}
{%- endfor -%}
{%- endif -%}
Expand All @@ -395,7 +395,7 @@ def _split_code_at_show(text):
(
{%- for fmt in img.formats -%}
{%- if not loop.first -%}, {% endif -%}
`{{ fmt }} <{{ dest_dir }}/{{ img.basename }}.{{ fmt }}>`__
:download:`{{ fmt }} <{{ build_dir }}/{{ img.basename }}.{{ fmt }}>`
{%- endfor -%}
)
{%- endif -%}
Expand Down Expand Up @@ -756,21 +756,13 @@ def run(arguments, content, options, state_machine, state, lineno):
build_dir = os.path.normpath(build_dir)
os.makedirs(build_dir, exist_ok=True)

# output_dir: final location in the builder's directory
dest_dir = os.path.abspath(os.path.join(setup.app.builder.outdir,
source_rel_dir))
os.makedirs(dest_dir, exist_ok=True)

# how to link to files from the RST file
dest_dir_link = os.path.join(relpath(setup.confdir, rst_dir),
source_rel_dir).replace(os.path.sep, '/')
try:
build_dir_link = relpath(build_dir, rst_dir).replace(os.path.sep, '/')
except ValueError:
# on Windows, relpath raises ValueError when path and start are on
# different mounts/drives
build_dir_link = build_dir
source_link = dest_dir_link + '/' + output_base + source_ext

# get list of included rst files so that the output is updated when any
# plots in the included files change. These attributes are modified by the
Expand All @@ -791,6 +783,14 @@ def run(arguments, content, options, state_machine, state, lineno):
except ValueError:
pass

# save script (if necessary)
if options['show-source-link']:
Path(build_dir, output_base + source_ext).write_text(
doctest.script_from_examples(code)
if source_file_name == rst_file and is_doctest
else code,
encoding='utf-8')

# make figures
try:
results = render_figures(code,
Expand Down Expand Up @@ -837,18 +837,17 @@ def run(arguments, content, options, state_machine, state, lineno):
':%s: %s' % (key, val) for key, val in options.items()
if key in ('alt', 'height', 'width', 'scale', 'align', 'class')]

# Not-None src_link signals the need for a source link in the generated
# html
# Not-None src_name signals the need for a source download in the
# generated html
if j == 0 and options['show-source-link']:
src_link = source_link
src_name = output_base + source_ext
else:
src_link = None
src_name = None

result = jinja2.Template(config.plot_template or TEMPLATE).render(
default_fmt=default_fmt,
dest_dir=dest_dir_link,
build_dir=build_dir_link,
source_link=src_link,
src_name=src_name,
multi_image=len(images) > 1,
options=opts,
images=images,
Expand All @@ -862,22 +861,4 @@ def run(arguments, content, options, state_machine, state, lineno):
if total_lines:
state_machine.insert_input(total_lines, source=source_file_name)

# copy image files to builder's output directory, if necessary
Path(dest_dir).mkdir(parents=True, exist_ok=True)

for code_piece, images in results:
for img in images:
for fn in img.filenames():
destimg = os.path.join(dest_dir, os.path.basename(fn))
if fn != destimg:
shutil.copyfile(fn, destimg)

# copy script (if necessary)
if options['show-source-link']:
Path(dest_dir, output_base + source_ext).write_text(
doctest.script_from_examples(code)
if source_file_name == rst_file and is_doctest
else code,
encoding='utf-8')

return errors
15 changes: 8 additions & 7 deletions lib/matplotlib/tests/test_sphinxext.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def test_tinypages(tmp_path):
shutil.copytree(Path(__file__).parent / 'tinypages', tmp_path,
dirs_exist_ok=True)
html_dir = tmp_path / '_build' / 'html'
img_dir = html_dir / '_images'
doctree_dir = tmp_path / 'doctrees'
# Build the pages with warnings turned into errors
cmd = [sys.executable, '-msphinx', '-W', '-b', 'html',
Expand All @@ -35,7 +36,7 @@ def test_tinypages(tmp_path):
build_sphinx_html(tmp_path, doctree_dir, html_dir)

def plot_file(num):
return html_dir / f'some_plots-{num}.png'
return img_dir / f'some_plots-{num}.png'

def plot_directive_file(num):
# This is always next to the doctree dir.
Expand All @@ -58,8 +59,8 @@ def plot_directive_file(num):

assert b'# Only a comment' in html_contents
# check plot defined in external file.
assert filecmp.cmp(range_4, html_dir / 'range4.png')
assert filecmp.cmp(range_6, html_dir / 'range6.png')
assert filecmp.cmp(range_4, img_dir / 'range4.png')
assert filecmp.cmp(range_6, img_dir / 'range6.png')
# check if figure caption made it into html file
assert b'This is the caption for plot 15.' in html_contents
# check if figure caption using :caption: made it into html file
Expand Down Expand Up @@ -111,13 +112,13 @@ def test_plot_html_show_source_link(tmp_path):
# Make sure source scripts are created by default
html_dir1 = tmp_path / '_build' / 'html1'
build_sphinx_html(tmp_path, doctree_dir, html_dir1)
assert "index-1.py" in [p.name for p in html_dir1.iterdir()]
assert len(list(html_dir1.glob("**/index-1.py"))) == 1
# Make sure source scripts are NOT created when
# plot_html_show_source_link` is False
html_dir2 = tmp_path / '_build' / 'html2'
build_sphinx_html(tmp_path, doctree_dir, html_dir2,
extra_args=['-D', 'plot_html_show_source_link=0'])
assert "index-1.py" not in [p.name for p in html_dir2.iterdir()]
assert len(list(html_dir2.glob("**/index-1.py"))) == 0


@pytest.mark.parametrize('plot_html_show_source_link', [0, 1])
Expand All @@ -137,7 +138,7 @@ def test_show_source_link_true(tmp_path, plot_html_show_source_link):
html_dir = tmp_path / '_build' / 'html'
build_sphinx_html(tmp_path, doctree_dir, html_dir, extra_args=[
'-D', f'plot_html_show_source_link={plot_html_show_source_link}'])
assert "index-1.py" in [p.name for p in html_dir.iterdir()]
assert len(list(html_dir.glob("**/index-1.py"))) == 1


@pytest.mark.parametrize('plot_html_show_source_link', [0, 1])
Expand All @@ -157,7 +158,7 @@ def test_show_source_link_false(tmp_path, plot_html_show_source_link):
html_dir = tmp_path / '_build' / 'html'
build_sphinx_html(tmp_path, doctree_dir, html_dir, extra_args=[
'-D', f'plot_html_show_source_link={plot_html_show_source_link}'])
assert "index-1.py" not in [p.name for p in html_dir.iterdir()]
assert len(list(html_dir.glob("**/index-1.py"))) == 0


def build_sphinx_html(tmp_path, doctree_dir, html_dir, extra_args=None):
Expand Down