diff --git a/lib/matplotlib/sphinxext/plot_directive.py b/lib/matplotlib/sphinxext/plot_directive.py index 7b46b3145e2b..6faedd66a8e6 100644 --- a/lib/matplotlib/sphinxext/plot_directive.py +++ b/lib/matplotlib/sphinxext/plot_directive.py @@ -162,6 +162,12 @@ The plot_srcset option is incompatible with *singlehtml* builds, and an error will be raised. +plot_exclude_patterns + List of non-recursive glob-style patterns for selectively skipping plot + directives. If any pattern matches the relative path of a documentation + file, then plot directives in that file will be skipped. + Matches are computed using :external+python:meth:`pathlib.PurePath.match`. + Notes on how it works --------------------- @@ -323,6 +329,7 @@ def setup(app): app.add_config_value('plot_working_directory', None, True) app.add_config_value('plot_template', None, True) app.add_config_value('plot_srcset', [], True) + app.add_config_value('plot_exclude_patterns', [], True) app.connect('doctree-read', mark_plot_labels) app.add_css_file('plot_directive.css') app.connect('build-finished', _copy_css_file) @@ -925,16 +932,20 @@ def run(arguments, content, options, state_machine, state, lineno): # make figures try: - results = render_figures(code=code, - code_path=source_file_name, - output_dir=build_dir, - output_base=output_base, - context=keep_context, - function_name=function_name, - config=config, - context_reset=context_opt == 'reset', - close_figs=context_opt == 'close-figs', - code_includes=source_file_includes) + if any([Path(source_rel_name).match(pattern) for pattern in + config.plot_exclude_patterns]): + results = [(code, [])] + else: + results = render_figures(code=code, + code_path=source_file_name, + output_dir=build_dir, + output_base=output_base, + context=keep_context, + function_name=function_name, + config=config, + context_reset=context_opt == 'reset', + close_figs=context_opt == 'close-figs', + code_includes=source_file_includes) errors = [] except PlotError as err: reporter = state.memo.reporter diff --git a/lib/matplotlib/tests/test_sphinxext.py b/lib/matplotlib/tests/test_sphinxext.py index c6f4e13c74c2..ae287f89a783 100644 --- a/lib/matplotlib/tests/test_sphinxext.py +++ b/lib/matplotlib/tests/test_sphinxext.py @@ -269,3 +269,95 @@ def plot_file(num, suff=''): st = ('srcset="../_images/nestedpage2-index-2.png, ' '../_images/nestedpage2-index-2.2x.png 2.00x"') assert st in (html_dir / 'nestedpage2/index.html').read_text(encoding='utf-8') + + +@pytest.mark.parametrize('plot_exclude_patterns', [False, "*index*", + "index*", "ndex*", "?ndex*", + "*nonmatch*", + "range*", "range6*", + "index*,range*", "*", + "*script*"]) +def test_plot_exclude_patterns(tmp_path, plot_exclude_patterns): + # test that modifying plot_exclude_patterns in config leads to skipping files + shutil.copyfile(tinypages / 'conf.py', tmp_path / 'conf.py') + shutil.copytree(tinypages / '_static', tmp_path / '_static') + shutil.copyfile(tinypages / 'range4.py', tmp_path / 'range4.py') + shutil.copyfile(tinypages / 'range6.py', tmp_path / 'range6.py') + + html_dir = tmp_path / '_build' / 'html' + img_dir = html_dir / '_images' + doctree_dir = tmp_path / 'doctrees' + + (tmp_path / 'index.rst').write_text(""" +.. plot:: + + plt.plot(range(2)) + +.. toctree:: + + script_func + script_nofunc +""") + (tmp_path / 'script_func.rst').write_text(""" +########## +Some plots +########## + +.. plot:: range6.py range6 + +.. plot:: range6.py range10 +""") + (tmp_path / 'script_nofunc.rst').write_text(""" +########## +Some plots +########## + +.. plot:: range4.py +""") + + if plot_exclude_patterns is not False: + extra_args = ["-D", f"plot_exclude_patterns={plot_exclude_patterns}"] + else: + extra_args = [] + # Build the pages with warnings turned into errors + build_sphinx_html(tmp_path, doctree_dir, html_dir, + extra_args=extra_args) + + # default behavior and non-matching patterns mean all plots created. while script + # matches the name of the rst file, it does not match the relative path (which comes + # from the name of the script containing the plotting function), and thus doesn't + # match. ndex* doesn't match because we're not matching substrings, and thus need + # wildcards for missing characters + if plot_exclude_patterns in ["*script*", "*nonmatch*", False, "ndex*"]: + assert (img_dir / "index-1.png").exists() + assert (img_dir / "range6_range6.png").exists() + assert (img_dir / "range6_range10.png").exists() + assert (img_dir / "range4.png").exists() + # relative path is the name of the rst file when it contains plotting code. thus, + # index is skipped. we match against relative path + elif plot_exclude_patterns in ["*index*", "index*", "?ndex*"]: + assert not (img_dir / "index-1.png").exists() + assert (img_dir / "range6_range6.png").exists() + assert (img_dir / "range6_range10.png").exists() + assert (img_dir / "range4.png").exists() + # name of the script used by the plot directive in the scripts rst files all match + # this pattern and thus are all skipped + elif plot_exclude_patterns == "range*": + assert (img_dir / "index-1.png").exists() + assert not (img_dir / "range6_range6.png").exists() + assert not (img_dir / "range6_range10.png").exists() + assert not (img_dir / "range4.png").exists() + # matches the name of one script, but not the other + elif plot_exclude_patterns == "range6*": + assert (img_dir / "index-1.png").exists() + assert not (img_dir / "range6_range6.png").exists() + assert not (img_dir / "range6_range10.png").exists() + assert (img_dir / "range4.png").exists() + # matches all relative paths, so no images created. + elif plot_exclude_patterns in ["index*,range*", "*"]: + assert not (img_dir / "index-1.png").exists() + assert not (img_dir / "range6_range6.png").exists() + assert not (img_dir / "range6_range10.png").exists() + assert not (img_dir / "range4.png").exists() + else: + raise ValueError(f"unsure how to check {plot_exclude_patterns=}")