swh:1:snp:4e3e7077647a709f15b8c1b32ce7100175d0580b
Raw File
Tip revision: f7e7962ff272df751da09e08d16cf9978bc37e01 authored by Jean Kossaifi on 03 December 2016, 23:53:35 UTC
Updated version
Tip revision: f7e7962
gen_gallery.py
# -*- coding: utf-8 -*-
# Author: Óscar Nájera
# License: 3-clause BSD
"""
========================
Sphinx-Gallery Generator
========================

Attaches Sphinx-Gallery to Sphinx in order to generate the galleries
when building the documentation.
"""


from __future__ import division, print_function, absolute_import
import copy
import re
import os
from . import glr_path_static
from .gen_rst import generate_dir_rst, SPHX_GLR_SIG
from .docs_resolv import embed_code_links
from .downloads import generate_zipfiles

DEFAULT_GALLERY_CONF = {
    'filename_pattern': re.escape(os.sep) + 'plot',
    'examples_dirs': os.path.join('..', 'examples'),
    'gallery_dirs': 'auto_examples',
    'mod_example_dir': os.path.join('modules', 'generated'),
    'doc_module': (),
    'reference_url': {},
    # build options
    'plot_gallery': True,
    'download_all_examples': True,
    'abort_on_example_error': False,
    'failing_examples': {},
    'expected_failing_examples': set(),
}


def clean_gallery_out(build_dir):
    """Deletes images under the sphx_glr namespace in the build directory"""
    # Sphinx hack: sphinx copies generated images to the build directory
    #  each time the docs are made.  If the desired image name already
    #  exists, it appends a digit to prevent overwrites.  The problem is,
    #  the directory is never cleared.  This means that each time you build
    #  the docs, the number of images in the directory grows.
    #
    # This question has been asked on the sphinx development list, but there
    #  was no response: http://osdir.com/ml/sphinx-dev/2011-02/msg00123.html
    #
    # The following is a hack that prevents this behavior by clearing the
    #  image build directory from gallery images each time the docs are built.
    #  If sphinx changes their layout between versions, this will not
    #  work (though it should probably not cause a crash).
    # Tested successfully on Sphinx 1.0.7

    build_image_dir = os.path.join(build_dir, '_images')
    if os.path.exists(build_image_dir):
        filelist = os.listdir(build_image_dir)
        for filename in filelist:
            if filename.startswith('sphx_glr') and filename.endswith('png'):
                os.remove(os.path.join(build_image_dir, filename))


def generate_gallery_rst(app):
    """Generate the Main examples gallery reStructuredText

    Start the sphinx-gallery configuration and recursively scan the examples
    directories in order to populate the examples gallery
    """
    try:
        plot_gallery = eval(app.builder.config.plot_gallery)
    except TypeError:
        plot_gallery = bool(app.builder.config.plot_gallery)

    gallery_conf = copy.deepcopy(DEFAULT_GALLERY_CONF)
    gallery_conf.update(app.config.sphinx_gallery_conf)
    gallery_conf.update(plot_gallery=plot_gallery)
    gallery_conf.update(
        abort_on_example_error=app.builder.config.abort_on_example_error)

    # this assures I can call the config in other places
    app.config.sphinx_gallery_conf = gallery_conf
    app.config.html_static_path.append(glr_path_static())

    clean_gallery_out(app.builder.outdir)

    examples_dirs = gallery_conf['examples_dirs']
    gallery_dirs = gallery_conf['gallery_dirs']

    if not isinstance(examples_dirs, list):
        examples_dirs = [examples_dirs]
    if not isinstance(gallery_dirs, list):
        gallery_dirs = [gallery_dirs]

    mod_examples_dir = os.path.relpath(gallery_conf['mod_example_dir'],
                                       app.builder.srcdir)
    seen_backrefs = set()

    computation_times = []

    # cd to the appropriate directory regardless of sphinx configuration
    working_dir = os.getcwd()
    os.chdir(app.builder.srcdir)
    for examples_dir, gallery_dir in zip(examples_dirs, gallery_dirs):
        examples_dir = os.path.relpath(examples_dir,
                                       app.builder.srcdir)
        gallery_dir = os.path.relpath(gallery_dir,
                                      app.builder.srcdir)

        for workdir in [examples_dir, gallery_dir, mod_examples_dir]:
            if not os.path.exists(workdir):
                os.makedirs(workdir)
        # we create an index.rst with all examples
        fhindex = open(os.path.join(gallery_dir, 'index.rst'), 'w')
        # Here we don't use an os.walk, but we recurse only twice: flat is
        # better than nested.
        this_fhindex, this_computation_times = \
            generate_dir_rst(examples_dir, gallery_dir, gallery_conf,
                             seen_backrefs)

        computation_times += this_computation_times

        fhindex.write(this_fhindex)
        for directory in sorted(os.listdir(examples_dir)):
            if os.path.isdir(os.path.join(examples_dir, directory)):
                src_dir = os.path.join(examples_dir, directory)
                target_dir = os.path.join(gallery_dir, directory)
                this_fhindex, this_computation_times = \
                    generate_dir_rst(src_dir, target_dir, gallery_conf,
                                     seen_backrefs)
                fhindex.write(this_fhindex)
                computation_times += this_computation_times

        if gallery_conf['download_all_examples']:
            download_fhindex = generate_zipfiles(gallery_dir)
            fhindex.write(download_fhindex)

        fhindex.write(SPHX_GLR_SIG)
        fhindex.flush()

    # Back to initial directory
    os.chdir(working_dir)

    print("Computation time summary:")
    for time_elapsed, fname in sorted(computation_times)[::-1]:
        if time_elapsed is not None:
            print("\t- %s : %.2g sec" % (fname, time_elapsed))
        else:
            print("\t- %s : not run" % fname)


