From d58fc21b8bd2617f15aff00207d2c116622b899f Mon Sep 17 00:00:00 2001 From: Leo Singer Date: Tue, 18 Oct 2022 12:41:07 -0400 Subject: [PATCH] Add :shows-source-link: option to Sphinx plot directive The Sphinx plot directive (`.. plot::`) now supports a `:show-source-link` option to show or hide the link to the source for each plot. The default is set using the `plot_html_show_source_link` variable in `conf.py` (which itself defaults to True). --- .../show_source_links_directive_option.rst | 7 ++ lib/matplotlib/sphinxext/plot_directive.py | 11 ++- lib/matplotlib/tests/test_sphinxext.py | 82 ++++++++++++++----- 3 files changed, 76 insertions(+), 24 deletions(-) create mode 100644 doc/users/next_whats_new/show_source_links_directive_option.rst diff --git a/doc/users/next_whats_new/show_source_links_directive_option.rst b/doc/users/next_whats_new/show_source_links_directive_option.rst new file mode 100644 index 000000000000..8beb233f32e8 --- /dev/null +++ b/doc/users/next_whats_new/show_source_links_directive_option.rst @@ -0,0 +1,7 @@ +Source links can be shown or hidden for each Sphinx plot directive +------------------------------------------------------------------ +The :doc:`Sphinx plot directive ` +(``.. plot::``) now supports a ``:show-source-link:`` option to show or hide +the link to the source code for each plot. The default is set using the +``plot_html_show_source_link`` variable in :file:`conf.py` (which +defaults to True). diff --git a/lib/matplotlib/sphinxext/plot_directive.py b/lib/matplotlib/sphinxext/plot_directive.py index 743344086211..f483d17ce1f3 100644 --- a/lib/matplotlib/sphinxext/plot_directive.py +++ b/lib/matplotlib/sphinxext/plot_directive.py @@ -55,6 +55,11 @@ the ``plot_include_source`` variable in :file:`conf.py` (which itself defaults to False). + ``:show-source-link:`` : bool + Whether to show a link to the source in HTML. The default can be + changed using the ``plot_html_show_source_link`` variable in + :file:`conf.py` (which itself defaults to True). + ``:encoding:`` : str If this source file is in a non-UTF8 or non-ASCII encoding, the encoding must be specified using the ``:encoding:`` option. The @@ -245,6 +250,7 @@ class PlotDirective(Directive): 'align': Image.align, 'class': directives.class_option, 'include-source': _option_boolean, + 'show-source-link': _option_boolean, 'format': _option_format, 'context': _option_context, 'nofigs': directives.flag, @@ -666,6 +672,7 @@ def run(arguments, content, options, state_machine, state, lineno): default_fmt = formats[0][0] options.setdefault('include-source', config.plot_include_source) + options.setdefault('show-source-link', config.plot_html_show_source_link) if 'class' in options: # classes are parsed into a list of string, and output by simply # printing the list, abusing the fact that RST guarantees to strip @@ -832,7 +839,7 @@ def run(arguments, content, options, state_machine, state, lineno): # Not-None src_link signals the need for a source link in the generated # html - if j == 0 and config.plot_html_show_source_link: + if j == 0 and options['show-source-link']: src_link = source_link else: src_link = None @@ -866,7 +873,7 @@ def run(arguments, content, options, state_machine, state, lineno): shutil.copyfile(fn, destimg) # copy script (if necessary) - if config.plot_html_show_source_link: + 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 diff --git a/lib/matplotlib/tests/test_sphinxext.py b/lib/matplotlib/tests/test_sphinxext.py index d4fb94ade5d1..de3146070918 100644 --- a/lib/matplotlib/tests/test_sphinxext.py +++ b/lib/matplotlib/tests/test_sphinxext.py @@ -14,11 +14,11 @@ minversion=None if sys.version_info < (3, 10) else '4.1.3') -def test_tinypages(tmpdir): - source_dir = Path(tmpdir) / 'src' - shutil.copytree(Path(__file__).parent / 'tinypages', source_dir) - html_dir = source_dir / '_build' / 'html' - doctree_dir = source_dir / 'doctrees' +def test_tinypages(tmp_path): + shutil.copytree(Path(__file__).parent / 'tinypages', tmp_path, + dirs_exist_ok=True) + html_dir = tmp_path / '_build' / 'html' + doctree_dir = tmp_path / 'doctrees' # Build the pages with warnings turned into errors cmd = [sys.executable, '-msphinx', '-W', '-b', 'html', '-d', str(doctree_dir), @@ -32,7 +32,7 @@ def test_tinypages(tmpdir): out, err = proc.communicate() # Build the pages with warnings turned into errors - build_sphinx_html(source_dir, doctree_dir, html_dir) + build_sphinx_html(tmp_path, doctree_dir, html_dir) def plot_file(num): return html_dir / f'some_plots-{num}.png' @@ -75,13 +75,13 @@ def plot_directive_file(num): assert filecmp.cmp(range_6, plot_file(17)) # Modify the included plot - contents = (source_dir / 'included_plot_21.rst').read_bytes() + contents = (tmp_path / 'included_plot_21.rst').read_bytes() contents = contents.replace(b'plt.plot(range(6))', b'plt.plot(range(4))') - (source_dir / 'included_plot_21.rst').write_bytes(contents) + (tmp_path / 'included_plot_21.rst').write_bytes(contents) # Build the pages again and check that the modified file was updated modification_times = [plot_directive_file(i).stat().st_mtime for i in (1, 2, 3, 5)] - build_sphinx_html(source_dir, doctree_dir, html_dir) + build_sphinx_html(tmp_path, doctree_dir, html_dir) assert filecmp.cmp(range_4, plot_file(17)) # Check that the plots in the plot_directive folder weren't changed. # (plot_directive_file(1) won't be modified, but it will be copied to html/ @@ -98,35 +98,73 @@ def plot_directive_file(num): assert filecmp.cmp(range_6, plot_file(5)) -def test_plot_html_show_source_link(tmpdir): - source_dir = Path(tmpdir) / 'src' - source_dir.mkdir() +def test_plot_html_show_source_link(tmp_path): parent = Path(__file__).parent - shutil.copyfile(parent / 'tinypages/conf.py', source_dir / 'conf.py') - shutil.copytree(parent / 'tinypages/_static', source_dir / '_static') - doctree_dir = source_dir / 'doctrees' - (source_dir / 'index.rst').write_text(""" + shutil.copyfile(parent / 'tinypages/conf.py', tmp_path / 'conf.py') + shutil.copytree(parent / 'tinypages/_static', tmp_path / '_static') + doctree_dir = tmp_path / 'doctrees' + (tmp_path / 'index.rst').write_text(""" .. plot:: plt.plot(range(2)) """) # Make sure source scripts are created by default - html_dir1 = source_dir / '_build' / 'html1' - build_sphinx_html(source_dir, doctree_dir, html_dir1) + 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()] # Make sure source scripts are NOT created when # plot_html_show_source_link` is False - html_dir2 = source_dir / '_build' / 'html2' - build_sphinx_html(source_dir, doctree_dir, html_dir2, + 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()] -def build_sphinx_html(source_dir, doctree_dir, html_dir, extra_args=None): +@pytest.mark.parametrize('plot_html_show_source_link', [0, 1]) +def test_show_source_link_true(tmp_path, plot_html_show_source_link): + # Test that a source link is generated if :show-source-link: is true, + # whether or not plot_html_show_source_link is true. + parent = Path(__file__).parent + shutil.copyfile(parent / 'tinypages/conf.py', tmp_path / 'conf.py') + shutil.copytree(parent / 'tinypages/_static', tmp_path / '_static') + doctree_dir = tmp_path / 'doctrees' + (tmp_path / 'index.rst').write_text(""" +.. plot:: + :show-source-link: true + + plt.plot(range(2)) +""") + 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()] + + +@pytest.mark.parametrize('plot_html_show_source_link', [0, 1]) +def test_show_source_link_false(tmp_path, plot_html_show_source_link): + # Test that a source link is NOT generated if :show-source-link: is false, + # whether or not plot_html_show_source_link is true. + parent = Path(__file__).parent + shutil.copyfile(parent / 'tinypages/conf.py', tmp_path / 'conf.py') + shutil.copytree(parent / 'tinypages/_static', tmp_path / '_static') + doctree_dir = tmp_path / 'doctrees' + (tmp_path / 'index.rst').write_text(""" +.. plot:: + :show-source-link: false + + plt.plot(range(2)) +""") + 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()] + + +def build_sphinx_html(tmp_path, doctree_dir, html_dir, extra_args=None): # Build the pages with warnings turned into errors extra_args = [] if extra_args is None else extra_args cmd = [sys.executable, '-msphinx', '-W', '-b', 'html', - '-d', str(doctree_dir), str(source_dir), str(html_dir), *extra_args] + '-d', str(doctree_dir), str(tmp_path), str(html_dir), *extra_args] proc = Popen(cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True, env={**os.environ, "MPLBACKEND": ""}) out, err = proc.communicate()