diff --git a/build_tools/circle/build_doc.sh b/build_tools/circle/build_doc.sh index 63c8da5aafeac..b3f785254c2ae 100755 --- a/build_tools/circle/build_doc.sh +++ b/build_tools/circle/build_doc.sh @@ -109,7 +109,7 @@ conda update --yes --quiet conda conda create -n $CONDA_ENV_NAME --yes --quiet python numpy scipy \ cython nose coverage matplotlib sphinx=1.6.2 pillow source activate testenv -pip install numpydoc +pip install sphinx-gallery numpydoc # Build and install scikit-learn in dev mode python setup.py develop diff --git a/doc/README.md b/doc/README.md index 141db3d7a8da5..82240fb701aa3 100644 --- a/doc/README.md +++ b/doc/README.md @@ -1,8 +1,13 @@ # Documentation for scikit-learn This section contains the full manual and web page as displayed in -http://scikit-learn.org. To generate the full web page, including -the example gallery (this might take a while): +http://scikit-learn.org. +Building the website requires the sphinx and sphinx-gallery packages: + + pip install sphinx sphinx-gallery + +To generate the full web page, including the example gallery (this might take a +while): make html @@ -16,7 +21,6 @@ To build the PDF manual, run make latexpdf - The website is hosted at github and can be updated manually (for releases) by pushing to the https://github.com/scikit-learn/scikit-learn.github.io repository. diff --git a/doc/sphinxext/sphinx_gallery/__init__.py b/doc/sphinxext/sphinx_gallery/__init__.py deleted file mode 100644 index e113f97d2a2c7..0000000000000 --- a/doc/sphinxext/sphinx_gallery/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -""" -Sphinx Gallery -============== - -""" -import os -__version__ = '0.1.11' - - -def glr_path_static(): - """Returns path to packaged static files""" - return os.path.abspath(os.path.join(os.path.dirname(__file__), '_static')) diff --git a/doc/sphinxext/sphinx_gallery/_static/broken_example.png b/doc/sphinxext/sphinx_gallery/_static/broken_example.png deleted file mode 100644 index 4fea24e7df478..0000000000000 Binary files a/doc/sphinxext/sphinx_gallery/_static/broken_example.png and /dev/null differ diff --git a/doc/sphinxext/sphinx_gallery/_static/gallery.css b/doc/sphinxext/sphinx_gallery/_static/gallery.css deleted file mode 100644 index 37047a9b91175..0000000000000 --- a/doc/sphinxext/sphinx_gallery/_static/gallery.css +++ /dev/null @@ -1,192 +0,0 @@ -/* -Sphinx-Gallery has compatible CSS to fix default sphinx themes -Tested for Sphinx 1.3.1 for all themes: default, alabaster, sphinxdoc, -scrolls, agogo, traditional, nature, haiku, pyramid -Tested for Read the Docs theme 0.1.7 */ -.sphx-glr-thumbcontainer { - background: #fff; - border: solid #fff 1px; - -moz-border-radius: 5px; - -webkit-border-radius: 5px; - border-radius: 5px; - box-shadow: none; - float: left; - margin: 5px; - min-height: 230px; - padding-top: 5px; - position: relative; -} -.sphx-glr-thumbcontainer:hover { - border: solid #b4ddfc 1px; - box-shadow: 0 0 15px rgba(142, 176, 202, 0.5); -} -.sphx-glr-thumbcontainer a.internal { - bottom: 0; - display: block; - left: 0; - padding: 150px 10px 0; - position: absolute; - right: 0; - top: 0; -} -/* Next one is to avoid Sphinx traditional theme to cover all the -thumbnail with its default link Background color */ -.sphx-glr-thumbcontainer a.internal:hover { - background-color: transparent; -} - -.sphx-glr-thumbcontainer p { - margin: 0 0 .1em 0; -} -.sphx-glr-thumbcontainer .figure { - margin: 10px; - width: 160px; -} -.sphx-glr-thumbcontainer img { - display: inline; - max-height: 160px; - width: 160px; -} -.sphx-glr-thumbcontainer[tooltip]:hover:after { - background: rgba(0, 0, 0, 0.8); - -webkit-border-radius: 5px; - -moz-border-radius: 5px; - border-radius: 5px; - color: #fff; - content: attr(tooltip); - left: 95%; - padding: 5px 15px; - position: absolute; - z-index: 98; - width: 220px; - bottom: 52%; -} -.sphx-glr-thumbcontainer[tooltip]:hover:before { - border: solid; - border-color: #333 transparent; - border-width: 18px 0 0 20px; - bottom: 58%; - content: ''; - left: 85%; - position: absolute; - z-index: 99; -} - -.highlight-pytb pre { - background-color: #ffe4e4; - border: 1px solid #f66; - margin-top: 10px; - padding: 7px; -} - -.sphx-glr-script-out { - color: #888; - margin: 0; -} -.sphx-glr-script-out .highlight { - background-color: transparent; - margin-left: 2.5em; - margin-top: -1.4em; -} -.sphx-glr-script-out .highlight pre { - background-color: #fafae2; - border: 0; - max-height: 30em; - overflow: auto; - padding-left: 1ex; - margin: 0px; - word-break: break-word; -} -.sphx-glr-script-out + p { - margin-top: 1.8em; -} -blockquote.sphx-glr-script-out { - margin-left: 0pt; -} - -div.sphx-glr-footer { - text-align: center; -} - -div.sphx-glr-download { - display: inline-block; - margin: 1em auto 1ex 2ex; - vertical-align: middle; -} - -div.sphx-glr-download a { - background-color: #ffc; - background-image: linear-gradient(to bottom, #FFC, #d5d57e); - border-radius: 4px; - border: 1px solid #c2c22d; - color: #000; - display: inline-block; - /* Not valid in old browser, hence we keep the line above to override */ - display: table-caption; - font-weight: bold; - padding: 1ex; - text-align: center; -} - -/* The last child of a download button is the file name */ -div.sphx-glr-download a span:last-child { - font-size: smaller; -} - -@media (min-width: 20em) { - div.sphx-glr-download a { - min-width: 10em; - } -} - -@media (min-width: 30em) { - div.sphx-glr-download a { - min-width: 13em; - } -} - -@media (min-width: 40em) { - div.sphx-glr-download a { - min-width: 16em; - } -} - - -div.sphx-glr-download code.download { - display: inline-block; - white-space: normal; - word-break: normal; - overflow-wrap: break-word; - /* border and background are given by the enclosing 'a' */ - border: none; - background: none; -} - -div.sphx-glr-download a:hover { - box-shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 5px rgba(0,0,0,.25); - text-decoration: none; - background-image: none; - background-color: #d5d57e; -} - -ul.sphx-glr-horizontal { - list-style: none; - padding: 0; -} -ul.sphx-glr-horizontal li { - display: inline; -} -ul.sphx-glr-horizontal img { - height: auto !important; -} - -p.sphx-glr-signature a.reference.external { - -moz-border-radius: 5px; - -webkit-border-radius: 5px; - border-radius: 5px; - padding: 3px; - font-size: 75%; - text-align: right; - margin-left: auto; - display: table; -} diff --git a/doc/sphinxext/sphinx_gallery/_static/no_image.png b/doc/sphinxext/sphinx_gallery/_static/no_image.png deleted file mode 100644 index 8c2d48d5d3f00..0000000000000 Binary files a/doc/sphinxext/sphinx_gallery/_static/no_image.png and /dev/null differ diff --git a/doc/sphinxext/sphinx_gallery/backreferences.py b/doc/sphinxext/sphinx_gallery/backreferences.py deleted file mode 100644 index 32e4dd913f901..0000000000000 --- a/doc/sphinxext/sphinx_gallery/backreferences.py +++ /dev/null @@ -1,197 +0,0 @@ -# -*- coding: utf-8 -*- -# Author: Óscar Nájera -# License: 3-clause BSD -""" -Backreferences Generator -======================== - -Parses example file code in order to keep track of used functions -""" - -from __future__ import print_function -import ast -import os - - -# Try Python 2 first, otherwise load from Python 3 -try: - import cPickle as pickle -except ImportError: - import pickle - - -class NameFinder(ast.NodeVisitor): - """Finds the longest form of variable names and their imports in code - - Only retains names from imported modules. - """ - - def __init__(self): - super(NameFinder, self).__init__() - self.imported_names = {} - self.accessed_names = set() - - def visit_Import(self, node, prefix=''): - for alias in node.names: - local_name = alias.asname or alias.name - self.imported_names[local_name] = prefix + alias.name - - def visit_ImportFrom(self, node): - self.visit_Import(node, node.module + '.') - - def visit_Name(self, node): - self.accessed_names.add(node.id) - - def visit_Attribute(self, node): - attrs = [] - while isinstance(node, ast.Attribute): - attrs.append(node.attr) - node = node.value - - if isinstance(node, ast.Name): - # This is a.b, not e.g. a().b - attrs.append(node.id) - self.accessed_names.add('.'.join(reversed(attrs))) - else: - # need to get a in a().b - self.visit(node) - - def get_mapping(self): - for name in self.accessed_names: - local_name = name.split('.', 1)[0] - remainder = name[len(local_name):] - if local_name in self.imported_names: - # Join import path to relative path - full_name = self.imported_names[local_name] + remainder - yield name, full_name - - -def get_short_module_name(module_name, obj_name): - """ Get the shortest possible module name """ - parts = module_name.split('.') - short_name = module_name - for i in range(len(parts) - 1, 0, -1): - short_name = '.'.join(parts[:i]) - try: - exec('from %s import %s' % (short_name, obj_name)) - except Exception: # libraries can throw all sorts of exceptions... - # get the last working module name - short_name = '.'.join(parts[:(i + 1)]) - break - return short_name - - -def identify_names(code): - """Builds a codeobj summary by identifying and resolving used names - - >>> code = ''' - ... from a.b import c - ... import d as e - ... print(c) - ... e.HelloWorld().f.g - ... ''' - >>> for name, o in sorted(identify_names(code).items()): - ... print(name, o['name'], o['module'], o['module_short']) - c c a.b a.b - e.HelloWorld HelloWorld d d - """ - finder = NameFinder() - try: - finder.visit(ast.parse(code)) - except SyntaxError: - return {} - - example_code_obj = {} - for name, full_name in finder.get_mapping(): - # name is as written in file (e.g. np.asarray) - # full_name includes resolved import path (e.g. numpy.asarray) - splitted = full_name.rsplit('.', 1) - if len(splitted) == 1: - # module without attribute. This is not useful for - # backreferences - continue - - module, attribute = splitted - # get shortened module name - module_short = get_short_module_name(module, attribute) - cobj = {'name': attribute, 'module': module, - 'module_short': module_short} - example_code_obj[name] = cobj - return example_code_obj - - -def scan_used_functions(example_file, gallery_conf): - """save variables so we can later add links to the documentation""" - example_code_obj = identify_names(open(example_file).read()) - if example_code_obj: - codeobj_fname = example_file[:-3] + '_codeobj.pickle' - with open(codeobj_fname, 'wb') as fid: - pickle.dump(example_code_obj, fid, pickle.HIGHEST_PROTOCOL) - - backrefs = set('{module_short}.{name}'.format(**entry) - for entry in example_code_obj.values() - if entry['module'].startswith(gallery_conf['doc_module'])) - - return backrefs - - -THUMBNAIL_TEMPLATE = """ -.. raw:: html - -
- -.. only:: html - - .. figure:: /{thumbnail} - - :ref:`sphx_glr_{ref_name}` - -.. raw:: html - -
-""" - -BACKREF_THUMBNAIL_TEMPLATE = THUMBNAIL_TEMPLATE + """ -.. only:: not html - - * :ref:`sphx_glr_{ref_name}` -""" - - -def _thumbnail_div(full_dir, fname, snippet, is_backref=False): - """Generates RST to place a thumbnail in a gallery""" - thumb = os.path.join(full_dir, 'images', 'thumb', - 'sphx_glr_%s_thumb.png' % fname[:-3]) - - # Inside rst files forward slash defines paths - thumb = thumb.replace(os.sep, "/") - - ref_name = os.path.join(full_dir, fname).replace(os.path.sep, '_') - - template = BACKREF_THUMBNAIL_TEMPLATE if is_backref else THUMBNAIL_TEMPLATE - return template.format(snippet=snippet, thumbnail=thumb, ref_name=ref_name) - - -def write_backreferences(seen_backrefs, gallery_conf, - target_dir, fname, snippet): - """Writes down back reference files, which include a thumbnail list - of examples using a certain module""" - if gallery_conf['backreferences_dir'] is None: - return - - example_file = os.path.join(target_dir, fname) - build_target_dir = os.path.relpath(target_dir, gallery_conf['src_dir']) - backrefs = scan_used_functions(example_file, gallery_conf) - for backref in backrefs: - include_path = os.path.join(gallery_conf['src_dir'], - gallery_conf['backreferences_dir'], - '%s.examples' % backref) - seen = backref in seen_backrefs - with open(include_path, 'a' if seen else 'w') as ex_file: - if not seen: - heading = '\n\nExamples using ``%s``' % backref - ex_file.write(heading + '\n') - ex_file.write('^' * len(heading) + '\n') - ex_file.write(_thumbnail_div(build_target_dir, fname, snippet, - is_backref=True)) - seen_backrefs.add(backref) diff --git a/doc/sphinxext/sphinx_gallery/docs_resolv.py b/doc/sphinxext/sphinx_gallery/docs_resolv.py deleted file mode 100644 index 0f9943b683d1c..0000000000000 --- a/doc/sphinxext/sphinx_gallery/docs_resolv.py +++ /dev/null @@ -1,463 +0,0 @@ -# -*- coding: utf-8 -*- -# Author: Óscar Nájera -# License: 3-clause BSD -""" -Link resolver objects -===================== -""" -from __future__ import print_function -import gzip -import os -import posixpath -import re -import shelve -import sys - -from sphinx.util.console import fuchsia - -# Try Python 2 first, otherwise load from Python 3 -try: - import cPickle as pickle - import urllib2 as urllib - from urllib2 import HTTPError, URLError -except ImportError: - import pickle - import urllib.request - import urllib.error - import urllib.parse - from urllib.error import HTTPError, URLError - -from io import StringIO - - -def _get_data(url): - """Helper function to get data over http or from a local file""" - if url.startswith('http://'): - # Try Python 2, use Python 3 on exception - try: - resp = urllib.urlopen(url) - encoding = resp.headers.dict.get('content-encoding', 'plain') - except AttributeError: - resp = urllib.request.urlopen(url) - encoding = resp.headers.get('content-encoding', 'plain') - data = resp.read() - if encoding == 'plain': - pass - elif encoding == 'gzip': - data = StringIO(data) - data = gzip.GzipFile(fileobj=data).read() - else: - raise RuntimeError('unknown encoding') - else: - with open(url, 'r') as fid: - data = fid.read() - - return data - - -def get_data(url, gallery_dir): - """Persistent dictionary usage to retrieve the search indexes""" - - # shelve keys need to be str in python 2 - if sys.version_info[0] == 2 and isinstance(url, unicode): - url = url.encode('utf-8') - - cached_file = os.path.join(gallery_dir, 'searchindex') - search_index = shelve.open(cached_file) - if url in search_index: - data = search_index[url] - else: - data = _get_data(url) - search_index[url] = data - search_index.close() - - return data - - -def _select_block(str_in, start_tag, end_tag): - """Select first block delimited by start_tag and end_tag""" - start_pos = str_in.find(start_tag) - if start_pos < 0: - raise ValueError('start_tag not found') - depth = 0 - for pos in range(start_pos, len(str_in)): - if str_in[pos] == start_tag: - depth += 1 - elif str_in[pos] == end_tag: - depth -= 1 - - if depth == 0: - break - sel = str_in[start_pos + 1:pos] - return sel - - -def _parse_dict_recursive(dict_str): - """Parse a dictionary from the search index""" - dict_out = dict() - pos_last = 0 - pos = dict_str.find(':') - while pos >= 0: - key = dict_str[pos_last:pos] - if dict_str[pos + 1] == '[': - # value is a list - pos_tmp = dict_str.find(']', pos + 1) - if pos_tmp < 0: - raise RuntimeError('error when parsing dict') - value = dict_str[pos + 2: pos_tmp].split(',') - # try to convert elements to int - for i in range(len(value)): - try: - value[i] = int(value[i]) - except ValueError: - pass - elif dict_str[pos + 1] == '{': - # value is another dictionary - subdict_str = _select_block(dict_str[pos:], '{', '}') - value = _parse_dict_recursive(subdict_str) - pos_tmp = pos + len(subdict_str) - else: - raise ValueError('error when parsing dict: unknown elem') - - key = key.strip('"') - if len(key) > 0: - dict_out[key] = value - - pos_last = dict_str.find(',', pos_tmp) - if pos_last < 0: - break - pos_last += 1 - pos = dict_str.find(':', pos_last) - - return dict_out - - -def parse_sphinx_searchindex(searchindex): - """Parse a Sphinx search index - - Parameters - ---------- - searchindex : str - The Sphinx search index (contents of searchindex.js) - - Returns - ------- - filenames : list of str - The file names parsed from the search index. - objects : dict - The objects parsed from the search index. - """ - # Make sure searchindex uses UTF-8 encoding - if hasattr(searchindex, 'decode'): - searchindex = searchindex.decode('UTF-8') - - # parse objects - query = 'objects:' - pos = searchindex.find(query) - if pos < 0: - raise ValueError('"objects:" not found in search index') - - sel = _select_block(searchindex[pos:], '{', '}') - objects = _parse_dict_recursive(sel) - - # parse filenames - query = 'filenames:' - pos = searchindex.find(query) - if pos < 0: - raise ValueError('"filenames:" not found in search index') - filenames = searchindex[pos + len(query) + 1:] - filenames = filenames[:filenames.find(']')] - filenames = [f.strip('"') for f in filenames.split(',')] - - return filenames, objects - - -class SphinxDocLinkResolver(object): - """ Resolve documentation links using searchindex.js generated by Sphinx - - Parameters - ---------- - doc_url : str - The base URL of the project website. - searchindex : str - Filename of searchindex, relative to doc_url. - extra_modules_test : list of str - List of extra module names to test. - relative : bool - Return relative links (only useful for links to documentation of this - package). - """ - - def __init__(self, doc_url, gallery_dir, searchindex='searchindex.js', - extra_modules_test=None, relative=False): - self.doc_url = doc_url - self.gallery_dir = gallery_dir - self.relative = relative - self._link_cache = {} - - self.extra_modules_test = extra_modules_test - self._page_cache = {} - if doc_url.startswith('http://'): - if relative: - raise ValueError('Relative links are only supported for local ' - 'URLs (doc_url cannot start with "http://)"') - searchindex_url = doc_url + '/' + searchindex - else: - searchindex_url = os.path.join(doc_url, searchindex) - - # detect if we are using relative links on a Windows system - if os.name.lower() == 'nt' and not doc_url.startswith('http://'): - if not relative: - raise ValueError('You have to use relative=True for the local' - ' package on a Windows system.') - self._is_windows = True - else: - self._is_windows = False - - # download and initialize the search index - sindex = get_data(searchindex_url, gallery_dir) - filenames, objects = parse_sphinx_searchindex(sindex) - - self._searchindex = dict(filenames=filenames, objects=objects) - - def _get_link(self, cobj): - """Get a valid link, False if not found""" - - fname_idx = None - full_name = cobj['module_short'] + '.' + cobj['name'] - if full_name in self._searchindex['objects']: - value = self._searchindex['objects'][full_name] - if isinstance(value, dict): - value = value[next(iter(value.keys()))] - fname_idx = value[0] - elif cobj['module_short'] in self._searchindex['objects']: - value = self._searchindex['objects'][cobj['module_short']] - if cobj['name'] in value.keys(): - fname_idx = value[cobj['name']][0] - - if fname_idx is not None: - fname = self._searchindex['filenames'][fname_idx] - # In 1.5+ Sphinx seems to have changed from .rst.html to only - # .html extension in converted files. But URLs could be - # built with < 1.5 or >= 1.5 regardless of what we're currently - # building with, so let's just check both :( - fnames = [fname + '.html', os.path.splitext(fname)[0] + '.html'] - for fname in fnames: - try: - if self._is_windows: - fname = fname.replace('/', '\\') - link = os.path.join(self.doc_url, fname) - else: - link = posixpath.join(self.doc_url, fname) - - if hasattr(link, 'decode'): - link = link.decode('utf-8', 'replace') - - if link in self._page_cache: - html = self._page_cache[link] - else: - html = get_data(link, self.gallery_dir) - self._page_cache[link] = html - except (HTTPError, URLError, IOError): - pass - else: - break - else: - raise - - # test if cobj appears in page - comb_names = [cobj['module_short'] + '.' + cobj['name']] - if self.extra_modules_test is not None: - for mod in self.extra_modules_test: - comb_names.append(mod + '.' + cobj['name']) - url = False - if hasattr(html, 'decode'): - # Decode bytes under Python 3 - html = html.decode('utf-8', 'replace') - - for comb_name in comb_names: - if hasattr(comb_name, 'decode'): - # Decode bytes under Python 3 - comb_name = comb_name.decode('utf-8', 'replace') - if comb_name in html: - url = link + u'#' + comb_name - link = url - else: - link = False - - return link - - def resolve(self, cobj, this_url): - """Resolve the link to the documentation, returns None if not found - - Parameters - ---------- - cobj : dict - Dict with information about the "code object" for which we are - resolving a link. - cobj['name'] : function or class name (str) - cobj['module_short'] : shortened module name (str) - cobj['module'] : module name (str) - this_url: str - URL of the current page. Needed to construct relative URLs - (only used if relative=True in constructor). - - Returns - ------- - link : str | None - The link (URL) to the documentation. - """ - full_name = cobj['module_short'] + '.' + cobj['name'] - link = self._link_cache.get(full_name, None) - if link is None: - # we don't have it cached - link = self._get_link(cobj) - # cache it for the future - self._link_cache[full_name] = link - - if link is False or link is None: - # failed to resolve - return None - - if self.relative: - link = os.path.relpath(link, start=this_url) - if self._is_windows: - # replace '\' with '/' so it on the web - link = link.replace('\\', '/') - - # for some reason, the relative link goes one directory too high up - link = link[3:] - - return link - - -def _embed_code_links(app, gallery_conf, gallery_dir): - # Add resolvers for the packages for which we want to show links - doc_resolvers = {} - - src_gallery_dir = os.path.join(app.builder.srcdir, gallery_dir) - for this_module, url in gallery_conf['reference_url'].items(): - try: - if url is None: - doc_resolvers[this_module] = SphinxDocLinkResolver( - app.builder.outdir, - src_gallery_dir, - relative=True) - else: - doc_resolvers[this_module] = SphinxDocLinkResolver(url, - src_gallery_dir) - - except HTTPError as e: - print("The following HTTP Error has occurred:\n") - print(e.code) - except URLError as e: - print("\n...\n" - "Warning: Embedding the documentation hyperlinks requires " - "Internet access.\nPlease check your network connection.\n" - "Unable to continue embedding `{0}` links due to a URL " - "Error:\n".format(this_module)) - print(e.args) - - html_gallery_dir = os.path.abspath(os.path.join(app.builder.outdir, - gallery_dir)) - - # patterns for replacement - link_pattern = ('%s') - orig_pattern = '%s' - period = '.' - - # This could be turned into a generator if necessary, but should be okay - flat = [[dirpath, filename] - for dirpath, _, filenames in os.walk(html_gallery_dir) - for filename in filenames] - iterator = app.status_iterator( - flat, os.path.basename(html_gallery_dir), colorfunc=fuchsia, - length=len(flat), stringify_func=lambda x: os.path.basename(x[1])) - for dirpath, fname in iterator: - full_fname = os.path.join(html_gallery_dir, dirpath, fname) - subpath = dirpath[len(html_gallery_dir) + 1:] - pickle_fname = os.path.join(src_gallery_dir, subpath, - fname[:-5] + '_codeobj.pickle') - - if os.path.exists(pickle_fname): - # we have a pickle file with the objects to embed links for - with open(pickle_fname, 'rb') as fid: - example_code_obj = pickle.load(fid) - fid.close() - str_repl = {} - # generate replacement strings with the links - for name, cobj in example_code_obj.items(): - this_module = cobj['module'].split('.')[0] - - if this_module not in doc_resolvers: - continue - - try: - link = doc_resolvers[this_module].resolve(cobj, - full_fname) - except (HTTPError, URLError) as e: - if isinstance(e, HTTPError): - extra = e.code - else: - extra = e.reason - print("\n\t\tError resolving %s.%s: %r (%s)" - % (cobj['module'], cobj['name'], e, extra)) - continue - - if link is not None: - parts = name.split('.') - name_html = period.join(orig_pattern % part - for part in parts) - full_function_name = '%s.%s' % ( - cobj['module'], cobj['name']) - str_repl[name_html] = link_pattern % ( - link, full_function_name, name_html) - # do the replacement in the html file - - # ensure greediness - names = sorted(str_repl, key=len, reverse=True) - regex_str = '|'.join(re.escape(name) for name in names) - regex = re.compile(regex_str) - - def substitute_link(match): - return str_repl[match.group()] - - if len(str_repl) > 0: - with open(full_fname, 'rb') as fid: - lines_in = fid.readlines() - with open(full_fname, 'wb') as fid: - for line in lines_in: - line = line.decode('utf-8') - line = regex.sub(substitute_link, line) - fid.write(line.encode('utf-8')) - - -def embed_code_links(app, exception): - """Embed hyperlinks to documentation into example code""" - if exception is not None: - return - - # No need to waste time embedding hyperlinks when not running the examples - # XXX: also at the time of writing this fixes make html-noplot - # for some reason I don't fully understand - if not app.builder.config.plot_gallery: - return - - # XXX: Whitelist of builders for which it makes sense to embed - # hyperlinks inside the example html. Note that the link embedding - # require searchindex.js to exist for the links to the local doc - # and there does not seem to be a good way of knowing which - # builders creates a searchindex.js. - if app.builder.name not in ['html', 'readthedocs']: - return - - print('Embedding documentation hyperlinks in examples..') - - gallery_conf = app.config.sphinx_gallery_conf - - gallery_dirs = gallery_conf['gallery_dirs'] - if not isinstance(gallery_dirs, list): - gallery_dirs = [gallery_dirs] - - for gallery_dir in gallery_dirs: - _embed_code_links(app, gallery_conf, gallery_dir) diff --git a/doc/sphinxext/sphinx_gallery/downloads.py b/doc/sphinxext/sphinx_gallery/downloads.py deleted file mode 100644 index 6b5b3df17fc87..0000000000000 --- a/doc/sphinxext/sphinx_gallery/downloads.py +++ /dev/null @@ -1,120 +0,0 @@ -# -*- coding: utf-8 -*- -r""" -Utilities for downloadable items -================================ - -""" -# Author: Óscar Nájera -# License: 3-clause BSD - -from __future__ import absolute_import, division, print_function - -import os -import zipfile - -CODE_DOWNLOAD = """ -\n.. container:: sphx-glr-footer - -\n .. container:: sphx-glr-download - - :download:`Download Python source code: {0} <{0}>`\n - -\n .. container:: sphx-glr-download - - :download:`Download Jupyter notebook: {1} <{1}>`\n""" - -CODE_ZIP_DOWNLOAD = """ -\n.. container:: sphx-glr-footer - -\n .. container:: sphx-glr-download - - :download:`Download all examples in Python source code: {0} `\n - -\n .. container:: sphx-glr-download - - :download:`Download all examples in Jupyter notebooks: {2} `\n""" - - -def python_zip(file_list, gallery_path, extension='.py'): - """Stores all files in file_list into an zip file - - Parameters - ---------- - file_list : list of strings - Holds all the file names to be included in zip file - gallery_path : string - path to where the zipfile is stored - extension : str - '.py' or '.ipynb' In order to deal with downloads of python - sources and jupyter notebooks the file extension from files in - file_list will be removed and replace with the value of this - variable while generating the zip file - Returns - ------- - zipname : string - zip file name, written as `target_dir_{python,jupyter}.zip` - depending on the extension - """ - zipname = os.path.basename(gallery_path) - zipname += '_python' if extension == '.py' else '_jupyter' - zipname = os.path.join(gallery_path, zipname + '.zip') - - zipf = zipfile.ZipFile(zipname, mode='w') - for fname in file_list: - file_src = os.path.splitext(fname)[0] + extension - zipf.write(file_src, os.path.relpath(file_src, gallery_path)) - zipf.close() - - return zipname - - -def list_downloadable_sources(target_dir): - """Returns a list of python source files is target_dir - - Parameters - ---------- - target_dir : string - path to the directory where python source file are - Returns - ------- - list - list of paths to all Python source files in `target_dir` - """ - return [os.path.join(target_dir, fname) - for fname in os.listdir(target_dir) - if fname.endswith('.py')] - - -def generate_zipfiles(gallery_dir): - """ - Collects all Python source files and Jupyter notebooks in - gallery_dir and makes zipfiles of them - - Parameters - ---------- - gallery_dir : string - path of the gallery to collect downloadable sources - - Return - ------ - download_rst: string - RestructuredText to include download buttons to the generated files - """ - - listdir = list_downloadable_sources(gallery_dir) - for directory in sorted(os.listdir(gallery_dir)): - if os.path.isdir(os.path.join(gallery_dir, directory)): - target_dir = os.path.join(gallery_dir, directory) - listdir.extend(list_downloadable_sources(target_dir)) - - py_zipfile = python_zip(listdir, gallery_dir) - jy_zipfile = python_zip(listdir, gallery_dir, ".ipynb") - - def rst_path(filepath): - return filepath.replace(os.sep, '/') - - dw_rst = CODE_ZIP_DOWNLOAD.format(os.path.basename(py_zipfile), - rst_path(py_zipfile), - os.path.basename(jy_zipfile), - rst_path(jy_zipfile)) - return dw_rst diff --git a/doc/sphinxext/sphinx_gallery/gen_gallery.py b/doc/sphinxext/sphinx_gallery/gen_gallery.py deleted file mode 100644 index 1a1ce299fab1c..0000000000000 --- a/doc/sphinxext/sphinx_gallery/gen_gallery.py +++ /dev/null @@ -1,304 +0,0 @@ -# -*- 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 - -try: - FileNotFoundError -except NameError: - # Python2 - FileNotFoundError = IOError - -DEFAULT_GALLERY_CONF = { - 'filename_pattern': re.escape(os.sep) + 'plot', - 'examples_dirs': os.path.join('..', 'examples'), - 'gallery_dirs': 'auto_examples', - 'backreferences_dir': None, - '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 parse_config(app): - """Process the Sphinx Gallery configuration""" - # TODO: Test this behavior. - 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) - gallery_conf['src_dir'] = app.builder.srcdir - - backreferences_warning = """\n======== -Sphinx-Gallery now requires you to set the configuration variable -'backreferences_dir' in your config to activate the -backreferences. That is mini galleries clustered by the functions used -in the example scripts. Have a look at it in sphinx-gallery - -https://sphinx-gallery.readthedocs.io/en/stable/index.html#examples-using-numpy-linspace -""" - - if gallery_conf.get("mod_example_dir", False): - update_msg = """\nFor a quick fix try replacing 'mod_example_dir' -by 'backreferences_dir' in your conf.py file. If that does not solve the -present issue read carefully how to update in the online documentation - -https://sphinx-gallery.readthedocs.io/en/latest/advanced_configuration.html#references-to-examples""" - - gallery_conf['backreferences_dir'] = gallery_conf['mod_example_dir'] - app.warn("Old configuration for backreferences detected \n" - "using the configuration variable `mod_example_dir`\n" - + backreferences_warning - + update_msg, prefix="DeprecationWarning: ") - - elif gallery_conf['backreferences_dir'] is None: - no_care_msg = """ -If you don't care about this features set in your conf.py -'backreferences_dir': False\n""" - - app.warn(backreferences_warning + no_care_msg) - - gallery_conf['backreferences_dir'] = os.path.join( - 'modules', 'generated') - app.warn("using old default 'backreferences_dir':'{}'.\n" - " This will be disabled in future releases\n".format( - gallery_conf['backreferences_dir']), - prefix="DeprecationWarning: ") - - # 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()) - - return gallery_conf - - -def _prepare_sphx_glr_dirs(gallery_conf, srcdir): - """Creates necessary folders for sphinx_gallery files """ - 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] - - if bool(gallery_conf['backreferences_dir']): - backreferences_dir = os.path.join( - srcdir, gallery_conf['backreferences_dir']) - if not os.path.exists(backreferences_dir): - os.makedirs(backreferences_dir) - - return examples_dirs, gallery_dirs - - -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 - """ - print('Generating gallery') - gallery_conf = parse_config(app) - - clean_gallery_out(app.builder.outdir) - - seen_backrefs = set() - - computation_times = [] - examples_dirs, gallery_dirs = _prepare_sphx_glr_dirs(gallery_conf, - app.builder.srcdir) - - for examples_dir, gallery_dir in zip(examples_dirs, gallery_dirs): - examples_dir = os.path.join(app.builder.srcdir, examples_dir) - gallery_dir = os.path.join(app.builder.srcdir, gallery_dir) - - for workdir in [examples_dir, gallery_dir]: - if not os.path.exists(workdir): - os.makedirs(workdir) - # 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) - if this_fhindex == "": - raise FileNotFoundError("Main example directory {0} does not " - "have a README.txt file. Please write " - "one to introduce your gallery." - .format(examples_dir)) - - computation_times += this_computation_times - - # we create an index.rst with all examples - fhindex = open(os.path.join(gallery_dir, 'index.rst'), 'w') - # :orphan: to suppress "not included in TOCTREE" sphinx warnings - fhindex.write(":orphan:\n\n" + 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() - - if gallery_conf['plot_gallery']: - 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""" - - if not bool(app.config.sphinx_gallery_conf['backreferences_dir']): - return - - examples_path = os.path.join(app.srcdir, - app.config.sphinx_gallery_conf[ - "backreferences_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'].keys()) - expected_failing_examples = set([os.path.normpath(os.path.join(app.srcdir, path)) - for path in - 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("\n\nExamples 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 these 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') - # Sphinx < 1.6 calls it `_extensions`, >= 1.6 is `extensions`. - extensions_attr = '_extensions' if hasattr(app, '_extensions') else 'extensions' - if 'sphinx.ext.autodoc' in getattr(app, extensions_attr): - 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) diff --git a/doc/sphinxext/sphinx_gallery/gen_rst.py b/doc/sphinxext/sphinx_gallery/gen_rst.py deleted file mode 100644 index c2a0b95545499..0000000000000 --- a/doc/sphinxext/sphinx_gallery/gen_rst.py +++ /dev/null @@ -1,641 +0,0 @@ -# -*- coding: utf-8 -*- -# Author: Óscar Nájera -# License: 3-clause BSD -""" -RST file generator -================== - -Generate the rst files for the examples by iterating over the python -example files. - -Files that generate images should start with 'plot' - -""" -# Don't use unicode_literals here (be explicit with u"..." instead) otherwise -# tricky errors come up with exec(code_blocks, ...) calls -from __future__ import division, print_function, absolute_import -from time import time -import codecs -import hashlib -import os -import re -import shutil -import subprocess -import sys -import traceback -import warnings - - -# Try Python 2 first, otherwise load from Python 3 -try: - # textwrap indent only exists in python 3 - from textwrap import indent -except ImportError: - def indent(text, prefix, predicate=None): - """Adds 'prefix' to the beginning of selected lines in 'text'. - - If 'predicate' is provided, 'prefix' will only be added to the lines - where 'predicate(line)' is True. If 'predicate' is not provided, - it will default to adding 'prefix' to all non-empty lines that do not - consist solely of whitespace characters. - """ - if predicate is None: - def predicate(line): - return line.strip() - - def prefixed_lines(): - for line in text.splitlines(True): - yield (prefix + line if predicate(line) else line) - return ''.join(prefixed_lines()) - -from io import StringIO - -# make sure that the Agg backend is set before importing any -# matplotlib -import matplotlib -matplotlib.use('agg') -matplotlib_backend = matplotlib.get_backend() - -if matplotlib_backend != 'agg': - mpl_backend_msg = ( - "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 " - "terminate the build now, because changing backends is " - "not well supported by matplotlib. We advise you to move " - "sphinx_gallery imports before any matplotlib-dependent " - "import. Moving sphinx_gallery imports at the top of " - "your conf.py file should fix this issue") - - raise ValueError(mpl_backend_msg.format(matplotlib_backend)) - -import matplotlib.pyplot as plt - -from . import glr_path_static -from .backreferences import write_backreferences, _thumbnail_div -from .downloads import CODE_DOWNLOAD -from .py_source_parser import (get_docstring_and_rest, - split_code_and_text_blocks) - -from .notebook import jupyter_notebook, save_notebook - -try: - basestring -except NameError: - basestring = str - unicode = str - - -############################################################################### - - -class Tee(object): - """A tee object to redirect streams to multiple outputs""" - - def __init__(self, file1, file2): - self.file1 = file1 - self.file2 = file2 - - def write(self, data): - self.file1.write(data) - self.file2.write(data) - - def flush(self): - self.file1.flush() - self.file2.flush() - - # When called from a local terminal seaborn needs it in Python3 - def isatty(self): - self.file1.isatty() - - -class MixedEncodingStringIO(StringIO): - """Helper when both ASCII and unicode strings will be written""" - - def write(self, data): - if not isinstance(data, unicode): - data = data.decode('utf-8') - StringIO.write(self, data) - - -############################################################################### -# 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. -HLIST_HEADER = """ -.. rst-class:: sphx-glr-horizontal - -""" - -HLIST_IMAGE_TEMPLATE = """ - * - - .. image:: /%s - :scale: 47 -""" - -SINGLE_IMAGE = """ -.. image:: /%s - :align: center -""" - - -# This one could contain unicode -CODE_OUTPUT = u""".. rst-class:: sphx-glr-script-out - - Out:: - -{0}\n""" - - -SPHX_GLR_SIG = """\n.. rst-class:: sphx-glr-signature - - `Generated by Sphinx-Gallery `_\n""" - - -def codestr2rst(codestr, lang='python'): - """Return reStructuredText code block from code string""" - code_directive = "\n.. code-block:: {0}\n\n".format(lang) - indented_block = indent(codestr, ' ' * 4) - return code_directive + indented_block - - -def extract_thumbnail_number(text): - """ Pull out the thumbnail image number specified in the docstring. """ - - # check whether the user has specified a specific thumbnail image - pattr = re.compile( - r"^\s*#\s*sphinx_gallery_thumbnail_number\s*=\s*([0-9]+)\s*$", - flags=re.MULTILINE) - match = pattr.search(text) - - if match is None: - # by default, use the first figure created - thumbnail_number = 1 - else: - thumbnail_number = int(match.groups()[0]) - - return thumbnail_number - - -def extract_intro(filename): - """ Extract the first paragraph of module-level docstring. max:95 char""" - - docstring, _ = get_docstring_and_rest(filename) - - # lstrip is just in case docstring has a '\n\n' at the beginning - paragraphs = docstring.lstrip().split('\n\n') - if len(paragraphs) > 1: - first_paragraph = re.sub('\n', ' ', paragraphs[1]) - first_paragraph = (first_paragraph[:95] + '...' - if len(first_paragraph) > 95 else first_paragraph) - else: - raise ValueError( - "Example docstring should have a header for the example title " - "and at least a paragraph explaining what the example is about. " - "Please check the example file:\n {}\n".format(filename)) - - return first_paragraph - - -def get_md5sum(src_file): - """Returns md5sum of file""" - - with open(src_file, 'rb') as src_data: - src_content = src_data.read() - - src_md5 = hashlib.md5(src_content).hexdigest() - return src_md5 - - -def md5sum_is_current(src_file): - """Checks whether src_file has the same md5 hash as the one on disk""" - - src_md5 = get_md5sum(src_file) - - src_md5_file = src_file + '.md5' - if os.path.exists(src_md5_file): - with open(src_md5_file, 'r') as file_checksum: - ref_md5 = file_checksum.read() - - return src_md5 == ref_md5 - - return False - - -def save_figures(image_path, fig_count, gallery_conf): - """Save all open matplotlib figures of the example code-block - - Parameters - ---------- - image_path : str - Path where plots are saved (format string which accepts figure number) - fig_count : int - Previous figure number count. Figure number add from this number - gallery_conf : dict - Contains the configuration of Sphinx-Gallery - - Returns - ------- - images_rst : str - rst code to embed the images in the document - fig_num : int - number of figures saved - """ - figure_list = [] - - for fig_num in plt.get_fignums(): - # 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) - kwargs = {} - to_rgba = matplotlib.colors.colorConverter.to_rgba - for attr in ['facecolor', 'edgecolor']: - fig_attr = getattr(fig, 'get_' + attr)() - default_attr = matplotlib.rcParams['figure.' + attr] - if to_rgba(fig_attr) != to_rgba(default_attr): - kwargs[attr] = fig_attr - - current_fig = image_path.format(fig_count + fig_num) - fig.savefig(current_fig, **kwargs) - figure_list.append(current_fig) - - if gallery_conf.get('find_mayavi_figures', False): - from mayavi import mlab - e = mlab.get_engine() - last_matplotlib_fig_num = fig_count + len(figure_list) - total_fig_num = last_matplotlib_fig_num + len(e.scenes) - mayavi_fig_nums = range(last_matplotlib_fig_num + 1, total_fig_num + 1) - - for scene, mayavi_fig_num in zip(e.scenes, mayavi_fig_nums): - current_fig = image_path.format(mayavi_fig_num) - mlab.savefig(current_fig, figure=scene) - # make sure the image is not too large - scale_image(current_fig, current_fig, 850, 999) - figure_list.append(current_fig) - mlab.close(all=True) - - return figure_rst(figure_list, gallery_conf['src_dir']) - - -def figure_rst(figure_list, sources_dir): - """Given a list of paths to figures generate the corresponding rst - - Depending on whether we have one or more figures, we use a - single rst call to 'image' or a horizontal list. - - Parameters - ---------- - figure_list : list of str - Strings are the figures' absolute paths - sources_dir : str - absolute path of Sphinx documentation sources - - Returns - ------- - images_rst : str - rst code to embed the images in the document - fig_num : int - number of figures saved - """ - - figure_paths = [os.path.relpath(figure_path, sources_dir) - .replace(os.sep, '/').lstrip('/') - for figure_path in figure_list] - images_rst = "" - if len(figure_paths) == 1: - figure_name = figure_paths[0] - images_rst = SINGLE_IMAGE % figure_name - elif len(figure_paths) > 1: - images_rst = HLIST_HEADER - for figure_name in figure_paths: - images_rst += HLIST_IMAGE_TEMPLATE % figure_name - - return images_rst, len(figure_list) - - -def scale_image(in_fname, out_fname, max_width, max_height): - """Scales an image with the same aspect ratio centered in an - image with a given max_width and max_height - if in_fname == out_fname the image can only be scaled down - """ - # local import to avoid testing dependency on PIL: - try: - from PIL import Image - except ImportError: - import Image - img = Image.open(in_fname) - width_in, height_in = img.size - scale_w = max_width / float(width_in) - scale_h = max_height / float(height_in) - - if height_in * scale_w <= max_height: - scale = scale_w - else: - scale = scale_h - - if scale >= 1.0 and in_fname == out_fname: - return - - width_sc = int(round(scale * width_in)) - height_sc = int(round(scale * height_in)) - - # resize the image - img.thumbnail((width_sc, height_sc), Image.ANTIALIAS) - - # insert centered - thumb = Image.new('RGB', (max_width, max_height), (255, 255, 255)) - pos_insert = ((max_width - width_sc) // 2, (max_height - height_sc) // 2) - thumb.paste(img, pos_insert) - - thumb.save(out_fname) - # Use optipng to perform lossless compression on the resized image if - # software is installed - if os.environ.get('SKLEARN_DOC_OPTIPNG', False): - try: - subprocess.call(["optipng", "-quiet", "-o", "9", out_fname]) - except Exception: - warnings.warn('Install optipng to reduce the size of the \ - generated images') - - -def save_thumbnail(image_path_template, src_file, gallery_conf): - """Save the thumbnail image""" - # read specification of the figure to display as thumbnail from main text - _, content = get_docstring_and_rest(src_file) - thumbnail_number = extract_thumbnail_number(content) - thumbnail_image_path = image_path_template.format(thumbnail_number) - - thumb_dir = os.path.join(os.path.dirname(thumbnail_image_path), 'thumb') - if not os.path.exists(thumb_dir): - os.makedirs(thumb_dir) - - base_image_name = os.path.splitext(os.path.basename(src_file))[0] - thumb_file = os.path.join(thumb_dir, - 'sphx_glr_%s_thumb.png' % base_image_name) - - if src_file in gallery_conf['failing_examples']: - broken_img = os.path.join(glr_path_static(), 'broken_example.png') - scale_image(broken_img, thumb_file, 200, 140) - - elif os.path.exists(thumbnail_image_path): - scale_image(thumbnail_image_path, thumb_file, 400, 280) - - elif not os.path.exists(thumb_file): - # create something to replace the thumbnail - default_thumb_file = os.path.join(glr_path_static(), 'no_image.png') - default_thumb_file = gallery_conf.get("default_thumb_file", - default_thumb_file) - scale_image(default_thumb_file, thumb_file, 200, 140) - - -def generate_dir_rst(src_dir, target_dir, gallery_conf, seen_backrefs): - """Generate the gallery reStructuredText for an example directory""" - if not os.path.exists(os.path.join(src_dir, 'README.txt')): - print(80 * '_') - print('Example directory %s does not have a README.txt file' % - src_dir) - print('Skipping this directory') - print(80 * '_') - return "", [] # because string is an expected return type - - with open(os.path.join(src_dir, 'README.txt')) as fid: - fhindex = fid.read() - # Add empty lines to avoid bug in issue #165 - fhindex += "\n\n" - - if not os.path.exists(target_dir): - os.makedirs(target_dir) - sorted_listdir = [fname for fname in sorted(os.listdir(src_dir)) - if fname.endswith('.py')] - entries_text = [] - computation_times = [] - build_target_dir = os.path.relpath(target_dir, gallery_conf['src_dir']) - for fname in sorted_listdir: - amount_of_code, time_elapsed = \ - generate_file_rst(fname, target_dir, src_dir, gallery_conf) - computation_times.append((time_elapsed, fname)) - new_fname = os.path.join(src_dir, fname) - intro = extract_intro(new_fname) - this_entry = _thumbnail_div(build_target_dir, fname, intro) + """ - -.. toctree:: - :hidden: - - /%s\n""" % os.path.join(build_target_dir, fname[:-3]).replace(os.sep, '/') - entries_text.append((amount_of_code, this_entry)) - - if gallery_conf['backreferences_dir']: - write_backreferences(seen_backrefs, gallery_conf, - target_dir, fname, intro) - - # sort to have the smallest entries in the beginning - entries_text.sort() - - for _, entry_text in entries_text: - fhindex += entry_text - - # clear at the end of the section - fhindex += """.. raw:: html\n -
\n\n""" - - return fhindex, computation_times - - -def execute_code_block(code_block, example_globals, - block_vars, gallery_conf): - """Executes the code block of the example file""" - time_elapsed = 0 - stdout = '' - - # If example is not suitable to run, skip executing its blocks - if not block_vars['execute_script']: - return stdout, time_elapsed - - plt.close('all') - cwd = os.getcwd() - # Redirect output to stdout and - orig_stdout = sys.stdout - src_file = block_vars['src_file'] - - try: - # First cd in the original example dir, so that any file - # created by the example get created in this directory - os.chdir(os.path.dirname(src_file)) - my_buffer = MixedEncodingStringIO() - my_stdout = Tee(sys.stdout, my_buffer) - sys.stdout = my_stdout - - t_start = time() - # don't use unicode_literals at the top of this file or you get - # nasty errors here on Py2.7 - exec(code_block, example_globals) - time_elapsed = time() - t_start - - sys.stdout = orig_stdout - - my_stdout = my_buffer.getvalue().strip().expandtabs() - # raise RuntimeError - if my_stdout: - stdout = CODE_OUTPUT.format(indent(my_stdout, u' ' * 4)) - os.chdir(cwd) - images_rst, fig_num = save_figures(block_vars['image_path'], - block_vars['fig_count'], gallery_conf) - - except Exception: - formatted_exception = traceback.format_exc() - - fail_example_warning = 80 * '_' + '\n' + \ - '%s failed to execute correctly:' % src_file + \ - formatted_exception + 80 * '_' + '\n' - warnings.warn(fail_example_warning) - - fig_num = 0 - images_rst = codestr2rst(formatted_exception, lang='pytb') - - # Breaks build on first example error - # XXX This check can break during testing e.g. if you uncomment the - # `raise RuntimeError` by the `my_stdout` call, maybe use `.get()`? - if gallery_conf['abort_on_example_error']: - raise - # Stores failing file - gallery_conf['failing_examples'][src_file] = formatted_exception - block_vars['execute_script'] = False - - finally: - os.chdir(cwd) - sys.stdout = orig_stdout - - code_output = u"\n{0}\n\n{1}\n\n".format(images_rst, stdout) - block_vars['fig_count'] += fig_num - - return code_output, time_elapsed - - -def clean_modules(): - """Remove "unload" seaborn from the name space - - After a script is executed it can load a variety of setting that one - does not want to influence in other examples in the gallery.""" - - # Horrible code to 'unload' seaborn, so that it resets - # its default when is load - # Python does not support unloading of modules - # https://bugs.python.org/issue9072 - for module in list(sys.modules.keys()): - if 'seaborn' in module: - del sys.modules[module] - - # Reset Matplotlib to default - plt.rcdefaults() - - -def generate_file_rst(fname, target_dir, src_dir, gallery_conf): - """Generate the rst file for a given example. - - Returns - ------- - amount_of_code : int - character count of the corresponding python script in file - time_elapsed : float - seconds required to run the script - """ - - src_file = os.path.normpath(os.path.join(src_dir, fname)) - example_file = os.path.join(target_dir, fname) - shutil.copyfile(src_file, example_file) - script_blocks = split_code_and_text_blocks(src_file) - amount_of_code = sum([len(bcontent) - for blabel, bcontent in script_blocks - if blabel == 'code']) - - if md5sum_is_current(example_file): - return amount_of_code, 0 - - image_dir = os.path.join(target_dir, 'images') - if not os.path.exists(image_dir): - os.makedirs(image_dir) - - base_image_name = os.path.splitext(fname)[0] - image_fname = 'sphx_glr_' + base_image_name + '_{0:03}.png' - build_image_dir = os.path.relpath(image_dir, gallery_conf['src_dir']) - image_path_template = os.path.join(image_dir, image_fname) - - ref_fname = os.path.relpath(example_file, gallery_conf['src_dir']) - ref_fname = ref_fname.replace(os.path.sep, '_') - example_rst = """\n\n.. _sphx_glr_{0}:\n\n""".format(ref_fname) - - filename_pattern = gallery_conf.get('filename_pattern') - execute_script = re.search(filename_pattern, src_file) and gallery_conf[ - 'plot_gallery'] - example_globals = { - # A lot of examples contains 'print(__doc__)' for example in - # scikit-learn so that running the example prints some useful - # information. Because the docstring has been separated from - # the code blocks in sphinx-gallery, __doc__ is actually - # __builtin__.__doc__ in the execution context and we do not - # want to print it - '__doc__': '', - # Examples may contain if __name__ == '__main__' guards - # for in example scikit-learn if the example uses multiprocessing - '__name__': '__main__', - # Don't ever support __file__: Issues #166 #212 - } - - # A simple example has two blocks: one for the - # example introduction/explanation and one for the code - is_example_notebook_like = len(script_blocks) > 2 - time_elapsed = 0 - block_vars = {'execute_script': execute_script, 'fig_count': 0, - 'image_path': image_path_template, 'src_file': src_file} - if block_vars['execute_script']: - print('Executing file %s' % src_file) - for blabel, bcontent in script_blocks: - if blabel == 'code': - code_output, rtime = execute_code_block(bcontent, - example_globals, - block_vars, - gallery_conf) - - time_elapsed += rtime - - if is_example_notebook_like: - example_rst += codestr2rst(bcontent) + '\n' - example_rst += code_output - else: - example_rst += code_output - if 'sphx-glr-script-out' in code_output: - # Add some vertical space after output - example_rst += "\n\n|\n\n" - example_rst += codestr2rst(bcontent) + '\n' - - else: - example_rst += bcontent + '\n\n' - - clean_modules() - - # Writes md5 checksum if example has build correctly - # not failed and was initially meant to run(no-plot shall not cache md5sum) - if block_vars['execute_script']: - with open(example_file + '.md5', 'w') as file_checksum: - file_checksum.write(get_md5sum(example_file)) - - save_thumbnail(image_path_template, src_file, gallery_conf) - - time_m, time_s = divmod(time_elapsed, 60) - example_nb = jupyter_notebook(script_blocks) - save_notebook(example_nb, example_file.replace('.py', '.ipynb')) - with codecs.open(os.path.join(target_dir, base_image_name + '.rst'), - mode='w', encoding='utf-8') as f: - example_rst += "**Total running time of the script:**" \ - " ({0: .0f} minutes {1: .3f} seconds)\n\n".format( - time_m, time_s) - example_rst += CODE_DOWNLOAD.format(fname, - fname.replace('.py', '.ipynb')) - example_rst += SPHX_GLR_SIG - f.write(example_rst) - - if block_vars['execute_script']: - print("{0} ran in : {1:.2g} seconds\n".format(src_file, time_elapsed)) - - return amount_of_code, time_elapsed diff --git a/doc/sphinxext/sphinx_gallery/notebook.py b/doc/sphinxext/sphinx_gallery/notebook.py deleted file mode 100644 index a0cfdbd7881d6..0000000000000 --- a/doc/sphinxext/sphinx_gallery/notebook.py +++ /dev/null @@ -1,193 +0,0 @@ -# -*- coding: utf-8 -*- -r""" -Parser for Jupyter notebooks -============================ - -Class that holds the Jupyter notebook information - -""" -# Author: Óscar Nájera -# License: 3-clause BSD - -from __future__ import division, absolute_import, print_function -from functools import partial -import argparse -import json -import re -import sys -from .py_source_parser import split_code_and_text_blocks - - -def jupyter_notebook_skeleton(): - """Returns a dictionary with the elements of a Jupyter notebook""" - py_version = sys.version_info - notebook_skeleton = { - "cells": [], - "metadata": { - "kernelspec": { - "display_name": "Python " + str(py_version[0]), - "language": "python", - "name": "python" + str(py_version[0]) - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": py_version[0] - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython" + str(py_version[0]), - "version": '{0}.{1}.{2}'.format(*sys.version_info[:3]) - } - }, - "nbformat": 4, - "nbformat_minor": 0 - } - return notebook_skeleton - - -def directive_fun(match, directive): - """Helper to fill in directives""" - directive_to_alert = dict(note="info", warning="danger") - return ('

{1}

{2}

' - .format(directive_to_alert[directive], directive.capitalize(), - match.group(1).strip())) - - -def rst2md(text): - """Converts the RST text from the examples docstrigs and comments - into markdown text for the Jupyter notebooks""" - - top_heading = re.compile(r'^=+$\s^([\w\s-]+)^=+$', flags=re.M) - text = re.sub(top_heading, r'# \1', text) - - math_eq = re.compile(r'^\.\. math::((?:.+)?(?:\n+^ .+)*)', flags=re.M) - text = re.sub(math_eq, - lambda match: r'\begin{{align}}{0}\end{{align}}'.format( - match.group(1).strip()), - text) - inline_math = re.compile(r':math:`(.+?)`', re.DOTALL) - text = re.sub(inline_math, r'$\1$', text) - - directives = ('warning', 'note') - for directive in directives: - directive_re = re.compile(r'^\.\. %s::((?:.+)?(?:\n+^ .+)*)' - % directive, flags=re.M) - text = re.sub(directive_re, - partial(directive_fun, directive=directive), text) - - links = re.compile(r'^ *\.\. _.*:.*$\n', flags=re.M) - text = re.sub(links, '', text) - - refs = re.compile(r':ref:`') - text = re.sub(refs, '`', text) - - contents = re.compile(r'^\s*\.\. contents::.*$(\n +:\S+: *$)*\n', - flags=re.M) - text = re.sub(contents, '', text) - - images = re.compile( - r'^\.\. image::(.*$)(?:\n *:alt:(.*$)\n)?(?: +:\S+:.*$\n)*', - flags=re.M) - text = re.sub( - images, lambda match: '![{1}]({0})\n'.format( - match.group(1).strip(), (match.group(2) or '').strip()), text) - - return text - - -def jupyter_notebook(script_blocks): - """Generate a Jupyter notebook file cell-by-cell - - Parameters - ---------- - script_blocks: list - script execution cells - """ - - work_notebook = jupyter_notebook_skeleton() - add_code_cell(work_notebook, "%matplotlib inline") - fill_notebook(work_notebook, script_blocks) - - return work_notebook - - -def add_code_cell(work_notebook, code): - """Add a code cell to the notebook - - Parameters - ---------- - code : str - Cell content - """ - - code_cell = { - "cell_type": "code", - "execution_count": None, - "metadata": {"collapsed": False}, - "outputs": [], - "source": [code.strip()] - } - work_notebook["cells"].append(code_cell) - - -def add_markdown_cell(work_notebook, text): - """Add a markdown cell to the notebook - - Parameters - ---------- - code : str - Cell content - """ - markdown_cell = { - "cell_type": "markdown", - "metadata": {}, - "source": [rst2md(text)] - } - work_notebook["cells"].append(markdown_cell) - - -def fill_notebook(work_notebook, script_blocks): - """Writes the Jupyter notebook cells - - Parameters - ---------- - script_blocks : list of tuples - """ - - for blabel, bcontent in script_blocks: - if blabel == 'code': - add_code_cell(work_notebook, bcontent) - else: - add_markdown_cell(work_notebook, bcontent + '\n') - - -def save_notebook(work_notebook, write_file): - """Saves the Jupyter work_notebook to write_file""" - with open(write_file, 'w') as out_nb: - json.dump(work_notebook, out_nb, indent=2) - - -############################################################################### -# Notebook shell utility - -def python_to_jupyter_cli(args=None, namespace=None): - """Exposes the jupyter notebook renderer to the command line - - Takes the same arguments as ArgumentParser.parse_args - """ - parser = argparse.ArgumentParser( - description='Sphinx-Gallery Notebook converter') - parser.add_argument('python_src_file', nargs='+', - help='Input Python file script to convert. ' - 'Supports multiple files and shell wildcards' - ' (e.g. *.py)') - args = parser.parse_args(args, namespace) - - for src_file in args.python_src_file: - blocks = split_code_and_text_blocks(src_file) - print('Converting {0}'.format(src_file)) - example_nb = jupyter_notebook(blocks) - save_notebook(example_nb, src_file.replace('.py', '.ipynb')) diff --git a/doc/sphinxext/sphinx_gallery/py_source_parser.py b/doc/sphinxext/sphinx_gallery/py_source_parser.py deleted file mode 100644 index d397087f99fbd..0000000000000 --- a/doc/sphinxext/sphinx_gallery/py_source_parser.py +++ /dev/null @@ -1,99 +0,0 @@ -# -*- coding: utf-8 -*- -r""" -Parser for python source files -============================== -""" -# Created Sun Nov 27 14:03:07 2016 -# Author: Óscar Nájera - -from __future__ import division, absolute_import, print_function -import ast -import re -from textwrap import dedent - -SYNTAX_ERROR_DOCSTRING = """ -SyntaxError -=========== - -Example script with invalid Python syntax -""" - - -def get_docstring_and_rest(filename): - """Separate `filename` content between docstring and the rest - - Strongly inspired from ast.get_docstring. - - Returns - ------- - docstring: str - docstring of `filename` - rest: str - `filename` content without the docstring - """ - # can't use codecs.open(filename, 'r', 'utf-8') here b/c ast doesn't - # seem to work with unicode strings in Python2.7 - # "SyntaxError: encoding declaration in Unicode string" - with open(filename, 'rb') as fid: - content = fid.read() - # change from Windows format to UNIX for uniformity - content = content.replace(b'\r\n', b'\n') - - try: - node = ast.parse(content) - except SyntaxError: - return SYNTAX_ERROR_DOCSTRING, content.decode('utf-8') - - if not isinstance(node, ast.Module): - raise TypeError("This function only supports modules. " - "You provided {0}".format(node.__class__.__name__)) - if node.body and isinstance(node.body[0], ast.Expr) and \ - isinstance(node.body[0].value, ast.Str): - docstring_node = node.body[0] - docstring = docstring_node.value.s - if hasattr(docstring, 'decode'): # python2.7 - docstring = docstring.decode('utf-8') - # This get the content of the file after the docstring last line - # Note: 'maxsplit' argument is not a keyword argument in python2 - rest = content.decode('utf-8').split('\n', docstring_node.lineno)[-1] - return docstring, rest - else: - raise ValueError(('Could not find docstring in file "{0}". ' - 'A docstring is required by sphinx-gallery') - .format(filename)) - - -def split_code_and_text_blocks(source_file): - """Return list with source file separated into code and text blocks. - - Returns - ------- - blocks : list of (label, content) - List where each element is a tuple with the label ('text' or 'code'), - and content string of block. - """ - docstring, rest_of_content = get_docstring_and_rest(source_file) - blocks = [('text', docstring)] - - pattern = re.compile( - r'(?P^#{20,}.*)\s(?P(?:^#.*\s)*)', - flags=re.M) - - pos_so_far = 0 - for match in re.finditer(pattern, rest_of_content): - match_start_pos, match_end_pos = match.span() - code_block_content = rest_of_content[pos_so_far:match_start_pos] - text_content = match.group('text_content') - sub_pat = re.compile('^#', flags=re.M) - text_block_content = dedent(re.sub(sub_pat, '', text_content)).lstrip() - if code_block_content.strip(): - blocks.append(('code', code_block_content)) - if text_block_content.strip(): - blocks.append(('text', text_block_content)) - pos_so_far = match_end_pos - - remaining_content = rest_of_content[pos_so_far:] - if remaining_content.strip(): - blocks.append(('code', remaining_content)) - - return blocks