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