diff -Nru sphinx-gallery-0.6.2/appveyor.yml sphinx-gallery-0.7.0/appveyor.yml --- sphinx-gallery-0.6.2/appveyor.yml 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/appveyor.yml 2020-05-21 17:28:31.000000000 +0000 @@ -10,7 +10,7 @@ PIP_DEPENDENCIES: "ipython" matrix: - - PYTHON_VERSION: "3.5" + - PYTHON_VERSION: "3.6" PYTHON_ARCH: "64" - PYTHON_VERSION: "3.7" PYTHON_ARCH: "64" diff -Nru sphinx-gallery-0.6.2/CHANGES.rst sphinx-gallery-0.7.0/CHANGES.rst --- sphinx-gallery-0.6.2/CHANGES.rst 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/CHANGES.rst 2020-05-21 17:28:31.000000000 +0000 @@ -1,14 +1,77 @@ Change Log ========== -v0.6.2 +v0.7.0 ------ -- Patch release due to missing CSS files in v0.6.1. Manifest check added to CI. - Developer changes ''''''''''''''''' +- Use Sphinx errors rather than built-in errors. + +**Implemented enhancements:** + +- ENH: Use Sphinx errors `#690 `__ (`larsoner `__) +- ENH: Add support for FuncAnimation `#687 `__ (`larsoner `__) +- Sphinx directive to insert mini-galleries `#685 `__ (`ayshih `__) +- Provide a Sphinx directive to insert a mini-gallery `#683 `__ +- ENH Add cross ref label to template module.rst `#680 `__ (`lucyleeow `__) +- ENH: Add show_memory extension API `#677 `__ (`larsoner `__) +- Support for GPU memory logging `#671 `__ +- ENH Add alt attribute for thumbnails `#668 `__ (`lucyleeow `__) +- ENH More informative ‘alt’ attribute for thumbnails in index `#664 `__ +- ENH More informative ‘alt’ attribute for images `#663 `__ (`lucyleeow `__) +- ENH: Use optipng when requested `#656 `__ (`larsoner `__) +- thumbnails cause heavy gallery pages and long loading time `#655 `__ +- MAINT: Better error messages `#600 `__ +- More informative “alt” attribute for image tags `#538 `__ +- ENH: easy linking to “examples using my_function” `#496 `__ +- sub-galleries should be generated with a separate “gallery rst” file `#413 `__ +- matplotlib animations support `#150 `__ + +**Fixed bugs:** + +- Add backref label for classes in module.rst `#688 `__ (`lucyleeow `__) +- Fixed backreference inspection to account for tilde use `#684 `__ (`ayshih `__) +- Fix regex for numpy RandomState in test_full `#682 `__ (`lucyleeow `__) +- fix tests regex to search for numpy data in html `#681 `__ +- FIX: Fix sys.stdout patching `#678 `__ (`larsoner `__) +- check-manifest causing master to fail `#675 `__ +- Output of logger is not captured if the logger is created in a different cell `#672 `__ +- FIX: Remove newlines from title `#669 `__ (`larsoner `__) +- BUG Tinybuild autosummary links fail with Sphinx dev `#659 `__ + +**Documentation:** + +- DOC Update label to raw string in plot_0_sin.py `#674 `__ (`lucyleeow `__) +- DOC Update Sphinx url to https `#673 `__ (`lucyleeow `__) +- DOC Clarify syntax.rst `#670 `__ (`lucyleeow `__) +- DOC Note config comment removal in code only `#667 `__ (`lucyleeow `__) +- DOC Update links in syntax.rst `#666 `__ (`lucyleeow `__) +- DOC Fix typos, clarify `#662 `__ (`lucyleeow `__) +- DOC Update html-noplot `#658 `__ (`lucyleeow `__) +- DOC: Fix PNGScraper example `#653 `__ (`denkii `__) +- DOC: Fix typos in documentation files. `#652 `__ (`TomDonoghue `__) +- Inconsistency with applying & removing sphinx gallery configs `#665 `__ +- ``make html-noplot`` instructions outdated `#606 `__ + +**Closed issues:** + +- intersphinx links need backreferences_dir `#467 `__ + +**Merged pull requests:** + +- Fix lint in gen_gallery.py `#686 `__ (`lucyleeow `__) +- Better alt thumbnail test for punctuation in title `#679 `__ (`lucyleeow `__) +- Update manifest for changes to check-manifest `#676 `__ (`lucyleeow `__) +- MAINT: Update CircleCI `#657 `__ (`larsoner `__) +- Bump version to 0.7.0.dev0 `#651 `__ (`lucyleeow `__) + +v0.6.2 +------ + +- Patch release due to missing CSS files in v0.6.1. Manifest check added to CI. + **Implemented enhancements:** - How do I best cite sphinx-gallery? `#639 `__ diff -Nru sphinx-gallery-0.6.2/.circleci/config.yml sphinx-gallery-0.7.0/.circleci/config.yml --- sphinx-gallery-0.6.2/.circleci/config.yml 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/.circleci/config.yml 2020-05-21 17:28:31.000000000 +0000 @@ -2,11 +2,11 @@ jobs: build_docs: docker: - - image: circleci/python:3.6-stretch + - image: circleci/python:3.7.7-buster steps: # Get our data and merge with upstream - run: sudo apt-get update - - run: sudo apt-get --no-install-recommends install -y texlive texlive-latex-extra latexmk + - run: sudo apt-get --no-install-recommends install -y texlive texlive-latex-extra latexmk libxkbcommon-x11-0 optipng - checkout - run: echo $(git log -1 --pretty=%B) | tee gitlog.txt - run: echo ${CI_PULL_REQUEST//*pull\//} | tee merge.txt @@ -24,9 +24,7 @@ - restore_cache: keys: - cache-pip - # PyQt5 5.12 causes problems with libxcb - # We use a specific commit of sphinx to get --keep-going support in setup.py build (as of 2019/05/31, it's not released) - - run: pip install --user numpy matplotlib "pyqt5<5.12" seaborn sphinx_rtd_theme pillow joblib https://api.github.com/repos/sphinx-doc/sphinx/zipball/925bc187eacbc0fbdd2c56f360a040a23cb13145 pytest vtk traits traitsui pyface mayavi memory_profiler ipython pandas + - run: pip install --user numpy matplotlib pyqt5 seaborn sphinx_rtd_theme pillow joblib sphinx pytest vtk traits traitsui pyface mayavi memory_profiler ipython pandas - save_cache: key: cache-pip paths: diff -Nru sphinx-gallery-0.6.2/debian/changelog sphinx-gallery-0.7.0/debian/changelog --- sphinx-gallery-0.6.2/debian/changelog 2020-05-13 02:18:52.000000000 +0000 +++ sphinx-gallery-0.7.0/debian/changelog 2020-07-19 23:47:19.000000000 +0000 @@ -1,3 +1,19 @@ +sphinx-gallery (0.7.0-1) unstable; urgency=medium + + * New upstream release + * debian/control + - add optipng to b-d and recommends + - bump b-d on matplotlib to >= 3.2.2 + * refresh patches + * debian/rules + - temporary remove plot_8_animations.py, triggers a bug with matplotlib + 3.2.2 + * debian/patches/doc-intersphinx.patch + - fix sklearn intersphinx location + - temporary skip test_full.py, triggers a bug with matplotlib 3.2.2 + + -- Sandro Tosi Sun, 19 Jul 2020 19:47:19 -0400 + sphinx-gallery (0.6.2-2) unstable; urgency=medium * debian/rules diff -Nru sphinx-gallery-0.6.2/debian/control sphinx-gallery-0.7.0/debian/control --- sphinx-gallery-0.6.2/debian/control 2020-05-13 02:18:52.000000000 +0000 +++ sphinx-gallery-0.7.0/debian/control 2020-07-19 23:47:19.000000000 +0000 @@ -5,15 +5,16 @@ Uploaders: Debian Python Modules Team , Build-Depends: debhelper-compat (= 9), dh-python, + optipng, python-matplotlib-doc , python-numpy-doc , python-pandas-doc , - python-scipy-doc , + python-sklearn-doc , python3-all, python3-doc , python3-ipython , python3-joblib , - python3-matplotlib, + python3-matplotlib (>= 3.2.2), python3-memory-profiler , python3-pil, python3-pytest , @@ -36,6 +37,7 @@ python3-sphinx, ${misc:Depends}, ${python3:Depends}, +Recommends: optipng, Suggests: python-sphinx-gallery-doc, python3-seaborn, Description: extension that builds an HTML gallery of examples from Python scripts (Python 3) diff -Nru sphinx-gallery-0.6.2/debian/patches/doc-intersphinx.patch sphinx-gallery-0.7.0/debian/patches/doc-intersphinx.patch --- sphinx-gallery-0.6.2/debian/patches/doc-intersphinx.patch 2020-05-13 02:18:52.000000000 +0000 +++ sphinx-gallery-0.7.0/debian/patches/doc-intersphinx.patch 2020-07-19 23:47:19.000000000 +0000 @@ -5,16 +5,16 @@ # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { - 'python': ('https://docs.python.org/{.major}'.format(sys.version_info), None), -- 'numpy': ('https://docs.scipy.org/doc/numpy', None), +- 'numpy': ('https://numpy.org/doc/stable/', None), - 'matplotlib': ('https://matplotlib.org/', None), - 'mayavi': ('http://docs.enthought.com/mayavi/mayavi', None), - 'sklearn': ('https://scikit-learn.org/stable', None), -- 'sphinx': ('http://www.sphinx-doc.org/en/stable', None), -- 'pandas': ('http://pandas.pydata.org/pandas-docs/stable/', None), +- 'sphinx': ('https://www.sphinx-doc.org/en/stable', None), +- 'pandas': ('https://pandas.pydata.org/pandas-docs/stable/', None), + 'python': ('/usr/share/doc/python3-doc/html', None), + 'numpy': ('/usr/share/doc/python-numpy-doc/html', None), -+ 'scipy': ('/usr/share/doc/python-scipy-doc/html', None), + 'matplotlib': ('/usr/share/doc/python-matplotlib-doc/html', None), ++ 'sklearn': ('/usr/share/doc/python-sklearn-doc/html', None), + 'sphinx': ('/usr/share/doc/sphinx-doc/html', None), + 'pandas': ('/usr/share/doc/python-pandas-doc/html', None), + diff -Nru sphinx-gallery-0.6.2/debian/patches/doc-no-mayavi.patch sphinx-gallery-0.7.0/debian/patches/doc-no-mayavi.patch --- sphinx-gallery-0.6.2/debian/patches/doc-no-mayavi.patch 2020-05-13 02:18:52.000000000 +0000 +++ sphinx-gallery-0.7.0/debian/patches/doc-no-mayavi.patch 2020-07-19 23:47:19.000000000 +0000 @@ -1,6 +1,6 @@ --- a/doc/configuration.rst +++ b/doc/configuration.rst -@@ -866,7 +866,7 @@ The following scrapers are supported: +@@ -932,7 +932,7 @@ The following scrapers are supported: ``'matplotlib'``. - Mayavi Sphinx-gallery maintains a scraper for diff -Nru sphinx-gallery-0.6.2/debian/patches/test-fixes.patch sphinx-gallery-0.7.0/debian/patches/test-fixes.patch --- sphinx-gallery-0.6.2/debian/patches/test-fixes.patch 2020-05-13 02:18:52.000000000 +0000 +++ sphinx-gallery-0.7.0/debian/patches/test-fixes.patch 2020-07-19 23:47:19.000000000 +0000 @@ -1,6 +1,6 @@ --- a/sphinx_gallery/tests/test_full.py +++ b/sphinx_gallery/tests/test_full.py -@@ -140,7 +140,7 @@ def test_run_sphinx(sphinx_app): +@@ -153,7 +153,7 @@ def test_run_sphinx(sphinx_app): assert 'after excluding 0' in status # intentionally have a bad URL in references warning = sphinx_app._warning.getvalue() @@ -9,18 +9,7 @@ assert re.match(want, warning, re.DOTALL) is not None, warning -@@ -231,8 +231,8 @@ def test_embed_links_and_styles(sphinx_a - # gh-587: np.random.RandomState links properly - # NumPy has had this linked as numpy.random.RandomState and - # numpy.random.mtrand.RandomState so we need regex... -- assert re.search(r'\.html#numpy\.random\.(mtrand\.?)RandomState" title="numpy\.random\.(mtrand\.?)RandomState" class="sphx-glr-backref-module-numpy-random(-mtrand?) sphx-glr-backref-type-py-class">np', lines) is not None # noqa: E501 -- assert re.search(r'\.html#numpy\.random\.(mtrand\.?)RandomState" title="numpy\.random\.(mtrand\.?)RandomState" class="sphx-glr-backref-module-numpy-random(-mtrand?) sphx-glr-backref-type-py-class sphx-glr-backref-instance">rng', lines) is not None # noqa: E501 -+ assert re.search(r'\.html#numpy\.random\.(mtrand\.?)?RandomState" title="numpy\.random\.(mtrand\.?)?RandomState" class="sphx-glr-backref-module-numpy-random(-mtrand?)? sphx-glr-backref-type-py-class">np', lines) is not None # noqa: E501 -+ assert re.search(r'\.html#numpy\.random\.(mtrand\.?)?RandomState" title="numpy\.random\.(mtrand\.?)?RandomState" class="sphx-glr-backref-module-numpy-random(-mtrand?)? sphx-glr-backref-type-py-class sphx-glr-backref-instance">rng', lines) is not None # noqa: E501 - # gh-587: methods of classes in the module currently being documented - # issue 617 (regex '-'s) - # instance -@@ -283,10 +283,11 @@ def test_embed_links_and_styles(sphinx_a +@@ -305,10 +305,11 @@ def test_embed_links_and_styles(sphinx_a assert re.match(want_warn, lines, re.DOTALL) is not None sys.stdout.write(lines) diff -Nru sphinx-gallery-0.6.2/debian/patches/test-intersphinx.patch sphinx-gallery-0.7.0/debian/patches/test-intersphinx.patch --- sphinx-gallery-0.6.2/debian/patches/test-intersphinx.patch 2020-05-13 02:18:52.000000000 +0000 +++ sphinx-gallery-0.7.0/debian/patches/test-intersphinx.patch 2020-07-19 23:47:19.000000000 +0000 @@ -5,7 +5,7 @@ exclude_patterns = ['_build'] intersphinx_mapping = { - 'python': ('https://docs.python.org/3', None), -- 'numpy': ('https://docs.scipy.org/doc/numpy', None), +- 'numpy': ('https://numpy.org/doc/stable/', None), - 'matplotlib': ('https://matplotlib.org/', None), - 'joblib': ('https://joblib.readthedocs.io/en/latest', None), + 'python': ('/usr/share/doc/python3-doc/html', None), diff -Nru sphinx-gallery-0.6.2/debian/rules sphinx-gallery-0.7.0/debian/rules --- sphinx-gallery-0.6.2/debian/rules 2020-05-13 02:18:52.000000000 +0000 +++ sphinx-gallery-0.7.0/debian/rules 2020-07-19 23:47:19.000000000 +0000 @@ -1,7 +1,7 @@ #!/usr/bin/make -f export PYBUILD_NAME=sphinx-gallery -export PYBUILD_AFTER_BUILD=PYTHONPATH={build_dir} $(MAKE) -C doc html +export PYBUILD_AFTER_BUILD=rm -rf $(CURDIR)/examples/plot_8_animations.py ; PYTHONPATH={build_dir} $(MAKE) -C doc html export PYBUILD_AFTER_INSTALL=rm -rf $(CURDIR)/debian/*/usr/bin %: @@ -15,4 +15,4 @@ dh_installdocs -ppython-sphinx-gallery-doc examples/ mayavi_examples/ tutorials/ doc/_build/html/ override_dh_auto_test: - dh_auto_test -- --system=custom --test-args='{interpreter} -m pytest -k-test_embed_code_links_get_data sphinx_gallery/tests' + dh_auto_test -- --system=custom --test-args='{interpreter} -m pytest --ignore=sphinx_gallery/tests/test_full.py -k-test_embed_code_links_get_data sphinx_gallery/tests' diff -Nru sphinx-gallery-0.6.2/doc/advanced.rst sphinx-gallery-0.7.0/doc/advanced.rst --- sphinx-gallery-0.6.2/doc/advanced.rst 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/doc/advanced.rst 2020-05-21 17:28:31.000000000 +0000 @@ -48,10 +48,10 @@ The Gallery has been built, now you and all of your project's users can already start enjoying it. All the temporary files needed to -generate the gallery(rst files, images, chache objects, etc) are +generate the gallery (rst files, images, cache objects, etc) are stored where you configured in ``gallery_dirs``. The final files that go into the HTML version of your documentation have a particular -namespace, to avoid colisions with your own files and images. +namespace, to avoid collisions with your own files and images. Our namespace convention is to prefix everything with ``sphx_glr`` and change path separators with underscores. This is valid for @@ -154,7 +154,7 @@ SVG images are copied. .. warning:: SVG images do not work with ``latex`` build modes, thus will not - work while building a PDF vesion of your documentation. + work while building a PDF version of your documentation. Example 1: a Matplotlib and Mayavi-style scraper ------------------------------------------------ @@ -223,14 +223,14 @@ def __call__(self, block, block_vars, gallery_conf): # Find all PNG files in the directory of this example. path_current_example = os.path.dirname(block_vars['src_file']) - pngs = sorted(glob(os.path.join(os.getcwd(), '*.png')) + pngs = sorted(glob(os.path.join(path_current_example, '*.png'))) # Iterate through PNGs, copy them to the sphinx-gallery output directory image_names = list() image_path_iterator = block_vars['image_path_iterator'] for png in pngs: - if png not in seen: - seen |= set(png) + if png not in self.seen: + self.seen |= set(png) this_image_path = image_path_iterator.next() image_names.append(this_image_path) shutil.move(png, this_image_path) diff -Nru sphinx-gallery-0.6.2/doc/configuration.rst sphinx-gallery-0.7.0/doc/configuration.rst --- sphinx-gallery-0.6.2/doc/configuration.rst 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/doc/configuration.rst 2020-05-21 17:28:31.000000000 +0000 @@ -31,6 +31,7 @@ - ``plot_gallery`` (:ref:`without_execution`) - ``image_scrapers`` (and the deprecated ``find_mayavi_figures``) (:ref:`image_scrapers`) +- ``compress_images`` (:ref:`compress_images`) - ``reset_modules`` (:ref:`reset_modules`) - ``abort_on_example_error`` (:ref:`abort_on_first`) - ``expected_failing_examples`` (:ref:`dont_fail_exit`) @@ -114,7 +115,7 @@ To omit some files from the gallery entirely (i.e., not execute, parse, or add them), you can change the ``ignore_pattern`` option. -To choose which of the parsed and added Python scripts are actualy +To choose which of the parsed and added Python scripts are actually executed, you can modify ``filename_pattern``. For example:: sphinx_gallery_conf = { @@ -310,45 +311,56 @@ For example, we can embed a small gallery of all examples that use or refer to :obj:`numpy.exp`, which looks like this: -.. include:: gen_modules/backreferences/numpy.exp.examples -.. raw:: html - -
+.. minigallery:: numpy.exp + :add-heading: For such behavior to be available, you have to activate it in your Sphinx-Gallery configuration ``conf.py`` file with:: sphinx_gallery_conf = { ... - # directory where function granular galleries are stored + # directory where function/class granular galleries are stored 'backreferences_dir' : 'gen_modules/backreferences', - # Modules for which function level galleries are created. In + # Modules for which function/class level galleries are created. In # this case sphinx_gallery and numpy in a tuple of strings. 'doc_module' : ('sphinx_gallery', 'numpy')} The path you specify in ``backreferences_dir`` (here we choose ``gen_modules/backreferences``) will be populated with -ReStructuredText files. Each will contain a reduced version of the -gallery specific to every function used across all the examples -galleries and belonging to the modules listed in ``doc_module``. -``backreferences_dir` should be a string or ``pathlib.Path`` object that is +ReStructuredText files. Each .rst file will contain a reduced version of the +gallery specific to every function/class that is used across all the examples +and belonging to the modules listed in ``doc_module``. +``backreferences_dir`` should be a string or ``pathlib.Path`` object that is **relative** to the ``conf.py`` file, or ``None``. It is ``None`` by default. -Then within your sphinx documentation ``.rst`` files you write these -lines to include this reduced version of the Gallery, which has -examples in use of a specific function, in this case ``numpy.exp``:: - - .. include:: gen_modules/backreferences/numpy.exp.examples - .. raw:: html - -
- -The ``include`` directive takes a path **relative** to the ``rst`` -file it is called from. In the case of this documentation file (which -is in the same directory as ``conf.py``) we directly use the path -declared in ``backreferences_dir`` followed by the function whose -examples we want to show and the file has the ``.examples`` extension. +Within your sphinx documentation ``.rst`` files, you can use easily +add this reduced version of the Gallery. For example, the rst below adds +the reduced version of the Gallery for ``numpy.exp``, which includes all +examples that use the specific function ``numpy.exp``: + +.. code-block:: rst + + .. minigallery:: numpy.exp + :add-heading: + +The ``add-heading`` option adds a heading for the mini-gallery, which will be a +default generated message if no string is provided as an argument. The example +mini-gallery shown above uses the default heading. The level of the heading +defaults to ``^``, but can be changed using the ``heading-level`` option, which +accepts a single character (e.g., ``-``). + +You can also list multiple items, separated by spaces, which will merge all +examples into a single mini-gallery, e.g.: + +.. code-block:: rst + + .. minigallery:: numpy.exp numpy.sin + :add-heading: Mini-gallery using ``numpy.exp`` or ``numpy.sin`` + :heading-level: - + +For such a mini-gallery, specifying a custom heading message is recommended +because the default message is vague: "Examples of one of multiple objects". Auto-documenting your API with links to examples ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -392,19 +404,22 @@ The template file ``module.rst`` for the ``autosummary`` directive has to be saved in the path ``_templates/module.rst``. We present our configuration in the following block. The most relevant part is the -loop defined between lines **12-22** that parses all the functions of -the module. There we have included the snippet introduced in the -previous section. Keep in mind that the include directive is -**relative** to the file location, and module documentation files are -saved in the directory we specified in the *toctree* option of the -``autosummary`` directive used before in the ``reference.rst`` file. -The files we are including are from the ``backreferences_dir`` -configuration option setup for Sphinx-Gallery. +loop defined between lines **12-21** that parses all the functions/classes +of the module. There we have used the ``minigallery`` directive introduced in +the previous section. + +We also add a cross referencing label (on line 16) before including the +examples mini-gallery. This enables you to reference the mini-gallery for +all functions/classes of the module using +``:ref:`sphx_glr_backref_```, where '' is the full path +to the function/class using dot notation (e.g., +``sphinx_gallery.backreferences.identify_names``). For example, see: +:ref:`sphx_glr_backref_sphinx_gallery.backreferences.identify_names`. .. literalinclude:: _templates/module.rst :language: rst :lines: 3- - :emphasize-lines: 12-22, 32-42 + :emphasize-lines: 12-21, 31-38 :linenos: Toggling global variable inspection @@ -545,6 +560,10 @@ 'remove_config_comments': True, } +This only removes configuration comments from code blocks, not from text +blocks. However, note that technically, configuration comments will work when +put in either code blocks or text blocks. + .. _own_notebook_cell: Add your own first and last notebook cell @@ -560,7 +579,7 @@ %matplotlib inline Adding a last cell can be useful for performing a desired action such as -reporting on the user's evironment. By default no last cell is added. +reporting on the user's environment. By default no last cell is added. You can choose whatever text you like by modifying the ``first_notebook_cell`` and ``last_notebook_cell`` configuration parameters. For example, the gallery @@ -818,7 +837,7 @@ .. code-block:: Makefile html-noplot: - $(SPHINXBUILD) -D plot_gallery=0 -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + $(SPHINXBUILD) -D plot_gallery=0 -b html $(ALLSPHINXOPTS) $(SOURCEDIR) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." @@ -838,6 +857,36 @@ ``sphinx-build`` command. +.. _compress_images: + +Compressing images +================== + +When writing PNG files (the default scraper format), sphinx-gallery can be +configured to use ``optipng`` to optimize the PNG file sizes. Typically this +yields roughly a 50% reduction in file sizes, thus reducing the loading time +of galleries. However, it can increase build +time. The allowed values are ``'images'`` and ``'thumbnails'``, or a +tuple/list (to optimize both), such as:: + + sphinx_gallery_conf = { + ... + 'compress_images': ('images', 'thumbnails'), + } + +The default is ``()`` (no optimization) and a warning will be emitted if +optimization is requested but ``optipng`` is not available. You can also pass +additional command-line options (starting with ``'-'``), for example to +optimize less but speed up the build time you could do:: + + sphinx_gallery_conf = { + ... + 'compress_images': ('images', 'thumbnails', '-o1'), + } + +See ``$ optipng --help`` for a complete list of options. + + .. _image_scrapers: Image scrapers @@ -855,8 +904,25 @@ } The default value is ``'image_scrapers': ('matplotlib',)`` which only scrapes -Matplotlib images. Note that this includes any images produced by pacakges that -are based on Matplotlib, for example Seaborn or Yellowbrick. +Matplotlib images. Note that this includes any images produced by packages that +are based on Matplotlib, for example Seaborn or Yellowbrick. If you want +to embed :class:`matplotlib.animation.FuncAnimation`\s as animations rather +than a single static image of the animation figure, you should add:: + + sphinx_gallery_conf = { + ... + 'matplotlib_animations': True, + } + +HTML embedding options can be changed by setting ``rcParams['animation.html']`` +and related options in your +:ref:`matplotlib rcParams `. +It's also recommended to ensure that "imagemagick" is available as a +``writer``, which you can check with +:class:`matplotlib.animation.ImageMagickWriter.isAvailable() +`. +The FFmpeg writer in some light testing did not work as well for +creating GIF thumbnails for the gallery pages. The following scrapers are supported: @@ -1055,6 +1121,21 @@ 'show_memory': True, } +It's also possible to use your own custom memory reporter, for example +if you would rather see the GPU memory. In that case, ``show_memory`` must +be a callable that takes a single function to call (i.e., one generated +internally to run an individual script code block), and returns a two-element +tuple containing: + +1. The memory used in MiB while running the function, and +2. The function output + +A version of this that would always report 0 memory used would be:: + + sphinx_gallery_conf = { + ... + 'show_memory': lambda func: (0., func()), + } .. _capture_repr: @@ -1063,7 +1144,7 @@ .. note:: - Configure ``capture_repr`` to be an empty tuple (i.e.,``capture_repr: ()``) + Configure ``capture_repr`` to be an empty tuple (i.e., `capture_repr: ()`) to return to the output capturing behaviour prior to release v0.5.0. The ``capture_repr`` configuration allows the user to control what output @@ -1171,5 +1252,5 @@ sphinx_gallery_conf = { ... 'capture_repr': ('__repr__'), - 'ignore_repr_types': r'matplotlib.text|matplotlib.axes', + 'ignore_repr_types': r'matplotlib[text, axes]', } diff -Nru sphinx-gallery-0.6.2/doc/conf.py sphinx-gallery-0.7.0/doc/conf.py --- sphinx-gallery-0.6.2/doc/conf.py 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/doc/conf.py 2020-05-21 17:28:31.000000000 +0000 @@ -301,12 +301,12 @@ # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { 'python': ('https://docs.python.org/{.major}'.format(sys.version_info), None), - 'numpy': ('https://docs.scipy.org/doc/numpy', None), + 'numpy': ('https://numpy.org/doc/stable/', None), 'matplotlib': ('https://matplotlib.org/', None), 'mayavi': ('http://docs.enthought.com/mayavi/mayavi', None), 'sklearn': ('https://scikit-learn.org/stable', None), - 'sphinx': ('http://www.sphinx-doc.org/en/stable', None), - 'pandas': ('http://pandas.pydata.org/pandas-docs/stable/', None), + 'sphinx': ('https://www.sphinx-doc.org/en/stable', None), + 'pandas': ('https://pandas.pydata.org/pandas-docs/stable/', None), } examples_dirs = ['../examples', '../tutorials'] @@ -337,10 +337,11 @@ 'doc_module': ('sphinx_gallery', 'numpy'), 'reference_url': { 'sphinx_gallery': None, - }, + }, 'examples_dirs': examples_dirs, 'gallery_dirs': gallery_dirs, 'image_scrapers': image_scrapers, + 'compress_images': ('images', 'thumbnails'), # specify the order of examples to be according to filename 'within_subsection_order': FileNameSortKey, 'expected_failing_examples': ['../examples/no_output/plot_raise.py', @@ -359,6 +360,7 @@ # capture raw HTML or, if not present, __repr__ of last expression in # each code block 'capture_repr': ('_repr_html_', '__repr__'), + 'matplotlib_animations': True, } # Remove matplotlib agg warnings from generated doc when using plt.show diff -Nru sphinx-gallery-0.6.2/doc/getting_started.rst sphinx-gallery-0.7.0/doc/getting_started.rst --- sphinx-gallery-0.6.2/doc/getting_started.rst 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/doc/getting_started.rst 2020-05-21 17:28:31.000000000 +0000 @@ -16,7 +16,7 @@ download. * Create a gallery with thumbnails for each of these examples (such as `the one that scikit-learn - `_ uses) + `_ uses). A `template repository `_, with sample example galleries and basic configurations is also available to @@ -93,7 +93,7 @@ `_ selected) output of the script. Files without that prefix will be only parsed and presented in a rich literate programming fashion, without any output. To - change the default pattern for execution and capture see + change the default file pattern for execution and capture see :ref:`build_pattern`. * The output that is captured while executing the ``.py`` files and subsequently incorporated into the built documentation can be finely @@ -126,8 +126,9 @@ ``...`` represents your other loaded extensions. Next, create your configuration dictionary for Sphinx-Gallery. Here we will -simply tell Sphinx-Gallery the location of the 'examples' directory -(containing the gallery header file and our example Python scripts) and the +simply set the minimal required configurations. We must set the location of +the 'examples' directory (containing the gallery header file and our example +Python scripts) and the directory to place the output files generated. The path to both of these directories should be relative to the ``doc/conf.py`` file. @@ -143,8 +144,8 @@ After building your documentation, ``gallery_dirs`` will contain the following files and directories: -* ``index.rst`` - the master document of the gallery containing the Gallery - Header, table of contents tree and thumbnails for each example. It will serve +* ``index.rst`` - the master document of the gallery containing the gallery + header, table of contents tree and thumbnails for each example. It will serve as the welcome page for that gallery. * ``sg_execution_times.rst`` - execution time of all example ``.py`` files, summarised in table format (`original pull request on GitHub @@ -152,7 +153,7 @@ * ``images`` - directory containing images produced during execution of the example ``.py`` files (more details in :ref:`image_scrapers`) and thumbnail images for the gallery. -* A directory for each sub-directory in ``'example_dirs'``. Within each +* A directory for each sub-directory in ``'example_dirs'``. Within each directory will be the above and below listed files for that 'sub-gallery'. Additionally for **each** ``.py`` file, a file with the following suffix is diff -Nru sphinx-gallery-0.6.2/doc/maintainers.rst sphinx-gallery-0.7.0/doc/maintainers.rst --- sphinx-gallery-0.6.2/doc/maintainers.rst 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/doc/maintainers.rst 2020-05-21 17:28:31.000000000 +0000 @@ -23,7 +23,7 @@ You should double-check a few things to make sure that you can create a new release for Sphinx Gallery. -1. Ensure that you **registered an acccount** on `the PyPI index `_. +1. Ensure that you **registered an account** on `the PyPI index `_. 2. Ensure you have **push access** to the `Sphinx Gallery pypi repository `_. Ask one of the Sphinx Gallery core developers if you do not. diff -Nru sphinx-gallery-0.6.2/doc/reference.rst sphinx-gallery-0.7.0/doc/reference.rst --- sphinx-gallery-0.6.2/doc/reference.rst 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/doc/reference.rst 2020-05-21 17:28:31.000000000 +0000 @@ -29,3 +29,18 @@ downloads sorting binder + directives + +.. currentmodule:: sphinx_gallery.utils + +.. automodule:: sphinx_gallery.utils + :no-members: + :no-inherited-members: + +:py:mod:`sphinx_gallery.utils`: + +.. autosummary:: + :toctree: gen_modules/ + :template: module.rst + + optipng diff -Nru sphinx-gallery-0.6.2/doc/_static/theme_override.css sphinx-gallery-0.7.0/doc/_static/theme_override.css --- sphinx-gallery-0.6.2/doc/_static/theme_override.css 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/doc/_static/theme_override.css 2020-05-21 17:28:31.000000000 +0000 @@ -29,3 +29,7 @@ text-decoration: underline; background-color: #E6E6E6; } + +.anim-state label { + display: inline-block; +} \ No newline at end of file diff -Nru sphinx-gallery-0.6.2/doc/syntax.rst sphinx-gallery-0.7.0/doc/syntax.rst --- sphinx-gallery-0.6.2/doc/syntax.rst 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/doc/syntax.rst 2020-05-21 17:28:31.000000000 +0000 @@ -46,13 +46,16 @@ You can embed rST in your Python examples by including a line of >= 20 ``#`` symbols, ``#%%``, or ``# %%``. For consistency, it is recommended that you use only one of the above three 'block splitter' options in your project. If using -``#``'s, we recommend using 79 ``#``'s, like this:: +a line of ``#``'s, we recommend using 79 ``#``'s, like this:: ############################################################################### -Any commented lines (a line beginning with ``#`` followed by a space, to be -PEP8-compliant) that immediately follow a block splitter will be rendered as -rST in the built gallery examples. For example:: +Any commented lines (line beginning with ``#`` followed by a space, to +be PEP8-compliant) that immediately follow a block splitter will be rendered as +rST in the built gallery examples. To switch back to writing code, either +stop starting lines with ``#`` and a space or leave an empty line before writing +code comments. You can thus easily alternate between text and code 'blocks'. +For example:: # This is commented python myvariable = 2 @@ -62,25 +65,36 @@ # This is a section header # ------------------------ # - # In the built documentation, it will be rendered as rST. + # In the built documentation, it will be rendered as rST. All rST lines + # must begin with '# ' (note the space) including underlines below section + # headers. # These lines won't be rendered as rST because there is a gap after the last # commented rST block. Instead, they'll resolve as regular Python comments. + # Normal Python code can follow these comments. print('my variable plus 2 is {}'.format(myvariable + 2)) The ``#%%`` and ``# %%`` syntax is consistent with the 'code block' (or -'code cell') separator syntax in `Jupyter VSCode plugin -`_, `Jupytext -`_, `Pycharm +'code cell') separator syntax in `Visual Studio Code Python extension +`_, +`Visual Studio Python Tools +`_, +`Jupytext +`_, +`Pycharm Professional `_, `Hydrogen plugin (for Atom) -`_ and `Spyder -`_. Note that although the +`_ +and `Spyder +`_. +Note that although the documentation may only mention one of ``#%%`` or ``# %%``, in practice both -work in these editors. In these IDEs (or with these IDE plugins), ``#%%`` or +work. With these editors/IDEs, ``#%%`` or ``# %%`` at the start of a line signifies the start of a new code block. -Code within a code block can be easily executed together, at the same time. This -functionality can be helpful when writing a Sphinx-Gallery ``.py`` example as +Code blocks allow you to separate your code into chunks, like in Jupyter +Notebooks. All the code within a code block can be easily executed together. +This functionality can be helpful when writing a Sphinx-Gallery ``.py`` +example as the blocks allow you to easily create pairs of subsequent Sphinx-Gallery text and code blocks. diff -Nru sphinx-gallery-0.6.2/doc/_templates/module.rst sphinx-gallery-0.7.0/doc/_templates/module.rst --- sphinx-gallery-0.6.2/doc/_templates/module.rst 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/doc/_templates/module.rst 2020-05-21 17:28:31.000000000 +0000 @@ -16,11 +16,10 @@ .. autofunction:: {{ item }} - .. include:: backreferences/{{fullname}}.{{item}}.examples + .. _sphx_glr_backref_{{fullname}}.{{item}}: - .. raw:: html - -
+ .. minigallery:: {{fullname}}.{{item}} + :add-heading: {%- endfor %} {% endif %} @@ -36,11 +35,10 @@ .. autoclass:: {{ item }} :members: - .. include:: backreferences/{{fullname}}.{{item}}.examples - - .. raw:: html + .. _sphx_glr_backref_{{fullname}}.{{item}}: -
+ .. minigallery:: {{fullname}}.{{item}} + :add-heading: {%- endfor %} {% endif %} diff -Nru sphinx-gallery-0.6.2/examples/plot_0_sin.py sphinx-gallery-0.7.0/examples/plot_0_sin.py --- sphinx-gallery-0.6.2/examples/plot_0_sin.py 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/examples/plot_0_sin.py 2020-05-21 17:28:31.000000000 +0000 @@ -47,8 +47,8 @@ y = np.sin(x) plt.plot(x, y) -plt.xlabel('$x$') -plt.ylabel('$\sin(x)$') +plt.xlabel(r'$x$') +plt.ylabel(r'$\sin(x)$') # To avoid matplotlib text output plt.show() diff -Nru sphinx-gallery-0.6.2/examples/plot_1_exp.py sphinx-gallery-0.7.0/examples/plot_1_exp.py --- sphinx-gallery-0.6.2/examples/plot_1_exp.py 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/examples/plot_1_exp.py 2020-05-21 17:28:31.000000000 +0000 @@ -29,11 +29,13 @@ plt.plot(x, y) plt.xlabel('$x$') plt.ylabel('$\exp(x)$') + plt.title('Exponential function') plt.figure() plt.plot(x, -np.exp(-x)) plt.xlabel('$x$') plt.ylabel('$-\exp(-x)$') + plt.title('Negative exponential\nfunction') # To avoid matplotlib text output plt.show() diff -Nru sphinx-gallery-0.6.2/examples/plot_8_animations.py sphinx-gallery-0.7.0/examples/plot_8_animations.py --- sphinx-gallery-0.6.2/examples/plot_8_animations.py 1970-01-01 00:00:00.000000000 +0000 +++ sphinx-gallery-0.7.0/examples/plot_8_animations.py 2020-05-21 17:28:31.000000000 +0000 @@ -0,0 +1,25 @@ +""" +Animation support +================= + +Show an animation, which should end up nicely embedded below. +""" + +import numpy as np +import matplotlib.pyplot as plt +import matplotlib.animation as animation + +# Adapted from +# https://matplotlib.org/gallery/animation/basic_example.html + + +def _update_line(num): + line.set_data(data[..., :num]) + return line, + + +fig, ax = plt.subplots() +data = np.random.RandomState(0).rand(2, 25) +line, = ax.plot([], [], 'r-') +ax.set(xlim=(0, 1), ylim=(0, 1)) +ani = animation.FuncAnimation(fig, _update_line, 25, interval=100, blit=True) diff -Nru sphinx-gallery-0.6.2/MANIFEST.in sphinx-gallery-0.7.0/MANIFEST.in --- sphinx-gallery-0.6.2/MANIFEST.in 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/MANIFEST.in 2020-05-21 17:28:31.000000000 +0000 @@ -18,11 +18,11 @@ recursive-exclude doc * exclude doc exclude *.yml -exclude *.pyc +global-exclude *.pyc exclude dev-requirements.txt recursive-exclude .circleci * exclude .circleci -recursive-exclude */__pycache__ * +prune **/__pycache__ exclude __pycache__ recursive-exclude */gen_modules * exclude gen_modules diff -Nru sphinx-gallery-0.6.2/README.rst sphinx-gallery-0.7.0/README.rst --- sphinx-gallery-0.6.2/README.rst 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/README.rst 2020-05-21 17:28:31.000000000 +0000 @@ -68,7 +68,9 @@ * Seaborn * Mayavi -For much of this functionality, you will need `pillow`. +For much of this functionality, you will need ``pillow``. We also recommend +installing system ``optipng`` binaries to reduce the file sizes of the +generated PNG files. Install as a Sphinx-gallery developer ------------------------------------- diff -Nru sphinx-gallery-0.6.2/sphinx_gallery/backreferences.py sphinx-gallery-0.7.0/sphinx_gallery/backreferences.py --- sphinx-gallery-0.6.2/sphinx_gallery/backreferences.py 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/sphinx_gallery/backreferences.py 2020-05-21 17:28:31.000000000 +0000 @@ -18,6 +18,8 @@ import re import warnings +from sphinx.errors import ExtensionError + from . import sphinx_compatibility from .scrapers import _find_image_ext from .utils import _replace_md5 @@ -188,7 +190,7 @@ r'meth(?:od)?|' r'attr(?:ibute)?|' r'obj(?:ect)?|' - r'class):`(\S*)`' + r'class):`~?(\S*)`' ) @@ -240,6 +242,7 @@ .. only:: html .. figure:: /{thumbnail} + :alt: {title} :ref:`sphx_glr_{ref_name}` @@ -255,16 +258,16 @@ """ -def _thumbnail_div(target_dir, src_dir, fname, snippet, is_backref=False, - check=True): +def _thumbnail_div(target_dir, src_dir, fname, snippet, title, + is_backref=False, check=True): """Generate RST to place a thumbnail in a gallery.""" thumb, _ = _find_image_ext( os.path.join(target_dir, 'images', 'thumb', 'sphx_glr_%s_thumb.png' % fname[:-3])) if check and not os.path.isfile(thumb): # This means we have done something wrong in creating our thumbnail! - raise RuntimeError('Could not find internal sphinx-gallery thumbnail ' - 'file:\n%s' % (thumb,)) + raise ExtensionError('Could not find internal sphinx-gallery thumbnail' + ' file:\n%s' % (thumb,)) thumb = os.path.relpath(thumb, src_dir) full_dir = os.path.relpath(target_dir, src_dir) @@ -275,11 +278,11 @@ template = BACKREF_THUMBNAIL_TEMPLATE if is_backref else THUMBNAIL_TEMPLATE return template.format(snippet=escape(snippet), - thumbnail=thumb, ref_name=ref_name) + thumbnail=thumb, title=title, ref_name=ref_name) def _write_backreferences(backrefs, seen_backrefs, gallery_conf, - target_dir, fname, snippet): + target_dir, fname, snippet, title): """Write backreference file including a thumbnail list of examples.""" if gallery_conf['backreferences_dir'] is None: return @@ -292,11 +295,14 @@ with codecs.open(include_path, 'a' if seen else 'w', encoding='utf-8') as ex_file: if not seen: + # Be aware that if the number of lines of this heading changes, + # the minigallery directive should be modified accordingly heading = 'Examples using ``%s``' % backref ex_file.write('\n\n' + heading + '\n') ex_file.write('^' * len(heading) + '\n') ex_file.write(_thumbnail_div(target_dir, gallery_conf['src_dir'], - fname, snippet, is_backref=True)) + fname, snippet, title, + is_backref=True)) seen_backrefs.add(backref) diff -Nru sphinx-gallery-0.6.2/sphinx_gallery/binder.py sphinx-gallery-0.7.0/sphinx_gallery/binder.py --- sphinx-gallery-0.6.2/sphinx_gallery/binder.py 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/sphinx_gallery/binder.py 2020-05-21 17:28:31.000000000 +0000 @@ -18,6 +18,8 @@ import shutil import os +from sphinx.errors import ConfigError + from .utils import replace_py_ipynb from . import sphinx_compatibility @@ -134,9 +136,10 @@ path_reqs = binder_conf.get('dependencies') for path in path_reqs: if not os.path.exists(os.path.join(app.srcdir, path)): - raise ValueError(("Couldn't find the Binder requirements file: {}," - " did you specify the path correctly?" - .format(path))) + raise ConfigError( + "Couldn't find the Binder requirements file: {}, " + "did you specify the path correctly?" + .format(path)) binder_folder = os.path.join(app.outdir, 'binder') if not os.path.isdir(binder_folder): @@ -196,7 +199,7 @@ # Grab the configuration and return None if it's not configured binder_conf = {} if binder_conf is None else binder_conf if not isinstance(binder_conf, dict): - raise ValueError('`binder_conf` must be a dictionary or None.') + raise ConfigError('`binder_conf` must be a dictionary or None.') if len(binder_conf) == 0: return binder_conf @@ -209,19 +212,19 @@ missing_values.append(val) if len(missing_values) > 0: - raise ValueError('binder_conf is missing values for: {}'.format( + raise ConfigError('binder_conf is missing values for: {}'.format( missing_values)) for key in binder_conf.keys(): if key not in (req_values + optional_values): - raise ValueError("Unknown Binder config key: {}".format(key)) + raise ConfigError("Unknown Binder config key: {}".format(key)) # Ensure we have http in the URL if not any(binder_conf['binderhub_url'].startswith(ii) for ii in ['http://', 'https://']): - raise ValueError('did not supply a valid url, ' - 'gave binderhub_url: {}' - .format(binder_conf['binderhub_url'])) + raise ConfigError('did not supply a valid url, ' + 'gave binderhub_url: {}' + .format(binder_conf['binderhub_url'])) # Ensure we have at least one dependency file # Need at least one of these three files @@ -231,14 +234,14 @@ path_reqs = [path_reqs] binder_conf['dependencies'] = path_reqs elif not isinstance(path_reqs, (list, tuple)): - raise ValueError("`dependencies` value should be a list of strings. " - "Got type {}.".format(type(path_reqs))) + raise ConfigError("`dependencies` value should be a list of strings. " + "Got type {}.".format(type(path_reqs))) binder_conf['notebooks_dir'] = binder_conf.get('notebooks_dir', 'notebooks') path_reqs_filenames = [os.path.basename(ii) for ii in path_reqs] if not any(ii in path_reqs_filenames for ii in required_reqs_files): - raise ValueError( + raise ConfigError( 'Did not find one of `requirements.txt` or `environment.yml` ' 'in the "dependencies" section of the binder configuration ' 'for sphinx-gallery. A path to at least one of these files ' diff -Nru sphinx-gallery-0.6.2/sphinx_gallery/directives.py sphinx-gallery-0.7.0/sphinx_gallery/directives.py --- sphinx-gallery-0.6.2/sphinx_gallery/directives.py 1970-01-01 00:00:00.000000000 +0000 +++ sphinx-gallery-0.7.0/sphinx_gallery/directives.py 2020-05-21 17:28:31.000000000 +0000 @@ -0,0 +1,79 @@ +""" +Custom Sphinx directives +======================== +""" + +import os + +from docutils import statemachine +from docutils.parsers.rst import Directive, directives + + +class MiniGallery(Directive): + """ + Custom directive to insert a mini-gallery + + The required argument is one or more fully qualified names of objects, + separated by spaces. The mini-gallery will be the subset of gallery + examples that make use of that object (from that specific namespace). + + Options: + + * `add-heading` adds a heading to the mini-gallery. If an argument is + provided, it uses that text for the heading. Otherwise, it uses + default text. + * `heading-level` specifies the heading level of the heading as a single + character. If omitted, the default heading level is `'^'`. + """ + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + option_spec = {'add-heading': directives.unchanged, + 'heading-level': directives.single_char_or_unicode} + + def run(self): + # Respect the same disabling options as the `raw` directive + if (not self.state.document.settings.raw_enabled + or not self.state.document.settings.file_insertion_enabled): + raise self.warning('"%s" directive disabled.' % self.name) + + # Retrieve the backreferences directory + config = self.state.document.settings.env.config + backreferences_dir = config.sphinx_gallery_conf['backreferences_dir'] + + # Parse the argument into the individual objects + obj_list = self.arguments[0].split() + + lines = [] + + # Add a heading if requested + if 'add-heading' in self.options: + heading = self.options['add-heading'] + if heading == "": + if len(obj_list) == 1: + heading = 'Examples using ``{}``'.format(obj_list[0]) + else: + heading = 'Examples using one of multiple objects' + lines.append(heading) + heading_level = self.options.get('heading-level', '^') + lines.append(heading_level * len(heading)) + + # Insert the backreferences file(s) using the `include` directive + for obj in obj_list: + path = os.path.join('/', # Sphinx treats this as the source dir + backreferences_dir, + '{}.examples'.format(obj)) + + # Always remove the heading (first 5 lines) from the file + lines.append('.. include:: {}\n :start-line: 5'.format(path)) + + # Insert the end for the gallery using the `raw` directive + lines.append('.. raw:: html\n\n
') + + # Parse the assembly of `include` and `raw` directives + text = '\n'.join(lines) + include_lines = statemachine.string2lines(text, + convert_whitespace=True) + self.state_machine.insert_input(include_lines, path) + + return [] diff -Nru sphinx-gallery-0.6.2/sphinx_gallery/docs_resolv.py sphinx-gallery-0.7.0/sphinx_gallery/docs_resolv.py --- sphinx-gallery-0.6.2/sphinx_gallery/docs_resolv.py 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/sphinx_gallery/docs_resolv.py 2020-05-21 17:28:31.000000000 +0000 @@ -19,6 +19,7 @@ import urllib.parse as urllib_parse from urllib.error import HTTPError, URLError +from sphinx.errors import ExtensionError from sphinx.search import js_index from . import sphinx_compatibility @@ -39,7 +40,7 @@ if encoding == 'gzip': data = gzip.GzipFile(fileobj=BytesIO(data)).read() elif encoding != 'plain': - raise RuntimeError('unknown encoding %r' % (encoding,)) + raise ExtensionError('unknown encoding %r' % (encoding,)) data = data.decode('utf-8') else: with codecs.open(url, mode='r', encoding='utf-8') as fid: @@ -83,13 +84,16 @@ pos = index.find('var DOCUMENTATION_OPTIONS') if pos < 0: - raise ValueError('Documentation options could not be found in index.') + raise ExtensionError( + 'Documentation options could not be found in index.') pos = index.find('{', pos) if pos < 0: - raise ValueError('Documentation options could not be found in index.') + raise ExtensionError( + 'Documentation options could not be found in index.') endpos = index.find('};', pos) if endpos < 0: - raise ValueError('Documentation options could not be found in index.') + raise ExtensionError( + 'Documentation options could not be found in index.') block = index[pos + 1:endpos].strip() docopts = {} for line in block.splitlines(): @@ -140,8 +144,9 @@ if doc_url.startswith(('http://', 'https://')): if relative: - raise ValueError('Relative links are only supported for local ' - 'URLs (doc_url cannot be absolute)') + raise ExtensionError( + 'Relative links are only supported for local ' + 'URLs (doc_url cannot be absolute)') index_url = doc_url + '/' searchindex_url = doc_url + '/searchindex.js' docopts_url = doc_url + '_static/documentation_options.js' @@ -155,8 +160,9 @@ if (os.name.lower() == 'nt' and not doc_url.startswith(('http://', 'https://'))): if not relative: - raise ValueError('You have to use relative=True for the local' - ' package on a Windows system.') + raise ExtensionError( + 'You have to use relative=True for the local' + ' package on a Windows system.') self._is_windows = True else: self._is_windows = False diff -Nru sphinx-gallery-0.6.2/sphinx_gallery/gen_gallery.py sphinx-gallery-0.7.0/sphinx_gallery/gen_gallery.py --- sphinx-gallery-0.6.2/sphinx_gallery/gen_gallery.py 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/sphinx_gallery/gen_gallery.py 2020-05-21 17:28:31.000000000 +0000 @@ -20,9 +20,10 @@ import pathlib from xml.sax.saxutils import quoteattr, escape +from sphinx.errors import ConfigError, ExtensionError from sphinx.util.console import red from . import sphinx_compatibility, glr_path_static, __version__ as _sg_version -from .utils import _replace_md5 +from .utils import _replace_md5, _has_optipng from .backreferences import _finalize_backreferences from .gen_rst import (generate_dir_rst, SPHX_GLR_SIG, _get_memory_base, _get_readme) @@ -31,6 +32,7 @@ from .downloads import generate_zipfiles from .sorting import NumberOfCodeLinesSortKey from .binder import copy_binder_files +from .directives import MiniGallery _KNOWN_CSS = ('gallery', 'gallery-binder', 'gallery-dataframe') @@ -62,6 +64,7 @@ 'min_reported_time': 0, 'binder': {}, 'image_scrapers': ('matplotlib',), + 'compress_images': (), 'reset_modules': ('matplotlib', 'seaborn'), 'first_notebook_cell': '%matplotlib inline', 'last_notebook_cell': None, @@ -72,6 +75,7 @@ 'inspect_global_variables': True, 'ignore_repr_types': r'', 'css': _KNOWN_CSS, + 'matplotlib_animations': False, } logger = sphinx_compatibility.getLogger('sphinx-gallery') @@ -128,14 +132,33 @@ type=DeprecationWarning) # deal with show_memory + gallery_conf['memory_base'] = 0. if gallery_conf['show_memory']: - try: - from memory_profiler import memory_usage # noqa, analysis:ignore - except ImportError: - logger.warning("Please install 'memory_profiler' to enable peak " - "memory measurements.") - gallery_conf['show_memory'] = False - gallery_conf['memory_base'] = _get_memory_base(gallery_conf) + if not callable(gallery_conf['show_memory']): # True-like + try: + from memory_profiler import memory_usage # noqa + except ImportError: + logger.warning("Please install 'memory_profiler' to enable " + "peak memory measurements.") + gallery_conf['show_memory'] = False + else: + def call_memory(func): + mem, out = memory_usage(func, max_usage=True, retval=True, + multiprocess=True) + try: + mem = mem[0] # old MP always returned a list + except TypeError: # 'float' object is not subscriptable + pass + return mem, out + gallery_conf['call_memory'] = call_memory + gallery_conf['memory_base'] = _get_memory_base(gallery_conf) + else: + gallery_conf['call_memory'] = gallery_conf['show_memory'] + if not gallery_conf['show_memory']: # can be set to False above + def call_memory(func): + return 0., func() + gallery_conf['call_memory'] = call_memory + assert callable(gallery_conf['call_memory']) # deal with scrapers scrapers = gallery_conf['image_scrapers'] @@ -153,11 +176,11 @@ scraper = getattr(scraper, '_get_sg_image_scraper') scraper = scraper() except Exception as exp: - raise ValueError('Unknown image scraper %r, got:\n%s' - % (orig_scraper, exp)) + raise ConfigError('Unknown image scraper %r, got:\n%s' + % (orig_scraper, exp)) scrapers[si] = scraper if not callable(scraper): - raise ValueError('Scraper %r was not callable' % (scraper,)) + raise ConfigError('Scraper %r was not callable' % (scraper,)) gallery_conf['image_scrapers'] = tuple(scrapers) del scrapers # Here we try to set up matplotlib but don't raise an error, @@ -174,6 +197,33 @@ except (ImportError, ValueError): pass + # compress_images + compress_images = gallery_conf['compress_images'] + if isinstance(compress_images, str): + compress_images = [compress_images] + elif not isinstance(compress_images, (tuple, list)): + raise ConfigError('compress_images must be a tuple, list, or str, ' + 'got %s' % (type(compress_images),)) + compress_images = list(compress_images) + allowed_values = ('images', 'thumbnails') + pops = list() + for ki, kind in enumerate(compress_images): + if kind not in allowed_values: + if kind.startswith('-'): + pops.append(ki) + continue + raise ConfigError('All entries in compress_images must be one of ' + '%s or a command-line switch starting with "-", ' + 'got %r' % (allowed_values, kind)) + compress_images_args = [compress_images.pop(p) for p in pops[::-1]] + if len(compress_images) and not _has_optipng(): + logger.warning( + 'optipng binaries not found, PNG %s will not be optimized' + % (' and '.join(compress_images),)) + compress_images = () + gallery_conf['compress_images'] = compress_images + gallery_conf['compress_images_args'] = compress_images_args + # deal with resetters resetters = gallery_conf['reset_modules'] if not isinstance(resetters, (tuple, list)): @@ -182,12 +232,12 @@ for ri, resetter in enumerate(resetters): if isinstance(resetter, str): if resetter not in _reset_dict: - raise ValueError('Unknown module resetter named %r' - % (resetter,)) + raise ConfigError('Unknown module resetter named %r' + % (resetter,)) resetters[ri] = _reset_dict[resetter] elif not callable(resetter): - raise ValueError('Module resetter %r was not callable' - % (resetter,)) + raise ConfigError('Module resetter %r was not callable' + % (resetter,)) gallery_conf['reset_modules'] = tuple(resetters) lang = lang if lang in ('python', 'python3', 'default') else 'python' @@ -197,13 +247,13 @@ # Ensure the first cell text is a string if we have it first_cell = gallery_conf.get("first_notebook_cell") if (not isinstance(first_cell, str)) and (first_cell is not None): - raise ValueError("The 'first_notebook_cell' parameter must be type " - "str or None, found type %s" % type(first_cell)) + raise ConfigError("The 'first_notebook_cell' parameter must be type " + "str or None, found type %s" % type(first_cell)) # Ensure the last cell text is a string if we have it last_cell = gallery_conf.get("last_notebook_cell") if (not isinstance(last_cell, str)) and (last_cell is not None): - raise ValueError("The 'last_notebook_cell' parameter must be type str " - "or None, found type %s" % type(last_cell)) + raise ConfigError("The 'last_notebook_cell' parameter must be type str" + " or None, found type %s" % type(last_cell)) # Make it easy to know which builder we're in gallery_conf['builder_name'] = builder_name gallery_conf['titles'] = {} @@ -211,21 +261,21 @@ backref = gallery_conf['backreferences_dir'] if (not isinstance(backref, (str, pathlib.Path))) and \ (backref is not None): - raise ValueError("The 'backreferences_dir' parameter must be of type " - "str, pathlib.Path or None, " - "found type %s" % type(backref)) + raise ConfigError("The 'backreferences_dir' parameter must be of type " + "str, pathlib.Path or None, " + "found type %s" % type(backref)) # if 'backreferences_dir' is pathlib.Path, make str for Python <=3.5 # compatibility if isinstance(backref, pathlib.Path): gallery_conf['backreferences_dir'] = str(backref) if not isinstance(gallery_conf['css'], (list, tuple)): - raise TypeError('gallery_conf["css"] must be list or tuple, got %r' - % (gallery_conf['css'],)) + raise ConfigError('gallery_conf["css"] must be list or tuple, got %r' + % (gallery_conf['css'],)) for css in gallery_conf['css']: if css not in _KNOWN_CSS: - raise ValueError('Unknown css %r, must be one of %r' - % (css, _KNOWN_CSS)) + raise ConfigError('Unknown css %r, must be one of %r' + % (css, _KNOWN_CSS)) if gallery_conf['app'] is not None: # can be None in testing gallery_conf['app'].add_css_file(css + '.css') @@ -401,7 +451,8 @@ t = '%0.2f sec' % (cost[0][0],) m = '{0:.1f} MB'.format(cost[0][1]) lines.append([name, t, m]) - lens = [max(x) for x in zip(*[[len(l) for l in ll] for ll in lines])] + lens = [max(x) for x in zip(*[[len(item) for item in cost] + for cost in lines])] return lines, lens @@ -419,7 +470,7 @@ .format(_sec_to_readable(total_time), target_dir_clean)) lines, lens = _format_for_writing(costs, target_dir_clean) del costs - hline = ''.join(('+' + '-' * (l + 2)) for l in lens) + '+\n' + hline = ''.join(('+' + '-' * (length + 2)) for length in lens) + '+\n' fid.write(hline) format_str = ''.join('| {%s} ' % (ii,) for ii in range(len(lines[0]))) + '|\n' @@ -586,9 +637,10 @@ color='brown') if fail_msgs: - raise ValueError("Here is a summary of the problems encountered when " - "running the examples\n\n" + "\n".join(fail_msgs) + - "\n" + "-" * 79) + raise ExtensionError( + "Here is a summary of the problems encountered " + "when running the examples\n\n" + "\n".join(fail_msgs) + + "\n" + "-" * 79) def collect_gallery_files(examples_dirs, gallery_conf): @@ -652,6 +704,9 @@ if 'sphinx.ext.autodoc' in app.extensions: app.connect('autodoc-process-docstring', touch_empty_backreferences) + # Add the custom directive + app.add_directive('minigallery', MiniGallery) + app.connect('builder-inited', generate_gallery_rst) app.connect('build-finished', copy_binder_files) app.connect('build-finished', summarize_failing_examples) diff -Nru sphinx-gallery-0.6.2/sphinx_gallery/gen_rst.py sphinx-gallery-0.7.0/sphinx_gallery/gen_rst.py --- sphinx-gallery-0.6.2/sphinx_gallery/gen_rst.py 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/sphinx_gallery/gen_rst.py 2020-05-21 17:28:31.000000000 +0000 @@ -33,9 +33,12 @@ import traceback import codeop +from sphinx.errors import ExtensionError + from .scrapers import (save_figures, ImagePathIterator, clean_modules, _find_image_ext) -from .utils import replace_py_ipynb, scale_image, get_md5sum, _replace_md5 +from .utils import (replace_py_ipynb, scale_image, get_md5sum, _replace_md5, + optipng) from . import glr_path_static from . import sphinx_compatibility from .backreferences import (_write_backreferences, _thumbnail_div, @@ -53,18 +56,30 @@ ############################################################################### -class LoggingTee(object): - """A tee object to redirect streams to the logger""" +class _LoggingTee(object): + """A tee object to redirect streams to the logger.""" - def __init__(self, output_file, logger, src_filename): - self.output_file = output_file + def __init__(self, src_filename): self.logger = logger self.src_filename = src_filename - self.first_write = True self.logger_buffer = '' + self.set_std_and_reset_position() + + def set_std_and_reset_position(self): + if not isinstance(sys.stdout, _LoggingTee): + self.origs = (sys.stdout, sys.stderr) + sys.stdout = sys.stderr = self + self.first_write = True + self.output = StringIO() + return self + + def restore_std(self): + sys.stdout.flush() + sys.stderr.flush() + sys.stdout, sys.stderr = self.origs def write(self, data): - self.output_file.write(data) + self.output.write(data) if self.first_write: self.logger.verbose('Output from %s', self.src_filename, @@ -85,14 +100,21 @@ self.logger.verbose('%s', line) def flush(self): - self.output_file.flush() + self.output.flush() if self.logger_buffer: self.logger.verbose('%s', self.logger_buffer) self.logger_buffer = '' # When called from a local terminal seaborn needs it in Python3 def isatty(self): - return self.output_file.isatty() + return self.output.isatty() + + # When called in gen_rst, conveniently use context managing + def __enter__(self): + return self + + def __exit__(self, type_, value, tb): + self.restore_std() ############################################################################### @@ -192,7 +214,7 @@ paragraphs = [p for p in paragraphs if not p.startswith('.. ') and len(p) > 0] if len(paragraphs) == 0: - raise ValueError( + raise ExtensionError( "Example docstring should have a header for the example title. " "Please check the example file:\n {}\n".format(filename)) # Title is the first paragraph with any ReSTructuredText title chars @@ -204,7 +226,7 @@ re.MULTILINE) if match is None: - raise ValueError( + raise ExtensionError( 'Could not find a title in first paragraph:\n{}'.format( title_paragraph)) title = match.group(0).strip() @@ -261,7 +283,7 @@ image_path = os.path.join(gallery_conf['src_dir'], thumbnail_path) else: if not isinstance(thumbnail_number, int): - raise TypeError( + raise ExtensionError( 'sphinx_gallery_thumbnail_number setting is not a number, ' 'got %r' % (thumbnail_number,)) image_path = image_path_template.format(thumbnail_number) @@ -282,10 +304,12 @@ img = gallery_conf.get("default_thumb_file", img) else: return - if ext == 'svg': + if ext in ('svg', 'gif'): copyfile(img, thumb_file) else: scale_image(img, thumb_file, *gallery_conf["thumbnail_size"]) + if 'thumbnails' in gallery_conf['compress_images']: + optipng(thumb_file, gallery_conf['compress_images_args']) def _get_readme(dir_, gallery_conf, raise_error=True): @@ -296,7 +320,7 @@ if os.path.isfile(fpth): return fpth if raise_error: - raise FileNotFoundError( + raise ExtensionError( "Example directory {0} does not have a README file with one " "of the expected file extensions {1}. Please write one to " "introduce your gallery.".format(dir_, extensions)) @@ -336,12 +360,12 @@ 'generating gallery for %s... ' % build_target_dir, length=len(sorted_listdir)) for fname in iterator: - intro, cost = generate_file_rst( + intro, title, cost = generate_file_rst( fname, target_dir, src_dir, gallery_conf, seen_backrefs) src_file = os.path.normpath(os.path.join(src_dir, fname)) costs.append((cost, src_file)) this_entry = _thumbnail_div(target_dir, gallery_conf['src_dir'], - fname, intro) + """ + fname, intro, title) + """ .. toctree:: :hidden: @@ -445,50 +469,32 @@ sys.modules['__main__'] = old_main -def _memory_usage(func, gallery_conf): - """Get memory usage of a function call.""" - if gallery_conf['show_memory']: - from memory_profiler import memory_usage - assert callable(func) - mem, out = memory_usage(func, max_usage=True, retval=True, - multiprocess=True) - try: - mem = mem[0] # old MP always returned a list - except TypeError: # 'float' object is not subscriptable - pass - else: - out = func() - mem = 0 - return out, mem - - def _get_memory_base(gallery_conf): """Get the base amount of memory used by running a Python process.""" - if not gallery_conf['show_memory'] or not gallery_conf['plot_gallery']: - memory_base = 0 - else: - # There might be a cleaner way to do this at some point - from memory_profiler import memory_usage - if sys.platform in ('win32', 'darwin'): - sleep, timeout = (1, 2) - else: - sleep, timeout = (0.5, 1) - proc = subprocess.Popen( - [sys.executable, '-c', - 'import time, sys; time.sleep(%s); sys.exit(0)' % sleep], - close_fds=True) - memories = memory_usage(proc, interval=1e-3, timeout=timeout) - kwargs = dict(timeout=timeout) if sys.version_info >= (3, 5) else {} - proc.communicate(**kwargs) - # On OSX sometimes the last entry can be None - memories = [mem for mem in memories if mem is not None] + [0.] - memory_base = max(memories) + if not gallery_conf['plot_gallery']: + return 0. + # There might be a cleaner way to do this at some point + from memory_profiler import memory_usage + if sys.platform in ('win32', 'darwin'): + sleep, timeout = (1, 2) + else: + sleep, timeout = (0.5, 1) + proc = subprocess.Popen( + [sys.executable, '-c', + 'import time, sys; time.sleep(%s); sys.exit(0)' % sleep], + close_fds=True) + memories = memory_usage(proc, interval=1e-3, timeout=timeout) + kwargs = dict(timeout=timeout) if sys.version_info >= (3, 5) else {} + proc.communicate(**kwargs) + # On OSX sometimes the last entry can be None + memories = [mem for mem in memories if mem is not None] + [0.] + memory_base = max(memories) return memory_base def execute_code_block(compiler, block, example_globals, script_vars, gallery_conf): - """Executes the code block of the example file""" + """Execute the code block of the example file.""" if example_globals is None: # testing shortcut example_globals = script_vars['fake_main'].__dict__ blabel, bcontent, lineno = block @@ -498,18 +504,18 @@ cwd = os.getcwd() # Redirect output to stdout and - orig_stdout, orig_stderr = sys.stdout, sys.stderr + src_file = script_vars['src_file'] + logging_tee = _check_reset_logging_tee(src_file) + assert isinstance(logging_tee, _LoggingTee) # First cd in the original example dir, so that any file # created by the example get created in this directory - captured_std = StringIO() os.chdir(os.path.dirname(src_file)) sys_path = copy.deepcopy(sys.path) sys.path.append(os.getcwd()) - sys.stdout = sys.stderr = LoggingTee(captured_std, logger, src_file) try: dont_inherit = 1 @@ -527,35 +533,30 @@ is_last_expr = True last_val = code_ast.body.pop().value # exec body minus last expression - _, mem_body = _memory_usage( + mem_body, _ = gallery_conf['call_memory']( _exec_once( compiler(code_ast, src_file, 'exec'), - script_vars['fake_main']), - gallery_conf) + script_vars['fake_main'])) # exec last expression, made into assignment body = [ast.Assign( targets=[ast.Name(id='___', ctx=ast.Store())], value=last_val)] last_val_ast = ast_Module(body=body) ast.fix_missing_locations(last_val_ast) - _, mem_last = _memory_usage( + mem_last, _ = gallery_conf['call_memory']( _exec_once( compiler(last_val_ast, src_file, 'exec'), - script_vars['fake_main']), - gallery_conf) + script_vars['fake_main'])) # capture the assigned variable ___ = example_globals['___'] mem_max = max(mem_body, mem_last) else: - _, mem_max = _memory_usage( + mem_max, _ = gallery_conf['call_memory']( _exec_once( compiler(code_ast, src_file, 'exec'), - script_vars['fake_main']), - gallery_conf) + script_vars['fake_main'])) script_vars['memory_delta'].append(mem_max) except Exception: - sys.stdout.flush() - sys.stderr.flush() - sys.stdout, sys.stderr = orig_stdout, orig_stderr + logging_tee.restore_std() except_rst = handle_exception(sys.exc_info(), src_file, script_vars, gallery_conf) code_output = u"\n{0}\n\n\n\n".format(except_rst) @@ -563,9 +564,7 @@ # figures are closed save_figures(block, script_vars, gallery_conf) else: - sys.stdout.flush() - sys.stderr.flush() - sys.stdout, orig_stderr = orig_stdout, orig_stderr + logging_tee.restore_std() sys.path = sys_path os.chdir(cwd) @@ -592,7 +591,7 @@ else: if isinstance(last_repr, str): break - captured_std = captured_std.getvalue().expandtabs() + captured_std = logging_tee.output.getvalue().expandtabs() # normal string output if repr_meth in ['__repr__', '__str__'] and last_repr: captured_std = u"{0}\n{1}".format(captured_std, last_repr) @@ -612,11 +611,22 @@ finally: os.chdir(cwd) sys.path = sys_path - sys.stdout, sys.stderr = orig_stdout, orig_stderr + logging_tee.restore_std() return code_output +def _check_reset_logging_tee(src_file): + # Helper to deal with our tests not necessarily calling execute_script + # but rather execute_code_block directly + if isinstance(sys.stdout, _LoggingTee): + logging_tee = sys.stdout + else: + logging_tee = _LoggingTee(src_file) + logging_tee.set_std_and_reset_position() + return logging_tee + + def executable_script(src_file, gallery_conf): """Validate if script has to be run according to gallery configuration @@ -690,7 +700,7 @@ sys.argv[0] = script_vars['src_file'] sys.argv[1:] = [] gc.collect() - _, memory_start = _memory_usage(lambda: None, gallery_conf) + memory_start, _ = gallery_conf['call_memory'](lambda: None) else: memory_start = 0. @@ -699,10 +709,12 @@ # include at least one entry to avoid max() ever failing script_vars['memory_delta'] = [memory_start] script_vars['fake_main'] = fake_main - output_blocks = [execute_code_block(compiler, block, - example_globals, - script_vars, gallery_conf) - for block in script_blocks] + output_blocks = list() + with _LoggingTee(script_vars.get('src_file', '')) as logging_tee: + for block in script_blocks: + logging_tee.set_std_and_reset_position() + output_blocks.append(execute_code_block( + compiler, block, example_globals, script_vars, gallery_conf)) time_elapsed = time() - t_start sys.argv = argv_orig script_vars['memory_delta'] = max(script_vars['memory_delta']) @@ -756,7 +768,7 @@ if md5sum_is_current(target_file): if executable: gallery_conf['stale_examples'].append(target_file) - return intro, (0, 0) + return intro, title, (0, 0) image_dir = os.path.join(target_dir, 'images') if not os.path.exists(image_dir): @@ -818,9 +830,9 @@ if cobj['module'].startswith(gallery_conf['doc_module'])) # Write backreferences _write_backreferences(backrefs, seen_backrefs, gallery_conf, target_dir, - fname, intro) + fname, intro, title) - return intro, (time_elapsed, memory_used) + return intro, title, (time_elapsed, memory_used) def rst_blocks(script_blocks, output_blocks, file_conf, gallery_conf): diff -Nru sphinx-gallery-0.6.2/sphinx_gallery/__init__.py sphinx-gallery-0.7.0/sphinx_gallery/__init__.py --- sphinx-gallery-0.6.2/sphinx_gallery/__init__.py 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/sphinx_gallery/__init__.py 2020-05-21 17:28:31.000000000 +0000 @@ -6,7 +6,7 @@ import os # dev versions should have "dev" in them, stable should not. # doc/conf.py makes use of this to set the version drop-down. -__version__ = '0.6.2' +__version__ = '0.7.0' def glr_path_static(): diff -Nru sphinx-gallery-0.6.2/sphinx_gallery/py_source_parser.py sphinx-gallery-0.7.0/sphinx_gallery/py_source_parser.py --- sphinx-gallery-0.6.2/sphinx_gallery/py_source_parser.py 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/sphinx_gallery/py_source_parser.py 2020-05-21 17:28:31.000000000 +0000 @@ -17,6 +17,7 @@ import tokenize from textwrap import dedent +from sphinx.errors import ExtensionError from .sphinx_compatibility import getLogger logger = getLogger('sphinx-gallery') @@ -90,14 +91,16 @@ return SYNTAX_ERROR_DOCSTRING, content, 1, node if not isinstance(node, ast.Module): - raise TypeError("This function only supports modules. " - "You provided {0}".format(node.__class__.__name__)) + raise ExtensionError("This function only supports modules. " + "You provided {0}" + .format(node.__class__.__name__)) if not (node.body and isinstance(node.body[0], ast.Expr) and isinstance(node.body[0].value, ast.Str)): - raise ValueError(('Could not find docstring in file "{0}". ' - 'A docstring is required by sphinx-gallery ' - 'unless the file is ignored by "ignore_pattern"') - .format(filename)) + raise ExtensionError( + 'Could not find docstring in file "{0}". ' + 'A docstring is required by sphinx-gallery ' + 'unless the file is ignored by "ignore_pattern"' + .format(filename)) if LooseVersion(sys.version) >= LooseVersion('3.7'): docstring = ast.get_docstring(node) diff -Nru sphinx-gallery-0.6.2/sphinx_gallery/scrapers.py sphinx-gallery-0.7.0/sphinx_gallery/scrapers.py --- sphinx-gallery-0.6.2/sphinx_gallery/scrapers.py 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/sphinx_gallery/scrapers.py 2020-05-21 17:28:31.000000000 +0000 @@ -13,8 +13,11 @@ import os import sys +import re +from textwrap import indent -from .utils import scale_image +from sphinx.errors import ExtensionError +from .utils import scale_image, optipng __all__ = ['save_figures', 'figure_rst', 'ImagePathIterator', 'clean_modules', 'matplotlib_scraper', 'mayavi_scraper'] @@ -32,7 +35,7 @@ matplotlib_backend = matplotlib.get_backend().lower() if matplotlib_backend != 'agg': - raise ValueError( + raise ExtensionError( "Sphinx-Gallery relies on the matplotlib 'agg' backend to " "render figures and write them to files. You are " "currently using the {} backend. Sphinx-Gallery will " @@ -47,6 +50,34 @@ return matplotlib, plt +def _matplotlib_fig_titles(fig): + titles = [] + # get supertitle if exists + suptitle = getattr(fig, "_suptitle", None) + if suptitle is not None: + titles.append(suptitle.get_text()) + # get titles from all axes, for all locs + title_locs = ['left', 'center', 'right'] + for ax in fig.axes: + for loc in title_locs: + text = ax.get_title(loc=loc) + if text: + titles.append(text) + fig_titles = ', '.join(titles) + return fig_titles + + +_ANIMATION_RST = ''' +.. only:: builder_html + + .. container:: sphx-glr-animation + + .. raw:: html + + {0} +''' + + def matplotlib_scraper(block, block_vars, gallery_conf, **kwargs): """Scrape Matplotlib images. @@ -72,8 +103,16 @@ the images. This is often produced by :func:`figure_rst`. """ matplotlib, plt = _import_matplotlib() + from matplotlib.animation import FuncAnimation image_path_iterator = block_vars['image_path_iterator'] - image_paths = list() + image_rsts = [] + # Check for animations + anims = list() + if gallery_conf.get('matplotlib_animations', False): + for ani in block_vars['example_globals'].values(): + if isinstance(ani, FuncAnimation): + anims.append(ani) + # Then standard images for fig_num, image_path in zip(plt.get_fignums(), image_path_iterator): if 'format' in kwargs: image_path = '%s.%s' % (os.path.splitext(image_path)[0], @@ -81,6 +120,17 @@ # Set the fig_num figure as the current figure as we can't # save a figure that's not the current figure. fig = plt.figure(fig_num) + # Deal with animations + cont = False + for anim in anims: + if anim._fig is fig: + image_rsts.append(_anim_rst(anim, image_path, gallery_conf)) + cont = True + break + if cont: + continue + # get fig titles + fig_titles = _matplotlib_fig_titles(fig) to_rgba = matplotlib.colors.colorConverter.to_rgba # shallow copy should be fine here, just want to avoid changing # "kwargs" for subsequent figures processed by the loop @@ -92,9 +142,45 @@ attr not in kwargs: these_kwargs[attr] = fig_attr fig.savefig(image_path, **these_kwargs) - image_paths.append(image_path) + if 'images' in gallery_conf['compress_images']: + optipng(image_path, gallery_conf['compress_images_args']) + image_rsts.append( + figure_rst([image_path], gallery_conf['src_dir'], fig_titles)) plt.close('all') - return figure_rst(image_paths, gallery_conf['src_dir']) + rst = '' + if len(image_rsts) == 1: + rst = image_rsts[0] + elif len(image_rsts) > 1: + image_rsts = [re.sub(r':class: sphx-glr-single-img', + ':class: sphx-glr-multi-img', + image) for image in image_rsts] + image_rsts = [HLIST_IMAGE_MATPLOTLIB + indent(image, u' ' * 6) + for image in image_rsts] + rst = HLIST_HEADER + ''.join(image_rsts) + return rst + + +def _anim_rst(anim, image_path, gallery_conf): + from matplotlib.animation import ImageMagickWriter + # output the thumbnail as the image, as it will just be copied + # if it's the file thumbnail + fig = anim._fig + image_path = image_path.replace('.png', '.gif') + fig_size = fig.get_size_inches() + thumb_size = gallery_conf['thumbnail_size'] + use_dpi = round( + min(t_s / f_s for t_s, f_s in zip(thumb_size, fig_size))) + # FFmpeg is buggy for GIFs + if ImageMagickWriter.isAvailable(): + writer = 'imagemagick' + else: + writer = None + anim.save(image_path, writer=writer, dpi=use_dpi) + html = anim._repr_html_() + if html is None: # plt.rcParams['animation.html'] == 'none' + html = anim.to_jshtml() + html = indent(html, ' ') + return _ANIMATION_RST.format(html) def mayavi_scraper(block, block_vars, gallery_conf): @@ -123,6 +209,8 @@ mlab.savefig(image_path, figure=scene) # make sure the image is not too large scale_image(image_path, image_path, 850, 999) + if 'images' in gallery_conf['compress_images']: + optipng(image_path, gallery_conf['compress_images_args']) image_paths.append(image_path) mlab.close(all=True) return figure_rst(image_paths, gallery_conf['src_dir']) @@ -177,7 +265,7 @@ for ii in range(self._stop): yield self.next() else: - raise RuntimeError('Generated over %s images' % (self._stop,)) + raise ExtensionError('Generated over %s images' % (self._stop,)) def next(self): return self.__next__() @@ -190,7 +278,7 @@ # For now, these are what we support -_KNOWN_IMG_EXTS = ('png', 'svg', 'jpg') # XXX add gif next +_KNOWN_IMG_EXTS = ('png', 'svg', 'jpg', 'gif') def _find_image_ext(path): @@ -228,21 +316,22 @@ for scraper in gallery_conf['image_scrapers']: rst = scraper(block, block_vars, gallery_conf) if not isinstance(rst, str): - raise TypeError('rst from scraper %r was not a string, ' - 'got type %s:\n%r' - % (scraper, type(rst), rst)) + raise ExtensionError('rst from scraper %r was not a string, ' + 'got type %s:\n%r' + % (scraper, type(rst), rst)) n_new = len(image_path_iterator) - prev_count for ii in range(n_new): current_path, _ = _find_image_ext( image_path_iterator.paths[prev_count + ii]) if not os.path.isfile(current_path): - raise RuntimeError('Scraper %s did not produce expected image:' - '\n%s' % (scraper, current_path)) + raise ExtensionError( + 'Scraper %s did not produce expected image:' + '\n%s' % (scraper, current_path)) all_rst += rst return all_rst -def figure_rst(figure_list, sources_dir): +def figure_rst(figure_list, sources_dir, fig_titles=''): """Generate RST for a list of image filenames. Depending on whether we have one or more figures, we use a @@ -254,6 +343,9 @@ List of strings of the figures' absolute paths. sources_dir : str absolute path of Sphinx documentation sources + fig_titles : str + Titles of figures, empty string if no titles found. Currently + only supported for matplotlib figures, default = ''. Returns ------- @@ -264,18 +356,37 @@ figure_paths = [os.path.relpath(figure_path, sources_dir) .replace(os.sep, '/').lstrip('/') for figure_path in figure_list] + # Get alt text + alt = '' + if fig_titles: + alt = fig_titles + elif figure_list: + file_name = os.path.split(figure_list[0])[1] + # remove ext & 'sphx_glr_' from start & n#'s from end + file_name_noext = os.path.splitext(file_name)[0][9:-4] + # replace - & _ with \s + file_name_final = re.sub(r'[-,_]', ' ', file_name_noext) + alt = file_name_final + alt = _single_line_sanitize(alt) + images_rst = "" if len(figure_paths) == 1: figure_name = figure_paths[0] - images_rst = SINGLE_IMAGE % figure_name + images_rst = SINGLE_IMAGE % (figure_name, alt) elif len(figure_paths) > 1: images_rst = HLIST_HEADER for figure_name in figure_paths: - images_rst += HLIST_IMAGE_TEMPLATE % figure_name - + images_rst += HLIST_IMAGE_TEMPLATE % (figure_name, alt) return images_rst +def _single_line_sanitize(s): + """Remove problematic newlines.""" + # For example, when setting a :alt: for an image, it shouldn't have \n + # This is a function in case we end up finding other things to replace + return s.replace('\n', ' ') + + # The following strings are used when we have several pictures: we use # an html div tag that our CSS uses to turn the lists into horizontal # lists. @@ -284,15 +395,21 @@ """ +HLIST_IMAGE_MATPLOTLIB = """ + * +""" + HLIST_IMAGE_TEMPLATE = """ * .. image:: /%s - :class: sphx-glr-multi-img + :alt: %s + :class: sphx-glr-multi-img """ SINGLE_IMAGE = """ .. image:: /%s + :alt: %s :class: sphx-glr-single-img """ diff -Nru sphinx-gallery-0.6.2/sphinx_gallery/sorting.py sphinx-gallery-0.7.0/sphinx_gallery/sorting.py --- sphinx-gallery-0.6.2/sphinx_gallery/sorting.py 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/sphinx_gallery/sorting.py 2020-05-21 17:28:31.000000000 +0000 @@ -13,6 +13,8 @@ import os import types +from sphinx.errors import ConfigError + from .gen_rst import extract_intro_and_title from .py_source_parser import split_code_and_text_blocks @@ -35,9 +37,9 @@ def __init__(self, ordered_list): if not isinstance(ordered_list, (list, tuple, types.GeneratorType)): - raise ValueError("ExplicitOrder sorting key takes a list, " - "tuple or Generator, which hold" - "the paths of each gallery subfolder") + raise ConfigError("ExplicitOrder sorting key takes a list, " + "tuple or Generator, which hold" + "the paths of each gallery subfolder") self.ordered_list = list(os.path.normpath(path) for path in ordered_list) @@ -46,9 +48,9 @@ if item in self.ordered_list: return self.ordered_list.index(item) else: - raise ValueError('If you use an explicit folder ordering, you ' - 'must specify all folders. Explicit order not ' - 'found for {}'.format(item)) + raise ConfigError('If you use an explicit folder ordering, you ' + 'must specify all folders. Explicit order not ' + 'found for {}'.format(item)) def __repr__(self): return '<%s : %s>' % (self.__class__.__name__, self.ordered_list) diff -Nru sphinx-gallery-0.6.2/sphinx_gallery/_static/gallery.css sphinx-gallery-0.7.0/sphinx_gallery/_static/gallery.css --- sphinx-gallery-0.6.2/sphinx_gallery/_static/gallery.css 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/sphinx_gallery/_static/gallery.css 2020-05-21 17:28:31.000000000 +0000 @@ -175,6 +175,15 @@ height: auto; } +div.sphx-glr-animation { + margin: auto; + display: block; + max-width: 100%; +} +div.sphx-glr-animation .animation{ + display: block; +} + p.sphx-glr-signature a.reference.external { -moz-border-radius: 5px; -webkit-border-radius: 5px; diff -Nru sphinx-gallery-0.6.2/sphinx_gallery/tests/conftest.py sphinx-gallery-0.7.0/sphinx_gallery/tests/conftest.py --- sphinx-gallery-0.6.2/sphinx_gallery/tests/conftest.py 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/sphinx_gallery/tests/conftest.py 2020-05-21 17:28:31.000000000 +0000 @@ -14,6 +14,7 @@ import sphinx from sphinx.application import Sphinx +from sphinx.errors import ExtensionError from sphinx.util.docutils import docutils_namespace from sphinx_gallery import (docs_resolv, gen_gallery, gen_rst, utils, sphinx_compatibility, py_source_parser) @@ -156,7 +157,7 @@ def req_pil(): try: _get_image() - except RuntimeError: + except ExtensionError: pytest.skip('Test requires pillow') @@ -240,4 +241,5 @@ return SphinxAppWrapper( srcdir, srcdir, os.path.join(srcdir, "_build"), - os.path.join(srcdir, "_build", "toctree"), "html", warning=StringIO()) + os.path.join(srcdir, "_build", "toctree"), "html", warning=StringIO(), + status=StringIO()) diff -Nru sphinx-gallery-0.6.2/sphinx_gallery/tests/test_backreferences.py sphinx-gallery-0.7.0/sphinx_gallery/tests/test_backreferences.py --- sphinx-gallery-0.6.2/sphinx_gallery/tests/test_backreferences.py 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/sphinx_gallery/tests/test_backreferences.py 2020-05-21 17:28:31.000000000 +0000 @@ -7,6 +7,7 @@ from __future__ import division, absolute_import, print_function import pytest +from sphinx.errors import ExtensionError import sphinx_gallery.backreferences as sg from sphinx_gallery.py_source_parser import split_code_and_text_blocks from sphinx_gallery.gen_rst import _sanitize_rst @@ -20,6 +21,7 @@ .. only:: html .. figure:: /fake_dir/images/thumb/sphx_glr_test_file_thumb.png + :alt: test title :ref:`sphx_glr_fake_dir_test_file.py` @@ -44,12 +46,14 @@ ]) def test_thumbnail_div(content, tooltip, is_backref): """Test if the thumbnail div generates the correct string.""" - with pytest.raises(RuntimeError, match='internal sphinx-gallery thumb'): + with pytest.raises(ExtensionError, match='internal sphinx-gallery thumb'): html_div = sg._thumbnail_div('fake_dir', '', 'test_file.py', - '<"test">') + '<"test">', '<"title">') content = _sanitize_rst(content) + title = 'test title' html_div = sg._thumbnail_div('fake_dir', '', 'test_file.py', - content, is_backref=is_backref, check=False) + content, title, is_backref=is_backref, + check=False) if is_backref: extra = """ @@ -134,7 +138,7 @@ }], } - fname = tmpdir.join("indentify_names.py") + fname = tmpdir.join("identify_names.py") fname.write(code_str, 'wb') _, script_blocks = split_code_and_text_blocks(fname.strpath) @@ -147,13 +151,15 @@ Title ----- -This example uses :func:`k.l`. +This example uses :func:`k.l` and :meth:`~m.n`. ''' """ + code_str.split(b"'''")[-1] expected['k.l'] = [{u'module': u'k', u'module_short': u'k', u'name': u'l', 'is_class': False}] + expected['m.n'] = [{u'module': u'm', u'module_short': u'm', u'name': u'n', + 'is_class': False}] - fname = tmpdir.join("indentify_names.py") + fname = tmpdir.join("identify_names.py") fname.write(code_str, 'wb') _, script_blocks = split_code_and_text_blocks(fname.strpath) res = sg.identify_names(script_blocks) diff -Nru sphinx-gallery-0.6.2/sphinx_gallery/tests/test_binder.py sphinx-gallery-0.7.0/sphinx_gallery/tests/test_binder.py --- sphinx-gallery-0.6.2/sphinx_gallery/tests/test_binder.py 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/sphinx_gallery/tests/test_binder.py 2020-05-21 17:28:31.000000000 +0000 @@ -10,6 +10,7 @@ import pytest +from sphinx.errors import ConfigError from sphinx_gallery.binder import (gen_binder_url, check_binder_conf, _copy_binder_reqs) @@ -43,7 +44,7 @@ # URL must have http conf2 = deepcopy(conf1) conf2['binderhub_url'] = 'test1.com' - with pytest.raises(ValueError, match='did not supply a valid url'): + with pytest.raises(ConfigError, match='did not supply a valid url'): url = check_binder_conf(conf2) # Assert missing params @@ -52,7 +53,7 @@ continue conf3 = deepcopy(conf1) conf3.pop(key) - with pytest.raises(ValueError, match='binder_conf is missing values'): + with pytest.raises(ConfigError, match='binder_conf is missing values'): url = check_binder_conf(conf3) # Dependencies file @@ -60,14 +61,14 @@ for ifile in dependency_file_tests: conf3 = deepcopy(conf1) conf3['dependencies'] = ifile - with pytest.raises(ValueError, + with pytest.raises(ConfigError, match=r"Did not find one of `requirements.txt` " "or `environment.yml`"): url = check_binder_conf(conf3) conf6 = deepcopy(conf1) conf6['dependencies'] = {'test': 'test'} - with pytest.raises(ValueError, match='`dependencies` value should be a ' + with pytest.raises(ConfigError, match='`dependencies` value should be a ' 'list of strings'): url = check_binder_conf(conf6) @@ -80,7 +81,7 @@ def apptmp(): pass apptmp.srcdir = '/' - with pytest.raises(ValueError, match="Couldn't find the Binder " + with pytest.raises(ConfigError, match="Couldn't find the Binder " "requirements file"): url = _copy_binder_reqs(apptmp, conf7) @@ -93,7 +94,7 @@ # Assert extra unkonwn params conf7 = deepcopy(conf1) conf7['foo'] = 'blah' - with pytest.raises(ValueError, match='Unknown Binder config key'): + with pytest.raises(ConfigError, match='Unknown Binder config key'): url = check_binder_conf(conf7) # Assert using lab correctly changes URL diff -Nru sphinx-gallery-0.6.2/sphinx_gallery/tests/test_docs_resolv.py sphinx-gallery-0.7.0/sphinx_gallery/tests/test_docs_resolv.py --- sphinx-gallery-0.6.2/sphinx_gallery/tests/test_docs_resolv.py 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/sphinx_gallery/tests/test_docs_resolv.py 2020-05-21 17:28:31.000000000 +0000 @@ -11,6 +11,7 @@ import pytest +from sphinx.errors import ExtensionError import sphinx_gallery.docs_resolv as sg @@ -86,11 +87,11 @@ 'SOURCELINK_SUFFIX': '.txt' } - with pytest.raises(ValueError): + with pytest.raises(ExtensionError): sg.parse_sphinx_docopts('empty input') - with pytest.raises(ValueError): + with pytest.raises(ExtensionError): sg.parse_sphinx_docopts('DOCUMENTATION_OPTIONS = ') - with pytest.raises(ValueError): + with pytest.raises(ExtensionError): sg.parse_sphinx_docopts('DOCUMENTATION_OPTIONS = {') diff -Nru sphinx-gallery-0.6.2/sphinx_gallery/tests/test_full.py sphinx-gallery-0.7.0/sphinx_gallery/tests/test_full.py --- sphinx-gallery-0.6.2/sphinx_gallery/tests/test_full.py 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/sphinx_gallery/tests/test_full.py 2020-05-21 17:28:31.000000000 +0000 @@ -19,15 +19,16 @@ from numpy.testing import assert_allclose from sphinx.application import Sphinx +from sphinx.errors import ExtensionError from sphinx.util.docutils import docutils_namespace -from sphinx_gallery.utils import _get_image, scale_image +from sphinx_gallery.utils import _get_image, scale_image, _has_optipng import pytest -N_TOT = 6 +N_TOT = 9 N_FAILING = 1 N_GOOD = N_TOT - N_FAILING -N_RST = 14 + N_TOT +N_RST = 15 + N_TOT N_RST = '(%s|%s)' % (N_RST, N_RST - 1) # AppVeyor weirdness @@ -84,6 +85,18 @@ assert ('- %s: ' % fname) in status +def test_optipng(sphinx_app): + """Test that optipng is detected.""" + status = sphinx_app._status.getvalue() + w = sphinx_app._warning.getvalue() + substr = 'will not be optimized' + if _has_optipng(): + assert substr not in w + else: + assert substr in w + assert 'optipng version' not in status.lower() # catch the --version + + def test_junit(sphinx_app, tmpdir): out_dir = sphinx_app.outdir junit_file = op.join(out_dir, 'sphinx-gallery', 'junit-results.xml') @@ -115,7 +128,7 @@ buildername='html', status=StringIO()) # need to build within the context manager # for automodule and backrefs to work - with pytest.raises(ValueError, match='Here is a summary of the '): + with pytest.raises(ExtensionError, match='Here is a summary of the '): app.build(False, []) junit_file = op.join(new_out_dir, 'sphinx-gallery', 'junit-results.xml') assert op.isfile(junit_file) @@ -158,8 +171,9 @@ Image = _get_image() orig = np.asarray(Image.open(fname_thumb)) new = np.asarray(Image.open(fname_new)) - assert new.shape == orig.shape - corr = np.corrcoef(new.ravel(), orig.ravel())[0, 1] + assert new.shape[:2] == orig.shape[:2] + assert new.shape[2] in (3, 4) # optipng can strip the alpha channel + corr = np.corrcoef(new[..., :3].ravel(), orig[..., :3].ravel())[0, 1] assert corr > 0.99 @@ -170,23 +184,31 @@ with codecs.open(generated_examples_index, 'r', 'utf-8') as fid: html = fid.read() thumb_fnames = ['../_images/sphx_glr_plot_svg_thumb.svg', - '../_images/sphx_glr_plot_numpy_matplotlib_thumb.png'] + '../_images/sphx_glr_plot_numpy_matplotlib_thumb.png', + '../_images/sphx_glr_plot_animation_thumb.gif', + ] for thumb_fname in thumb_fnames: file_fname = op.join(generated_examples_dir, thumb_fname) - assert op.isfile(file_fname) + assert op.isfile(file_fname), file_fname want_html = 'src="%s"' % (thumb_fname,) assert want_html in html - for ex, ext in (('plot_svg', 'svg'), - ('plot_numpy_matplotlib', 'png'), - ): + # the original GIF does not get copied because it's not used in the + # RST/HTML, so can't add it to this check + for ex, ext, nums, extra in ( + ('plot_svg', 'svg', [1], None), + ('plot_numpy_matplotlib', 'png', [1], None), + ('plot_animation', 'png', [1, 3], 'function Animation')): html_fname = op.join(generated_examples_dir, '%s.html' % ex) with codecs.open(html_fname, 'r', 'utf-8') as fid: html = fid.read() - img_fname = '../_images/sphx_glr_%s_001.%s' % (ex, ext) - file_fname = op.join(generated_examples_dir, img_fname) - assert op.isfile(file_fname) - want_html = 'src="%s"' % (img_fname,) - assert want_html in html + for num in nums: + img_fname = '../_images/sphx_glr_%s_%03d.%s' % (ex, num, ext) + file_fname = op.join(generated_examples_dir, img_fname) + assert op.isfile(file_fname), file_fname + want_html = 'src="%s"' % (img_fname,) + assert want_html in html + if extra is not None: + assert extra in html def test_embed_links_and_styles(sphinx_app): @@ -231,8 +253,8 @@ # gh-587: np.random.RandomState links properly # NumPy has had this linked as numpy.random.RandomState and # numpy.random.mtrand.RandomState so we need regex... - assert re.search(r'\.html#numpy\.random\.(mtrand\.?)RandomState" title="numpy\.random\.(mtrand\.?)RandomState" class="sphx-glr-backref-module-numpy-random(-mtrand?) sphx-glr-backref-type-py-class">np', lines) is not None # noqa: E501 - assert re.search(r'\.html#numpy\.random\.(mtrand\.?)RandomState" title="numpy\.random\.(mtrand\.?)RandomState" class="sphx-glr-backref-module-numpy-random(-mtrand?) sphx-glr-backref-type-py-class sphx-glr-backref-instance">rng', lines) is not None # noqa: E501 + assert re.search(r'\.html#numpy\.random\.(mtrand\.?)?RandomState" title="numpy\.random\.(mtrand\.?)?RandomState" class="sphx-glr-backref-module-numpy-random(-mtrand?)? sphx-glr-backref-type-py-class">np', lines) is not None # noqa: E501 + assert re.search(r'\.html#numpy\.random\.(mtrand\.?)?RandomState" title="numpy\.random\.(mtrand\.?)?RandomState" class="sphx-glr-backref-module-numpy-random(-mtrand?)? sphx-glr-backref-type-py-class sphx-glr-backref-instance">rng', lines) is not None # noqa: E501 # gh-587: methods of classes in the module currently being documented # issue 617 (regex '-'s) # instance @@ -323,6 +345,16 @@ assert example_used_in in lines +def test_logging_std_nested(sphinx_app): + """Test that nested stdout/stderr uses within a given script work.""" + log_rst = op.join( + sphinx_app.srcdir, 'auto_examples', 'plot_log.rst') + with codecs.open(log_rst, 'r', 'utf-8') as fid: + lines = fid.read() + assert '.. code-block:: none\n\n is in the same cell' in lines + assert '.. code-block:: none\n\n is not in the same cell' in lines + + def _assert_mtimes(list_orig, list_new, different=(), ignore=()): assert ([op.basename(x) for x in list_orig] == [op.basename(x) for x in list_new]) @@ -344,7 +376,7 @@ lines = [line for line in status.split('\n') if 'removed' in line] want = '.*%s added, 0 changed, 0 removed.*' % (N_RST,) assert re.match(want, status, re.MULTILINE | re.DOTALL) is not None, lines - want = '.*targets for 2 source files that are out of date$.*' + want = '.*targets for 3 source files that are out of date$.*' lines = [line for line in status.split('\n') if 'out of date' in line] assert re.match(want, status, re.MULTILINE | re.DOTALL) is not None, lines lines = [line for line in status.split('\n') if 'on MD5' in line] @@ -550,3 +582,169 @@ # mtimes for .ipynb files _assert_mtimes(copied_ipy_0, copied_ipy_1, different=('plot_numpy_matplotlib.ipynb')) + + +def test_alt_text_image(sphinx_app): + """Test alt text for matplotlib images in html and rst""" + out_dir = sphinx_app.outdir + src_dir = sphinx_app.srcdir + # alt text is fig titles, rst + example_rst = op.join(src_dir, 'auto_examples', 'plot_matplotlib_alt.rst') + with codecs.open(example_rst, 'r', 'utf-8') as fid: + rst = fid.read() + # suptitle and axes titles + assert ':alt: This is a sup title, subplot 1, subplot 2' in rst + # multiple titles + assert ':alt: Left Title, Center Title, Right Title' in rst + + # no fig title - alt text is file name, rst + example_rst = op.join(src_dir, 'auto_examples', + 'plot_numpy_matplotlib.rst') + with codecs.open(example_rst, 'r', 'utf-8') as fid: + rst = fid.read() + assert ':alt: plot numpy matplotlib' in rst + # html + example_html = op.join(out_dir, 'auto_examples', + 'plot_numpy_matplotlib.html') + with codecs.open(example_html, 'r', 'utf-8') as fid: + html = fid.read() + assert 'alt="plot numpy matplotlib"' in html + + +def test_alt_text_thumbnail(sphinx_app): + """Test alt text for thumbnail in html and rst.""" + out_dir = sphinx_app.outdir + src_dir = sphinx_app.srcdir + # check gallery index thumbnail, html + generated_examples_index = op.join(out_dir, 'auto_examples', 'index.html') + with codecs.open(generated_examples_index, 'r', 'utf-8') as fid: + html = fid.read() + assert 'alt=""SVG":-`graphics_`"' in html + # check backreferences thumbnail, html + backref_html = op.join(out_dir, 'gen_modules', + 'sphinx_gallery.backreferences.html') + with codecs.open(backref_html, 'r', 'utf-8') as fid: + html = fid.read() + assert 'alt="Link to other packages"' in html + # check gallery index thumbnail, rst + generated_examples_index = op.join(src_dir, 'auto_examples', + 'index.rst') + with codecs.open(generated_examples_index, 'r', 'utf-8') as fid: + rst = fid.read() + assert ':alt: Trivial module to provide a value for plot_numpy_matplotlib.py' in rst # noqa: E501 + + +def test_backreference_labels(sphinx_app): + """Tests that backreference labels work.""" + src_dir = sphinx_app.srcdir + out_dir = sphinx_app.outdir + # Test backreference label + backref_rst = op.join(src_dir, 'gen_modules', + 'sphinx_gallery.backreferences.rst') + with codecs.open(backref_rst, 'r', 'utf-8') as fid: + rst = fid.read() + label = '.. _sphx_glr_backref_sphinx_gallery.backreferences.identify_names:' # noqa: E501 + assert label in rst + # Test html link + index_html = op.join(out_dir, 'index.html') + with codecs.open(index_html, 'r', 'utf-8') as fid: + html = fid.read() + link = 'href="gen_modules/sphinx_gallery.backreferences.html#sphx-glr-backref-sphinx-gallery-backreferences-identify-names">' # noqa: E501 + assert link in html + + +def test_minigallery_directive(sphinx_app): + """Tests the functionality of the minigallery directive.""" + out_dir = sphinx_app.outdir + minigallery_html = op.join(out_dir, 'minigallery.html') + with codecs.open(minigallery_html, 'r', 'utf-8') as fid: + lines = fid.readlines() + + # Regular expressions for matching + any_heading = re.compile(r'.+<\/h\1>') + explicitorder_example = re.compile(r'(?s)Examples using .+ExplicitOrder.+<\/h2>') + assert heading.search(text) is not None + + # Check for examples + assert explicitorder_example.search(text) is not None + assert filenamesortkey_example.search(text) is None + + # Test 1-D-C (first example, default heading, custom level) + if "Test 1-D-C" in lines[i]: + text = ''.join(lines[i:i+8]) + + heading = re.compile(r'

Examples using .+ExplicitOrder.+<\/h3>') + assert heading.search(text) is not None + + # Check for examples + assert explicitorder_example.search(text) is not None + assert filenamesortkey_example.search(text) is None + + # Test 1-C-D (first example, custom heading, default level) + if "Test 1-C-D" in lines[i]: + text = ''.join(lines[i:i+8]) + + heading = re.compile(r'

This is a custom heading.*<\/h2>') + assert heading.search(text) is not None + + # Check for examples + assert explicitorder_example.search(text) is not None + assert filenamesortkey_example.search(text) is None + + # Test 2-N (both examples, no heading) + if "Test 2-N" in lines[i]: + text = ''.join(lines[i:i+8]) + + # Confirm there isn't a heading + assert any_heading.search(text) is None + + # Check for examples + assert explicitorder_example.search(text) is not None + assert filenamesortkey_example.search(text) is not None + + # Test 2-D-D (both examples, default heading, default level) + if "Test 2-D-D" in lines[i]: + text = ''.join(lines[i:i+12]) + + heading = re.compile(r'

Examples using one of multiple objects' + r'.*<\/h2>') + assert heading.search(text) is not None + + # Check for examples + assert explicitorder_example.search(text) is not None + assert filenamesortkey_example.search(text) is not None + + # Test 2-C-C (both examples, custom heading, custom level) + if "Test 2-C-C" in lines[i]: + text = ''.join(lines[i:i+12]) + + heading = re.compile(r'

This is a different custom heading.*' + r'<\/h1>') + assert heading.search(text) is not None + + # Check for examples + assert explicitorder_example.search(text) is not None + assert filenamesortkey_example.search(text) is not None diff -Nru sphinx-gallery-0.6.2/sphinx_gallery/tests/test_gen_gallery.py sphinx-gallery-0.7.0/sphinx_gallery/tests/test_gen_gallery.py --- sphinx-gallery-0.6.2/sphinx_gallery/tests/test_gen_gallery.py 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/sphinx_gallery/tests/test_gen_gallery.py 2020-05-21 17:28:31.000000000 +0000 @@ -13,7 +13,7 @@ import pytest -from sphinx.errors import ExtensionError +from sphinx.errors import ConfigError, ExtensionError from sphinx_gallery.gen_gallery import (check_duplicate_filenames, check_spaces_in_filenames, collect_gallery_files, @@ -51,12 +51,14 @@ assert build_warn == '' +# This changed from passing the ValueError directly to +# raising "sphinx.errors.ConfigError" with "threw an exception" @pytest.mark.parametrize('err_class, err_match', [ - pytest.param(ValueError, 'Unknown module resetter', + pytest.param(ConfigError, 'Unknown module resetter', id='Resetter unknown', marks=pytest.mark.conf_file( content="sphinx_gallery_conf={'reset_modules': ('f',)}")), - pytest.param(ValueError, 'Module resetter .* was not callab', + pytest.param(ConfigError, 'Module resetter .* was not callab', id='Resetter not callable', marks=pytest.mark.conf_file( content="sphinx_gallery_conf={'reset_modules': (1.,),}")), @@ -67,10 +69,10 @@ @pytest.mark.parametrize('err_class, err_match', [ - pytest.param(ValueError, 'Unknown css', id='CSS str error', + pytest.param(ConfigError, 'Unknown css', id='CSS str error', marks=pytest.mark.conf_file( content="sphinx_gallery_conf={'css': ('foo',)}")), - pytest.param(TypeError, 'must be list or tuple', id="CSS type error", + pytest.param(ConfigError, 'must be list or tuple', id="CSS type error", marks=pytest.mark.conf_file( content="sphinx_gallery_conf={'css': 1.}")), ]) @@ -315,7 +317,7 @@ with codecs.open(os.path.join(example_dir, 'plot_3.py'), 'a', encoding='utf-8') as fid: fid.write('raise SyntaxError') - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ExtensionError) as excinfo: sphinx_app_wrapper.build_sphinx_app() assert "Unexpected failing examples" in str(excinfo.value) @@ -341,19 +343,30 @@ 'expected_failing_examples' :['src/plot_2.py'], }""") def test_examples_not_expected_to_pass(sphinx_app_wrapper): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ExtensionError) as excinfo: sphinx_app_wrapper.build_sphinx_app() assert "expected to fail, but not failing" in str(excinfo.value) @pytest.mark.conf_file(content=""" sphinx_gallery_conf = { + 'show_memory': lambda func: (0., func()), + 'gallery_dirs': 'ex', +}""") +def test_show_memory_callable(sphinx_app_wrapper): + sphinx_app = sphinx_app_wrapper.build_sphinx_app() + status = sphinx_app._status.getvalue() + assert "0.0 MB" in status + + +@pytest.mark.conf_file(content=""" +sphinx_gallery_conf = { 'first_notebook_cell': 2, }""") def test_first_notebook_cell_config(sphinx_app_wrapper): from sphinx_gallery.gen_gallery import parse_config # First cell must be str - with pytest.raises(ValueError): + with pytest.raises(ConfigError): parse_config(sphinx_app_wrapper.create_sphinx_app()) @@ -364,7 +377,7 @@ def test_last_notebook_cell_config(sphinx_app_wrapper): from sphinx_gallery.gen_gallery import parse_config # First cell must be str - with pytest.raises(ValueError): + with pytest.raises(ConfigError): parse_config(sphinx_app_wrapper.create_sphinx_app()) @@ -375,7 +388,7 @@ def test_backreferences_dir_config(sphinx_app_wrapper): """Tests 'backreferences_dir' type checking.""" from sphinx_gallery.gen_gallery import parse_config - with pytest.raises(ValueError, + with pytest.raises(ConfigError, match="The 'backreferences_dir' parameter must be of"): parse_config(sphinx_app_wrapper.create_sphinx_app()) diff -Nru sphinx-gallery-0.6.2/sphinx_gallery/tests/test_gen_rst.py sphinx-gallery-0.7.0/sphinx_gallery/tests/test_gen_rst.py --- sphinx-gallery-0.6.2/sphinx_gallery/tests/test_gen_rst.py 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/sphinx_gallery/tests/test_gen_rst.py 2020-05-21 17:28:31.000000000 +0000 @@ -9,7 +9,6 @@ import ast import codecs import importlib -import io import tempfile import re import os @@ -19,6 +18,7 @@ import pytest +from sphinx.errors import ExtensionError import sphinx_gallery.gen_rst as sg from sphinx_gallery import downloads from sphinx_gallery.gen_gallery import generate_dir_rst @@ -286,9 +286,9 @@ assert intro_paragraph.replace('\n', ' ')[:95] == intro[:95] # Errors - with pytest.raises(ValueError, match='should have a header'): + with pytest.raises(ExtensionError, match='should have a header'): sg.extract_intro_and_title('', '') # no title - with pytest.raises(ValueError, match='Could not find a title'): + with pytest.raises(ExtensionError, match='Could not find a title'): sg.extract_intro_and_title('', '=====') # no real title @@ -451,7 +451,7 @@ args = (gallery_conf['src_dir'], gallery_conf['gallery_dir'], gallery_conf, []) if ext == '.bad': # not found with correct ext - with pytest.raises(FileNotFoundError, match='does not have a README'): + with pytest.raises(ExtensionError, match='does not have a README'): generate_dir_rst(*args) else: out = generate_dir_rst(*args) @@ -748,10 +748,9 @@ class TestLoggingTee: def setup(self): - self.output_file = io.StringIO() self.src_filename = 'source file name' - self.tee = sg.LoggingTee(self.output_file, sg.logger, - self.src_filename) + self.tee = sg._LoggingTee(self.src_filename) + self.output_file = self.tee.output def test_full_line(self, log_collector): # A full line is output immediately. @@ -802,7 +801,7 @@ def test_isatty(self, monkeypatch): assert not self.tee.isatty() - monkeypatch.setattr(self.tee.output_file, 'isatty', lambda: True) + monkeypatch.setattr(self.tee.output, 'isatty', lambda: True) assert self.tee.isatty() diff -Nru sphinx-gallery-0.6.2/sphinx_gallery/tests/test_py_source_parser.py sphinx-gallery-0.7.0/sphinx_gallery/tests/test_py_source_parser.py --- sphinx-gallery-0.6.2/sphinx_gallery/tests/test_py_source_parser.py 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/sphinx_gallery/tests/test_py_source_parser.py 2020-05-21 17:28:31.000000000 +0000 @@ -12,6 +12,7 @@ import os.path as op import pytest +from sphinx.errors import ExtensionError import sphinx_gallery.py_source_parser as sg @@ -23,13 +24,13 @@ fname = op.join(str(tmpdir), 'temp') with open(fname, 'w') as fid: fid.write('print("hello")\n') - with pytest.raises(ValueError, match='Could not find docstring'): + with pytest.raises(ExtensionError, match='Could not find docstring'): sg._get_docstring_and_rest(fname) with open(fname, 'w') as fid: fid.write('print hello\n') assert sg._get_docstring_and_rest(fname)[0] == sg.SYNTAX_ERROR_DOCSTRING monkeypatch.setattr(sg, 'parse_source_file', lambda x: ('', None)) - with pytest.raises(TypeError, match='only supports modules'): + with pytest.raises(ExtensionError, match='only supports modules'): sg._get_docstring_and_rest('') diff -Nru sphinx-gallery-0.6.2/sphinx_gallery/tests/test_scrapers.py sphinx-gallery-0.7.0/sphinx_gallery/tests/test_scrapers.py --- sphinx-gallery-0.6.2/sphinx_gallery/tests/test_scrapers.py 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/sphinx_gallery/tests/test_scrapers.py 2020-05-21 17:28:31.000000000 +0000 @@ -3,6 +3,7 @@ import pytest import numpy as np +from sphinx.errors import ConfigError, ExtensionError import sphinx_gallery from sphinx_gallery.gen_gallery import _complete_gallery_conf from sphinx_gallery.scrapers import (figure_rst, mayavi_scraper, SINGLE_IMAGE, @@ -135,13 +136,13 @@ # without the monkey patch to add sphinx_gallery._get_sg_image_scraper, # we should get an error gallery_conf.update(image_scrapers=['sphinx_gallery']) - with pytest.raises(ValueError, + with pytest.raises(ConfigError, match="has no attribute '_get_sg_image_scraper'"): _complete_gallery_conf(*complete_args) # other degenerate conditions gallery_conf.update(image_scrapers=['foo']) - with pytest.raises(ValueError, match='Unknown image scraper'): + with pytest.raises(ConfigError, match='Unknown image scraper'): _complete_gallery_conf(*complete_args) gallery_conf.update(image_scrapers=[_custom_func]) fname_template = os.path.join(gallery_conf['gallery_dir'], @@ -149,22 +150,22 @@ image_path_iterator = ImagePathIterator(fname_template) block = ('',) * 3 block_vars = dict(image_path_iterator=image_path_iterator) - with pytest.raises(RuntimeError, match='did not produce expected image'): + with pytest.raises(ExtensionError, match='did not produce expected image'): save_figures(block, block_vars, gallery_conf) gallery_conf.update(image_scrapers=[lambda x, y, z: 1.]) - with pytest.raises(TypeError, match='was not a string'): + with pytest.raises(ExtensionError, match='was not a string'): save_figures(block, block_vars, gallery_conf) # degenerate string interface gallery_conf.update(image_scrapers=['sphinx_gallery']) with monkeypatch.context() as m: m.setattr(sphinx_gallery, '_get_sg_image_scraper', 'foo', raising=False) - with pytest.raises(ValueError, match='^Unknown image.*\n.*callable'): + with pytest.raises(ConfigError, match='^Unknown image.*\n.*callable'): _complete_gallery_conf(*complete_args) with monkeypatch.context() as m: m.setattr(sphinx_gallery, '_get_sg_image_scraper', lambda: 'foo', raising=False) - with pytest.raises(ValueError, match='^Scraper.*was not callable'): + with pytest.raises(ConfigError, match='^Scraper.*was not callable'): _complete_gallery_conf(*complete_args) @@ -175,6 +176,7 @@ image_rst = figure_rst(figure_list, '.') single_image = """ .. image:: /sphx_glr_plot_1.{ext} + :alt: pl :class: sphx-glr-single-img """.format(ext=ext) assert image_rst == single_image @@ -188,12 +190,14 @@ * .. image:: /sphx_glr_plot_1.{ext} - :class: sphx-glr-multi-img + :alt: pl + :class: sphx-glr-multi-img * .. image:: /second.{ext} - :class: sphx-glr-multi-img + :alt: pl + :class: sphx-glr-multi-img """.format(ext=ext) assert image_rst == image_list_rst @@ -201,7 +205,7 @@ local_img = [os.path.join(os.getcwd(), 'third.' + ext)] image_rst = figure_rst(local_img, '.') - single_image = SINGLE_IMAGE % ("third." + ext) + single_image = SINGLE_IMAGE % ("third." + ext, '') assert image_rst == single_image @@ -209,6 +213,6 @@ """Test ImagePathIterator.""" ipi = ImagePathIterator('foo{0}') ipi._stop = 10 - with pytest.raises(RuntimeError, match='10 images'): + with pytest.raises(ExtensionError, match='10 images'): for ii in ipi: pass diff -Nru sphinx-gallery-0.6.2/sphinx_gallery/tests/test_sorting.py sphinx-gallery-0.7.0/sphinx_gallery/tests/test_sorting.py --- sphinx-gallery-0.6.2/sphinx_gallery/tests/test_sorting.py 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/sphinx_gallery/tests/test_sorting.py 2020-05-21 17:28:31.000000000 +0000 @@ -11,6 +11,7 @@ import os.path as op import pytest +from sphinx.errors import ConfigError from sphinx_gallery.sorting import (ExplicitOrder, NumberOfCodeLinesSortKey, FileNameSortKey, FileSizeSortKey, ExampleTitleSortKey) @@ -26,12 +27,12 @@ assert sorted_folders == explicit_folders # Test fails on wrong input - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ConfigError) as excinfo: ExplicitOrder('nope') excinfo.match("ExplicitOrder sorting key takes a list") # Test missing folder - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ConfigError) as excinfo: sorted_folders = sorted(all_folders, key=key) excinfo.match('If you use an explicit folder ordering') diff -Nru sphinx-gallery-0.6.2/sphinx_gallery/tests/tinybuild/conf.py sphinx-gallery-0.7.0/sphinx_gallery/tests/tinybuild/conf.py --- sphinx-gallery-0.6.2/sphinx_gallery/tests/tinybuild/conf.py 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/sphinx_gallery/tests/tinybuild/conf.py 2020-05-21 17:28:31.000000000 +0000 @@ -30,7 +30,7 @@ exclude_patterns = ['_build'] intersphinx_mapping = { 'python': ('https://docs.python.org/3', None), - 'numpy': ('https://docs.scipy.org/doc/numpy', None), + 'numpy': ('https://numpy.org/doc/stable/', None), 'matplotlib': ('https://matplotlib.org/', None), 'joblib': ('https://joblib.readthedocs.io/en/latest', None), } @@ -47,7 +47,9 @@ 'image_scrapers': (matplotlib_format_scraper(),), 'expected_failing_examples': ['examples/future/plot_future_imports_broken.py'], # noqa 'show_memory': True, + 'compress_images': ('images', 'thumbnails'), 'junit': op.join('sphinx-gallery', 'junit-results.xml'), + 'matplotlib_animations': True, } nitpicky = True highlight_language = 'python3' diff -Nru sphinx-gallery-0.6.2/sphinx_gallery/tests/tinybuild/examples/plot_animation.py sphinx-gallery-0.7.0/sphinx_gallery/tests/tinybuild/examples/plot_animation.py --- sphinx-gallery-0.6.2/sphinx_gallery/tests/tinybuild/examples/plot_animation.py 1970-01-01 00:00:00.000000000 +0000 +++ sphinx-gallery-0.7.0/sphinx_gallery/tests/tinybuild/examples/plot_animation.py 2020-05-21 17:28:31.000000000 +0000 @@ -0,0 +1,30 @@ +""" +Animation support +================= + +Show an animation, which should end up nicely embedded below. +""" + +import numpy as np +import matplotlib.pyplot as plt +import matplotlib.animation as animation + +# Adapted from +# https://matplotlib.org/gallery/animation/basic_example.html + + +def _update_line(num): + line.set_data(data[..., :num]) + return line, + + +fig_0, ax_0 = plt.subplots(figsize=(5, 1)) + +# sphinx_gallery_thumbnail_number = 2 +fig_1, ax_1 = plt.subplots(figsize=(5, 5)) +data = np.random.RandomState(0).rand(2, 25) +line, = ax_1.plot([], [], 'r-') +ax_1.set(xlim=(0, 1), ylim=(0, 1)) +ani = animation.FuncAnimation(fig_1, _update_line, 25, interval=100, blit=True) + +fig_2, ax_2 = plt.subplots(figsize=(5, 5)) diff -Nru sphinx-gallery-0.6.2/sphinx_gallery/tests/tinybuild/examples/plot_log.py sphinx-gallery-0.7.0/sphinx_gallery/tests/tinybuild/examples/plot_log.py --- sphinx-gallery-0.6.2/sphinx_gallery/tests/tinybuild/examples/plot_log.py 1970-01-01 00:00:00.000000000 +0000 +++ sphinx-gallery-0.7.0/sphinx_gallery/tests/tinybuild/examples/plot_log.py 2020-05-21 17:28:31.000000000 +0000 @@ -0,0 +1,26 @@ +""" +Logging test +============ +""" + +# %% + +import logging +import sys + + +def _create_logger(name=None): + logger = logging.getLogger(name=name) + logger.setLevel(logging.INFO) + sh = logging.StreamHandler(sys.stdout) + sh.setLevel(logging.INFO) + logger.addHandler(sh) + return logger + + +# %% +logger = _create_logger("first_logger") +logger.info("is in the same cell") + +# %% +logger.info("is not in the same cell") diff -Nru sphinx-gallery-0.6.2/sphinx_gallery/tests/tinybuild/examples/plot_matplotlib_alt.py sphinx-gallery-0.7.0/sphinx_gallery/tests/tinybuild/examples/plot_matplotlib_alt.py --- sphinx-gallery-0.6.2/sphinx_gallery/tests/tinybuild/examples/plot_matplotlib_alt.py 1970-01-01 00:00:00.000000000 +0000 +++ sphinx-gallery-0.7.0/sphinx_gallery/tests/tinybuild/examples/plot_matplotlib_alt.py 2020-05-21 17:28:31.000000000 +0000 @@ -0,0 +1,36 @@ +""" +====================== +Matplotlib alt text +====================== + +This example tests that the alt text is generated correctly for matplotlib +figures. +""" + +import matplotlib.pyplot as plt + +fig, axs = plt.subplots(2, 1, constrained_layout=True) +axs[0].plot([1, 2, 3]) +axs[0].set_title('subplot 1') +axs[0].set_xlabel('x label') +axs[0].set_ylabel('y lab') +fig.suptitle('This is a\nsup title') + +axs[1].plot([2, 3, 4]) +axs[1].set_title('subplot 2') +axs[1].set_xlabel('x label') +axs[1].set_ylabel('y label') + +plt.show() + + +# %% +# Several titles. + +plt.plot(range(10)) + +plt.title('Center Title') +plt.title('Left Title', loc='left') +plt.title('Right Title', loc='right') + +plt.show() diff -Nru sphinx-gallery-0.6.2/sphinx_gallery/tests/tinybuild/examples/plot_svg.py sphinx-gallery-0.7.0/sphinx_gallery/tests/tinybuild/examples/plot_svg.py --- sphinx-gallery-0.6.2/sphinx_gallery/tests/tinybuild/examples/plot_svg.py 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/sphinx_gallery/tests/tinybuild/examples/plot_svg.py 2020-05-21 17:28:31.000000000 +0000 @@ -1,9 +1,10 @@ """ -============ -SVG graphics -============ +================== +"SVG":-`graphics_` +================== Make sure we can embed SVG graphics. +Use title that has punctuation marks. """ import numpy as np diff -Nru sphinx-gallery-0.6.2/sphinx_gallery/tests/tinybuild/index.rst sphinx-gallery-0.7.0/sphinx_gallery/tests/tinybuild/index.rst --- sphinx-gallery-0.6.2/sphinx_gallery/tests/tinybuild/index.rst 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/sphinx_gallery/tests/tinybuild/index.rst 2020-05-21 17:28:31.000000000 +0000 @@ -29,6 +29,9 @@ Examples -------- +This tests that mini-gallery reference labels work: +:ref:`sphx_glr_backref_sphinx_gallery.backreferences.identify_names`. + .. toctree:: :maxdepth: 2 diff -Nru sphinx-gallery-0.6.2/sphinx_gallery/tests/tinybuild/minigallery.rst sphinx-gallery-0.7.0/sphinx_gallery/tests/tinybuild/minigallery.rst --- sphinx-gallery-0.6.2/sphinx_gallery/tests/tinybuild/minigallery.rst 1970-01-01 00:00:00.000000000 +0000 +++ sphinx-gallery-0.7.0/sphinx_gallery/tests/tinybuild/minigallery.rst 2020-05-21 17:28:31.000000000 +0000 @@ -0,0 +1,37 @@ +Test mini-galleries +=================== + +Test 1-N + +.. minigallery:: sphinx_gallery.sorting.ExplicitOrder + +Test 1-D-D + +.. minigallery:: sphinx_gallery.sorting.ExplicitOrder + :add-heading: + +Test 1-D-C + +.. minigallery:: sphinx_gallery.sorting.ExplicitOrder + :add-heading: + :heading-level: - + +Test 1-C-D + +.. minigallery:: sphinx_gallery.sorting.ExplicitOrder + :add-heading: This is a custom heading + +Test 2-N + +.. minigallery:: sphinx_gallery.sorting.ExplicitOrder sphinx_gallery.sorting.FileNameSortKey + +Test 2-D-D + +.. minigallery:: sphinx_gallery.sorting.ExplicitOrder sphinx_gallery.sorting.FileNameSortKey + :add-heading: + +Test 2-C-C + +.. minigallery:: sphinx_gallery.sorting.ExplicitOrder sphinx_gallery.sorting.FileNameSortKey + :add-heading: This is a different custom heading + :heading-level: = diff -Nru sphinx-gallery-0.6.2/sphinx_gallery/tests/tinybuild/_templates/module.rst sphinx-gallery-0.7.0/sphinx_gallery/tests/tinybuild/_templates/module.rst --- sphinx-gallery-0.6.2/sphinx_gallery/tests/tinybuild/_templates/module.rst 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/sphinx_gallery/tests/tinybuild/_templates/module.rst 2020-05-21 17:28:31.000000000 +0000 @@ -16,11 +16,10 @@ .. autofunction:: {{ item }} - .. include:: backreferences/{{fullname}}.{{item}}.examples + .. _sphx_glr_backref_{{fullname}}.{{item}}: - .. raw:: html - -
+ .. minigallery:: {{fullname}}.{{item}} + :add-heading: {%- endfor %} {% endif %} @@ -36,11 +35,10 @@ .. autoclass:: {{ item }} :members: - .. include:: backreferences/{{fullname}}.{{item}}.examples - - .. raw:: html + .. _sphx_glr_backref_{{fullname}}.{{item}}: -
+ .. minigallery:: {{fullname}}.{{item}} + :add-heading: {%- endfor %} {% endif %} diff -Nru sphinx-gallery-0.6.2/sphinx_gallery/utils.py sphinx-gallery-0.7.0/sphinx_gallery/utils.py --- sphinx-gallery-0.6.2/sphinx_gallery/utils.py 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/sphinx_gallery/utils.py 2020-05-21 17:28:31.000000000 +0000 @@ -13,6 +13,9 @@ import hashlib import os from shutil import move, copyfile +import subprocess + +from sphinx.errors import ExtensionError def _get_image(): @@ -22,9 +25,9 @@ try: import Image except ImportError: - raise RuntimeError('Could not import pillow, which is required ' - 'to rescale images (e.g., for thumbnails): %s' - % (exc,)) + raise ExtensionError( + 'Could not import pillow, which is required ' + 'to rescale images (e.g., for thumbnails): %s' % (exc,)) return Image @@ -36,6 +39,7 @@ # local import to avoid testing dependency on PIL: Image = _get_image() img = Image.open(in_fname) + # XXX someday we should just try img.thumbnail((max_width, max_height)) ... width_in, height_in = img.size scale_w = max_width / float(width_in) scale_h = max_height / float(height_in) @@ -70,6 +74,41 @@ thumb.convert('RGB').save(out_fname) +def optipng(fname, args=()): + """Optimize a PNG in place. + + Parameters + ---------- + fname : str + The filename. If it ends with '.png', ``optipng -o7 fname`` will + be run. If it fails because the ``optipng`` executable is not found + or optipng fails, the function returns. + args : tuple + Extra command-line arguments, such as ``['-o7']``. + """ + if fname.endswith('.png'): + # -o7 because this is what CPython used + # https://github.com/python/cpython/pull/8032 + try: + subprocess.check_call( + ['optipng'] + list(args) + [fname], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + except (subprocess.CalledProcessError, IOError): # FileNotFoundError + pass + + +def _has_optipng(): + try: + subprocess.check_call(['optipng', '--version'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + except IOError: # FileNotFoundError + return False + else: + return True + + def replace_py_ipynb(fname): """Replace .py extension in filename by .ipynb""" fname_prefix, extension = os.path.splitext(fname) diff -Nru sphinx-gallery-0.6.2/.travis.yml sphinx-gallery-0.7.0/.travis.yml --- sphinx-gallery-0.6.2/.travis.yml 2020-04-19 19:50:10.000000000 +0000 +++ sphinx-gallery-0.7.0/.travis.yml 2020-05-21 17:28:31.000000000 +0000 @@ -1,15 +1,14 @@ language: python +os: linux dist: xenial sudo: false - services: - xvfb # For mayavi headless matrix: include: - - os: linux - env: DISTRIB="ubuntu" PYTHON_VERSION="3.6" SPHINXOPTS="" + - env: DISTRIB="ubuntu" PYTHON_VERSION="3.6" SPHINXOPTS="" addons: apt: packages: @@ -17,28 +16,28 @@ - python3-matplotlib - python3-pip - python3-coverage - - os: linux - env: DISTRIB="conda" PYTHON_VERSION="3.5" SPHINX_VERSION="==1.8.3" - - os: linux - env: DISTRIB="conda" PYTHON_VERSION="3.6" - - os: linux - env: DISTRIB="conda" PYTHON_VERSION="3.6" LOCALE=C - - os: linux - env: DISTRIB="conda" PYTHON_VERSION="3.7" LOCALE=C + - optipng + - env: DISTRIB="conda" PYTHON_VERSION="3.5" SPHINX_VERSION="==1.8.3" + - env: DISTRIB="conda" PYTHON_VERSION="3.6" + - env: DISTRIB="conda" PYTHON_VERSION="3.6" LOCALE=C + - env: DISTRIB="conda" PYTHON_VERSION="3.7" LOCALE=C SPHINXOPTS="-nWT --keep-going" - - os: linux - env: DISTRIB="conda" PYTHON_VERSION="3.7" SPHINX_VERSION="dev" + addons: + apt: + packages: + - optipng + - env: DISTRIB="conda" PYTHON_VERSION="3.7" SPHINX_VERSION="dev" - python: 3.7 env: DISTRIB="minimal" - python: "nightly" env: PYTHON_VERSION="nightly" - os: linux addons: apt: packages: - libblas-dev - liblapack-dev - gfortran + - libblas-dev + - liblapack-dev + - gfortran + - optipng before_install: # Make sure that things work even if the locale is set to C (which