Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Check modification times of included RST files #20374

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 4 commits into from
Jul 7, 2021
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
55 changes: 45 additions & 10 deletions lib/matplotlib/sphinxext/plot_directive.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,14 +429,26 @@ def filenames(self):
return [self.filename(fmt) for fmt in self.formats]


def out_of_date(original, derived):
def out_of_date(original, derived, includes=None):
"""
Return whether *derived* is out-of-date relative to *original*, both of
which are full file paths.
Return whether *derived* is out-of-date relative to *original* or any of
the RST files included in it using the RST include directive (*includes*).
*derived* and *original* are full paths, and *includes* is optionally a
list of full paths which may have been included in the *original*.
"""
return (not os.path.exists(derived) or
(os.path.exists(original) and
os.stat(derived).st_mtime < os.stat(original).st_mtime))
if not os.path.exists(derived):
return True

if includes is None:
includes = []
files_to_check = [original, *includes]

def out_of_date_one(original, derived_mtime):
return (os.path.exists(original) and
derived_mtime < os.stat(original).st_mtime)

derived_mtime = os.stat(derived).st_mtime
return any(out_of_date_one(f, derived_mtime) for f in files_to_check)


class PlotError(RuntimeError):
Expand Down Expand Up @@ -532,7 +544,8 @@ def get_plot_formats(config):

def render_figures(code, code_path, output_dir, output_base, context,
function_name, config, context_reset=False,
close_figs=False):
close_figs=False,
code_includes=None):
"""
Run a pyplot script and save the images in *output_dir*.

Expand All @@ -549,7 +562,8 @@ def render_figures(code, code_path, output_dir, output_base, context,
all_exists = True
img = ImageFile(output_base, output_dir)
for format, dpi in formats:
if out_of_date(code_path, img.filename(format)):
if context or out_of_date(code_path, img.filename(format),
includes=code_includes):
all_exists = False
break
img.formats.append(format)
Expand All @@ -569,7 +583,8 @@ def render_figures(code, code_path, output_dir, output_base, context,
else:
img = ImageFile('%s_%02d' % (output_base, j), output_dir)
for fmt, dpi in formats:
if out_of_date(code_path, img.filename(fmt)):
if context or out_of_date(code_path, img.filename(fmt),
includes=code_includes):
all_exists = False
break
img.formats.append(fmt)
Expand Down Expand Up @@ -742,6 +757,25 @@ def run(arguments, content, options, state_machine, state, lineno):
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
# include directive (see the docutils.parsers.rst.directives.misc module).
try:
source_file_includes = [os.path.join(os.getcwd(), t[0])
for t in state.document.include_log]
except AttributeError:
# the document.include_log attribute only exists in docutils >=0.17,
# before that we need to inspect the state machine
possible_sources = {os.path.join(setup.confdir, t[0])
for t in state_machine.input_lines.items}
source_file_includes = [f for f in possible_sources
if os.path.isfile(f)]
# remove the source file itself from the includes
try:
source_file_includes.remove(source_file_name)
except ValueError:
pass

# make figures
try:
results = render_figures(code,
Expand All @@ -752,7 +786,8 @@ def run(arguments, content, options, state_machine, state, lineno):
function_name,
config,
context_reset=context_opt == 'reset',
close_figs=context_opt == 'close-figs')
close_figs=context_opt == 'close-figs',
code_includes=source_file_includes)
errors = []
except PlotError as err:
reporter = state.memo.reporter
Expand Down
70 changes: 54 additions & 16 deletions lib/matplotlib/tests/test_sphinxext.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import filecmp
import os
from pathlib import Path
import shutil
from subprocess import Popen, PIPE
import sys

Expand All @@ -13,27 +14,21 @@


def test_tinypages(tmpdir):
tmp_path = Path(tmpdir)
html_dir = tmp_path / '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),
str(Path(__file__).parent / 'tinypages'), str(html_dir)]
proc = Popen(cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True,
env={**os.environ, "MPLBACKEND": ""})
out, err = proc.communicate()
source_dir = Path(tmpdir) / 'src'
shutil.copytree(Path(__file__).parent / 'tinypages', source_dir)
html_dir = source_dir / '_build' / 'html'
doctree_dir = source_dir / 'doctrees'

assert proc.returncode == 0, \
f"sphinx build failed with stdout:\n{out}\nstderr:\n{err}\n"
if err:
pytest.fail(f"sphinx build emitted the following warnings:\n{err}")

assert html_dir.is_dir()
# Build the pages with warnings turned into errors
build_sphinx_html(source_dir, doctree_dir, html_dir)

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

def plot_directive_file(num):
# This is always next to the doctree dir.
return doctree_dir.parent / 'plot_directive' / f'some_plots-{num}.png'

range_10, range_6, range_4 = [plot_file(i) for i in range(1, 4)]
# Plot 5 is range(6) plot
assert filecmp.cmp(range_6, plot_file(5))
Expand All @@ -48,6 +43,7 @@ def plot_file(num):
assert filecmp.cmp(range_4, plot_file(13))
# Plot 14 has included source
html_contents = (html_dir / 'some_plots.html').read_bytes()

assert b'# Only a comment' in html_contents
# check plot defined in external file.
assert filecmp.cmp(range_4, html_dir / 'range4.png')
Expand All @@ -62,3 +58,45 @@ def plot_file(num):
assert b'plot-directive my-class my-other-class' in html_contents
# check that the multi-image caption is applied twice
assert html_contents.count(b'This caption applies to both plots.') == 2
# Plot 21 is range(6) plot via an include directive. But because some of
# the previous plots are repeated, the argument to plot_file() is only 17.
assert filecmp.cmp(range_6, plot_file(17))

# Modify the included plot
contents = (source_dir / 'included_plot_21.rst').read_text()
contents = contents.replace('plt.plot(range(6))', 'plt.plot(range(4))')
(source_dir / 'included_plot_21.rst').write_text(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)
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/
# upon compilation, so plot_file(1) will be modified)
assert plot_directive_file(1).stat().st_mtime == modification_times[0]
assert plot_directive_file(2).stat().st_mtime == modification_times[1]
assert plot_directive_file(3).stat().st_mtime == modification_times[2]
assert filecmp.cmp(range_10, plot_file(1))
assert filecmp.cmp(range_6, plot_file(2))
assert filecmp.cmp(range_4, plot_file(3))
# Make sure that figures marked with context are re-created (but that the
# contents are the same)
assert plot_directive_file(5).stat().st_mtime > modification_times[3]
assert filecmp.cmp(range_6, plot_file(5))


def build_sphinx_html(source_dir, doctree_dir, html_dir):
# Build the pages with warnings turned into errors
cmd = [sys.executable, '-msphinx', '-W', '-b', 'html',
'-d', str(doctree_dir), str(source_dir), str(html_dir)]
proc = Popen(cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True,
env={**os.environ, "MPLBACKEND": ""})
out, err = proc.communicate()

assert proc.returncode == 0, \
f"sphinx build failed with stdout:\n{out}\nstderr:\n{err}\n"
if err:
pytest.fail(f"sphinx build emitted the following warnings:\n{err}")

assert html_dir.is_dir()
6 changes: 6 additions & 0 deletions lib/matplotlib/tests/tinypages/included_plot_21.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Plot 21 has length 6

.. plot::

plt.plot(range(6))

5 changes: 5 additions & 0 deletions lib/matplotlib/tests/tinypages/some_plots.rst
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,8 @@ scenario:

plt.figure()
plt.plot(range(4))

Plot 21 is generated via an include directive:

.. include:: included_plot_21.rst