diff --git a/.travis.yml b/.travis.yml index fee37e4afe4c..6508201e97c2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,7 +23,7 @@ matrix: env: BUILD_DOCS=true install: - - pip install -q --use-mirrors nose python-dateutil $NUMPY pep8 pyparsing pillow + - pip install -q --use-mirrors nose python-dateutil $NUMPY pep8 pyparsing pillow sphinx - sudo apt-get update && sudo apt-get -qq install inkscape libav-tools gdb # We use --no-install-recommends to avoid pulling in additional large latex docs that we don't need @@ -33,7 +33,7 @@ install: - | if [[ $BUILD_DOCS == true ]]; then sudo apt-get install -qq --no-install-recommends dvipng texlive-latex-base texlive-latex-extra texlive-fonts-recommended graphviz - pip install sphinx numpydoc linkchecker + pip install numpydoc linkchecker wget http://mirrors.kernel.org/ubuntu/pool/universe/f/fonts-humor-sans/fonts-humor-sans_1.0-1_all.deb sudo dpkg -i fonts-humor-sans_1.0-1_all.deb wget https://googlefontdirectory.googlecode.com/hg/ofl/felipa/Felipa-Regular.ttf diff --git a/doc/users/whats_new.rst b/doc/users/whats_new.rst index daf53fe68249..f3dfbb720e89 100644 --- a/doc/users/whats_new.rst +++ b/doc/users/whats_new.rst @@ -66,6 +66,14 @@ New backend selection The environment variable :envvar:`MPLBACKEND` can now be used to set the matplotlib backend. +New ``close-figs`` argument for plot directive +---------------------------------------------- + +Matplotlib has a sphinx extension ``plot_directive`` that creates plots for +inclusion in sphinx documents. Matplotlib 1.5 adds a new option to the plot +directive - ``close-figs`` - that closes any previous figure windows before +creating the plots. This can help avoid some surprising duplicates of plots +when using ``plot_directive``. .. _whats-new-1-4: diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 9193d179e57c..95adb553800a 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -1440,6 +1440,7 @@ def tk_window_focus(): 'matplotlib.tests.test_transforms', 'matplotlib.tests.test_triangulation', 'matplotlib.tests.test_widgets', + 'matplotlib.sphinxext.tests.test_tinypages', 'mpl_toolkits.tests.test_mplot3d', 'mpl_toolkits.tests.test_axes_grid1', ] diff --git a/lib/matplotlib/sphinxext/plot_directive.py b/lib/matplotlib/sphinxext/plot_directive.py index 74d61006ca01..e9eff33bd8fd 100644 --- a/lib/matplotlib/sphinxext/plot_directive.py +++ b/lib/matplotlib/sphinxext/plot_directive.py @@ -62,8 +62,11 @@ If provided, the code will be run in the context of all previous plot directives for which the `:context:` option was specified. This only applies to inline code plot directives, - not those run from files. If the ``:context: reset`` is specified, - the context is reset for this and future plots. + not those run from files. If the ``:context: reset`` option is + specified, the context is reset for this and future plots, and + previous figures are closed prior to running the code. + ``:context:close-figs`` keeps the context but closes previous figures + before running the code. nofigs : bool If specified, the code block will be run, but no figures will @@ -190,11 +193,9 @@ def _option_boolean(arg): def _option_context(arg): - if arg in [None, 'reset']: + if arg in [None, 'reset', 'close-figs']: return arg - else: - raise ValueError("argument should be None or 'reset'") - return directives.choice(arg, ('None', 'reset')) + raise ValueError("argument should be None or 'reset' or 'close-figs'") def _option_format(arg): @@ -333,8 +334,8 @@ def remove_coding(text): """ Remove the coding comment, which six.exec_ doesn't like. """ - return re.sub( - "^#\s*-\*-\s*coding:\s*.*-\*-$", "", text, flags=re.MULTILINE) + sub_re = re.compile("^#\s*-\*-\s*coding:\s*.*-\*-$", flags=re.MULTILINE) + return sub_re.sub("", text) #------------------------------------------------------------------------------ # Template @@ -524,7 +525,8 @@ def clear_state(plot_rcparams, close=True): def render_figures(code, code_path, output_dir, output_base, context, - function_name, config, context_reset=False): + function_name, config, context_reset=False, + close_figs=False): """ Run a pyplot script and save the low and high res PNGs and a PDF in *output_dir*. @@ -600,11 +602,16 @@ def render_figures(code, code_path, output_dir, output_base, context, if context_reset: clear_state(config.plot_rcparams) + plot_context.clear() + + close_figs = not context or close_figs for i, code_piece in enumerate(code_pieces): if not context or config.plot_apply_rcparams: - clear_state(config.plot_rcparams, close=not context) + clear_state(config.plot_rcparams, close_figs) + elif close_figs: + plt.close('all') run_code(code_piece, code_path, ns, function_name) @@ -644,8 +651,8 @@ def run(arguments, content, options, state_machine, state, lineno): nofigs = 'nofigs' in options options.setdefault('include-source', config.plot_include_source) - context = 'context' in options - context_reset = True if (context and options['context'] == 'reset') else False + keep_context = 'context' in options + context_opt = None if not keep_context else options['context'] rst_file = document.attributes['source'] rst_dir = os.path.dirname(rst_file) @@ -729,9 +736,15 @@ def run(arguments, content, options, state_machine, state, lineno): # make figures try: - results = render_figures(code, source_file_name, build_dir, output_base, - context, function_name, config, - context_reset=context_reset) + results = render_figures(code, + source_file_name, + build_dir, + output_base, + keep_context, + function_name, + config, + context_reset=context_opt == 'reset', + close_figs=context_opt == 'close-figs') errors = [] except PlotError as err: reporter = state.memo.reporter diff --git a/lib/matplotlib/sphinxext/tests/__init__.py b/lib/matplotlib/sphinxext/tests/__init__.py new file mode 100644 index 000000000000..7a8947f7fb9b --- /dev/null +++ b/lib/matplotlib/sphinxext/tests/__init__.py @@ -0,0 +1 @@ +# Make tests a package diff --git a/lib/matplotlib/sphinxext/tests/test_tinypages.py b/lib/matplotlib/sphinxext/tests/test_tinypages.py new file mode 100644 index 000000000000..2e78ec88f7f0 --- /dev/null +++ b/lib/matplotlib/sphinxext/tests/test_tinypages.py @@ -0,0 +1,85 @@ +""" Tests for tinypages build using sphinx extensions """ + +import shutil +import tempfile + +from os.path import (join as pjoin, dirname, isdir) + +from subprocess import call, Popen, PIPE + +from nose import SkipTest +from nose.tools import assert_true + +HERE = dirname(__file__) +TINY_PAGES = pjoin(HERE, 'tinypages') + + +def setup(): + # Check we have the sphinx-build command + try: + ret = call(['sphinx-build', '--help'], stdout=PIPE, stderr=PIPE) + except OSError: + raise SkipTest('Need sphinx-build on path for these tests') + if ret != 0: + raise RuntimeError('sphinx-build does not return 0') + + +def file_same(file1, file2): + with open(file1, 'rb') as fobj: + contents1 = fobj.read() + with open(file2, 'rb') as fobj: + contents2 = fobj.read() + return contents1 == contents2 + + +class TestTinyPages(object): + # Test build and output of tinypages project + + @classmethod + def setup_class(cls): + cls.page_build = tempfile.mkdtemp() + try: + cls.html_dir = pjoin(cls.page_build, 'html') + cls.doctree_dir = pjoin(cls.page_build, 'doctrees') + # Build the pages with warnings turned into errors + cmd = ['sphinx-build', '-W', '-b', 'html', + '-d', cls.doctree_dir, + TINY_PAGES, + cls.html_dir] + proc = Popen(cmd, stdout=PIPE, stderr=PIPE) + out, err = proc.communicate() + except Exception as e: + shutil.rmtree(cls.page_build) + raise e + if proc.returncode != 0: + shutil.rmtree(cls.page_build) + raise RuntimeError('sphinx-build failed with stdout:\n' + '{0}\nstderr:\n{1}\n'.format( + out, err)) + + @classmethod + def teardown_class(cls): + shutil.rmtree(cls.page_build) + + def test_some_plots(self): + assert_true(isdir(self.html_dir)) + + def plot_file(num): + return pjoin(self.html_dir, 'some_plots-{0}.png'.format(num)) + + range_10, range_6, range_4 = [plot_file(i) for i in range(1, 4)] + # Plot 5 is range(6) plot + assert_true(file_same(range_6, plot_file(5))) + # Plot 7 is range(4) plot + assert_true(file_same(range_4, plot_file(7))) + # Plot 11 is range(10) plot + assert_true(file_same(range_10, plot_file(11))) + # Plot 12 uses the old range(10) figure and the new range(6) figure + assert_true(file_same(range_10, plot_file('12_00'))) + assert_true(file_same(range_6, plot_file('12_01'))) + # Plot 13 shows close-figs in action + assert_true(file_same(range_4, plot_file(13))) + # Plot 14 has included source + with open(pjoin(self.html_dir, 'some_plots.html'), 'rt') as fobj: + html_contents = fobj.read() + assert_true('# Only a comment' in html_contents) diff --git a/lib/matplotlib/sphinxext/tests/tinypages/.gitignore b/lib/matplotlib/sphinxext/tests/tinypages/.gitignore new file mode 100644 index 000000000000..69fa449dd96e --- /dev/null +++ b/lib/matplotlib/sphinxext/tests/tinypages/.gitignore @@ -0,0 +1 @@ +_build/ diff --git a/lib/matplotlib/sphinxext/tests/tinypages/README.md b/lib/matplotlib/sphinxext/tests/tinypages/README.md new file mode 100644 index 000000000000..e53d0adc5bdd --- /dev/null +++ b/lib/matplotlib/sphinxext/tests/tinypages/README.md @@ -0,0 +1,3 @@ +# Test project for matplotlib sphinx extensions + +A tiny sphinx project from ``sphinx-quickstart`` with all default answers. diff --git a/lib/matplotlib/sphinxext/tests/tinypages/_static/.gitignore b/lib/matplotlib/sphinxext/tests/tinypages/_static/.gitignore new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/lib/matplotlib/sphinxext/tests/tinypages/_static/README.txt b/lib/matplotlib/sphinxext/tests/tinypages/_static/README.txt new file mode 100644 index 000000000000..ebde2c4b4ab7 --- /dev/null +++ b/lib/matplotlib/sphinxext/tests/tinypages/_static/README.txt @@ -0,0 +1,7 @@ +############################## +Static directory for tinypages +############################## + +We need this README file to make sure the ``_static`` directory gets created +in the installation. The tests check for warnings in builds, and, when the +``_static`` directory is absent, this raises a warning. diff --git a/lib/matplotlib/sphinxext/tests/tinypages/conf.py b/lib/matplotlib/sphinxext/tests/tinypages/conf.py new file mode 100644 index 000000000000..22217fc97f75 --- /dev/null +++ b/lib/matplotlib/sphinxext/tests/tinypages/conf.py @@ -0,0 +1,259 @@ +# -*- coding: utf-8 -*- +# +# tinypages documentation build configuration file, created by +# sphinx-quickstart on Tue Mar 18 11:58:34 2014. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +from os.path import join as pjoin, abspath + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) +sys.path.insert(0, abspath(pjoin('..', '..'))) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = ['matplotlib.sphinxext.plot_directive'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'tinypages' +copyright = u'2014, Matplotlib developers' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.1' +# The full version, including alpha/beta/rc tags. +release = '0.1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'tinypagesdoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ('index', 'tinypages.tex', u'tinypages Documentation', + u'Matplotlib developers', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'tinypages', u'tinypages Documentation', + [u'Matplotlib developers'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'tinypages', u'tinypages Documentation', + u'Matplotlib developers', 'tinypages', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False diff --git a/lib/matplotlib/sphinxext/tests/tinypages/index.rst b/lib/matplotlib/sphinxext/tests/tinypages/index.rst new file mode 100644 index 000000000000..3905483a8a57 --- /dev/null +++ b/lib/matplotlib/sphinxext/tests/tinypages/index.rst @@ -0,0 +1,21 @@ +.. tinypages documentation master file, created by + sphinx-quickstart on Tue Mar 18 11:58:34 2014. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to tinypages's documentation! +===================================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + some_plots + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/lib/matplotlib/sphinxext/tests/tinypages/some_plots.rst b/lib/matplotlib/sphinxext/tests/tinypages/some_plots.rst new file mode 100644 index 000000000000..d80b8c43f56b --- /dev/null +++ b/lib/matplotlib/sphinxext/tests/tinypages/some_plots.rst @@ -0,0 +1,116 @@ +########## +Some plots +########## + +Plot 1 does not use context: + +.. plot:: + + plt.plot(range(10)) + a = 10 + +Plot 2 doesn't use context either; has length 6: + +.. plot:: + + plt.plot(range(6)) + +Plot 3 has length 4: + +.. plot:: + + plt.plot(range(4)) + +Plot 4 shows that a new block with context does not see the variable defined +in the no-context block: + +.. plot:: + :context: + + assert 'a' not in globals() + +Plot 5 defines ``a`` in a context block: + +.. plot:: + :context: + + plt.plot(range(6)) + a = 10 + +Plot 6 shows that a block with context sees the new variable. It also uses +``:nofigs:``: + +.. plot:: + :context: + :nofigs: + + assert a == 10 + b = 4 + +Plot 7 uses a variable previously defined in previous ``nofigs`` context. It +also closes any previous figures to create a fresh figure: + +.. plot:: + :context: close-figs + + assert b == 4 + plt.plot(range(b)) + +Plot 8 shows that a non-context block still doesn't have ``a``: + +.. plot:: + :nofigs: + + assert 'a' not in globals() + +Plot 9 has a context block, and does have ``a``: + +.. plot:: + :context: + :nofigs: + + assert a == 10 + +Plot 10 resets context, and ``a`` has gone again: + +.. plot:: + :context: reset + :nofigs: + + assert 'a' not in globals() + c = 10 + +Plot 11 continues the context, we have the new value, but not the old: + +.. plot:: + :context: + + assert c == 10 + assert 'a' not in globals() + plt.plot(range(c)) + +Plot 12 opens a new figure. By default the directive will plot both the first +and the second figure: + +.. plot:: + :context: + + plt.figure() + plt.plot(range(6)) + +Plot 13 shows ``close-figs`` in action. ``close-figs`` closes all figures +previous to this plot directive, so we get always plot the figure we create in +the directive: + +.. plot:: + :context: close-figs + + plt.figure() + plt.plot(range(4)) + +Plot 14 uses ``include-source``: + +.. plot:: + :include-source: + + # Only a comment diff --git a/setupext.py b/setupext.py index 604a62551a3d..61e25bc072c0 100755 --- a/setupext.py +++ b/setupext.py @@ -672,6 +672,7 @@ def check(self): def get_packages(self): return [ 'matplotlib.tests', + 'matplotlib.sphinxext.tests', ] def get_package_data(self): @@ -686,6 +687,9 @@ def get_package_data(self): 'tests/mpltest.ttf', 'tests/test_rcparams.rc', 'tests/test_utf32_be_rcparams.rc', + 'sphinxext/tests/tinypages/*.rst', + 'sphinxext/tests/tinypages/*.py', + 'sphinxext/tests/tinypages/_static/*', ]} def get_install_requires(self):