Skip to content

Commit 8485bfd

Browse files
committed
Check for duplicate output-base-name in the Sphinx extension
Previously it would just overwrite one plot with the other. I did not add tests for this because I would have to create a whole separate docs build just to test the error (but I have tested it manually).
1 parent 426abc7 commit 8485bfd

File tree

1 file changed

+43
-2
lines changed

1 file changed

+43
-2
lines changed

lib/matplotlib/sphinxext/plot_directive.py

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@
5050
``:output-base-name:`` : str
5151
The base name (without the extension) of the outputted image files. The
5252
default is to use the same name as the input script, or the name of
53-
the RST document if no script is provided. Note: two plots with the same
54-
output-base-name may overwrite each other.
53+
the RST document if no script is provided. The output-base-name for each
54+
plot directive must be unique.
5555
5656
``:format:`` : {'python', 'doctest'}
5757
The format of the input. If unset, the format is auto-detected.
@@ -171,6 +171,7 @@
171171
and *TEMPLATE_SRCSET*.
172172
"""
173173

174+
from collections import defaultdict
174175
import contextlib
175176
import doctest
176177
from io import StringIO
@@ -188,6 +189,7 @@
188189
from docutils.parsers.rst.directives.images import Image
189190
import jinja2 # Sphinx dependency.
190191

192+
from sphinx.environment.collectors import EnvironmentCollector
191193
from sphinx.errors import ExtensionError
192194

193195
import matplotlib
@@ -319,9 +321,35 @@ def setup(app):
319321
app.connect('build-finished', _copy_css_file)
320322
metadata = {'parallel_read_safe': True, 'parallel_write_safe': True,
321323
'version': matplotlib.__version__}
324+
app.connect('builder-inited', init_filename_registry)
325+
app.add_env_collector(FilenameCollector)
322326
return metadata
323327

324328

329+
# -----------------------------------------------------------------------------
330+
# Handle Duplicate Filenames
331+
# -----------------------------------------------------------------------------
332+
333+
def init_filename_registry(app):
334+
env = app.builder.env
335+
if not hasattr(env, 'mpl_custom_base_names'):
336+
env.mpl_custom_base_names = defaultdict(set)
337+
338+
class FilenameCollector(EnvironmentCollector):
339+
def process_doc(self, app, doctree):
340+
pass
341+
342+
def clear_doc(self, app, env, docname):
343+
if docname in env.mpl_custom_base_names:
344+
del env.mpl_custom_base_names[docname]
345+
346+
def merge_other(self, app, env, docnames, other):
347+
for docname in docnames:
348+
if docname in other.mpl_custom_base_names:
349+
if docname not in env.mpl_custom_base_names:
350+
env.mpl_custom_base_names[docname] = set()
351+
env.mpl_custom_base_names[docname].update(other.mpl_custom_base_names[docname])
352+
325353
# -----------------------------------------------------------------------------
326354
# Doctest handling
327355
# -----------------------------------------------------------------------------
@@ -606,6 +634,16 @@ def _parse_srcset(entries):
606634
raise ExtensionError(f'srcset argument {entry!r} is invalid.')
607635
return srcset
608636

637+
def check_output_base_name(env, output_base):
638+
docname = env.docname
639+
640+
for d in env.mpl_custom_base_names:
641+
if output_base in env.mpl_custom_base_names[d]:
642+
if d == docname:
643+
raise PlotError(f"The output-base-name '{output_base}' is used multiple times.")
644+
raise PlotError(f"The output-base-name '{output_base}' is used multiple times (it is also used in {env.doc2path(d)}).")
645+
646+
env.mpl_custom_base_names[docname].add(output_base)
609647

610648
def render_figures(code, code_path, output_dir, output_base, context,
611649
function_name, config, context_reset=False,
@@ -730,6 +768,7 @@ def render_figures(code, code_path, output_dir, output_base, context,
730768
def run(arguments, content, options, state_machine, state, lineno):
731769
document = state_machine.document
732770
config = document.settings.env.config
771+
env = document.settings.env
733772
nofigs = 'nofigs' in options
734773

735774
if config.plot_srcset and setup.app.builder.name == 'singlehtml':
@@ -785,6 +824,7 @@ def run(arguments, content, options, state_machine, state, lineno):
785824
code = Path(source_file_name).read_text(encoding='utf-8')
786825
if options['output-base-name']:
787826
output_base = options['output-base-name']
827+
check_output_base_name(env, output_base)
788828
else:
789829
output_base = os.path.basename(source_file_name)
790830
else:
@@ -793,6 +833,7 @@ def run(arguments, content, options, state_machine, state, lineno):
793833
base, ext = os.path.splitext(os.path.basename(source_file_name))
794834
if options['output-base-name']:
795835
output_base = options['output-base-name']
836+
check_output_base_name(env, output_base)
796837
else:
797838
counter = document.attributes.get('_plot_counter', 0) + 1
798839
document.attributes['_plot_counter'] = counter

0 commit comments

Comments
 (0)