def touch_empty_backreferences(app, what, name, obj, options, lines):
    """Generate empty back-reference example files

    This avoids inclusion errors/warnings if there are no gallery
    examples for a class / module that is being parsed by autodoc"""

    examples_path = os.path.join(app.srcdir,
                                 app.config.sphinx_gallery_conf[
                                     "mod_example_dir"],
                                 "%s.examples" % name)

    if not os.path.exists(examples_path):
        # touch file
        open(examples_path, 'w').close()


def sumarize_failing_examples(app, exception):
    """Collects the list of falling examples during build and prints them with the traceback

    Raises ValueError if there where failing examples
    """
    if exception is not None:
        return

    # Under no-plot Examples are not run so nothing to summarize
    if not app.config.sphinx_gallery_conf['plot_gallery']:
        return

    gallery_conf = app.config.sphinx_gallery_conf
    failing_examples = set(gallery_conf['failing_examples'])
    expected_failing_examples = set(gallery_conf['expected_failing_examples'])

    examples_expected_to_fail = failing_examples.intersection(
        expected_failing_examples)
    expected_fail_msg = []
    if examples_expected_to_fail:
        expected_fail_msg.append("Examples failing as expected:")
        for fail_example in examples_expected_to_fail:
            expected_fail_msg.append(fail_example + ' failed leaving traceback:\n' +
                                     gallery_conf['failing_examples'][fail_example] + '\n')
        print("\n".join(expected_fail_msg))

    examples_not_expected_to_fail = failing_examples.difference(
        expected_failing_examples)
    fail_msgs = []
    if examples_not_expected_to_fail:
        fail_msgs.append("Unexpected failing examples:")
        for fail_example in examples_not_expected_to_fail:
            fail_msgs.append(fail_example + ' failed leaving traceback:\n' +
                             gallery_conf['failing_examples'][fail_example] + '\n')

    examples_not_expected_to_pass = expected_failing_examples.difference(
        failing_examples)
    if examples_not_expected_to_pass:
        fail_msgs.append("Examples expected to fail, but not failling:\n" +
                         "Please remove this examples from\n" +
                         "sphinx_gallery_conf['expected_failing_examples']\n" +
                         "in your conf.py file"
                         "\n".join(examples_not_expected_to_pass))

    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)


def get_default_config_value(key):
    def default_getter(conf):
        return conf['sphinx_gallery_conf'].get(key, DEFAULT_GALLERY_CONF[key])
    return default_getter


def setup(app):
    """Setup sphinx-gallery sphinx extension"""
    app.add_config_value('sphinx_gallery_conf', DEFAULT_GALLERY_CONF, 'html')
    for key in ['plot_gallery', 'abort_on_example_error']:
        app.add_config_value(key, get_default_config_value(key), 'html')

    app.add_stylesheet('gallery.css')

    if 'sphinx.ext.autodoc' in app._extensions:
        app.connect('autodoc-process-docstring', touch_empty_backreferences)

    app.connect('builder-inited', generate_gallery_rst)

    app.connect('build-finished', sumarize_failing_examples)
    app.connect('build-finished', embed_code_links)


def setup_module():
    # HACK: Stop nosetests running setup() above
    pass
back to top