From 786cde6bdb4934e2afda3f4e8846553a21b2cd5a Mon Sep 17 00:00:00 2001 From: Peter Zahemszky <63169891+peterzahemszky@users.noreply.github.com> Date: Tue, 23 Nov 2021 16:27:37 +0000 Subject: [PATCH 01/12] Remove the `traitlet-documenter` dependency, add `autodoc-traits` --- doc-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc-requirements.txt b/doc-requirements.txt index 7e7c36ef..b3d8487e 100644 --- a/doc-requirements.txt +++ b/doc-requirements.txt @@ -3,4 +3,4 @@ sphinx==1.4.4 # See https://github.com/sphinx-doc/sphinx/issues/9727 docutils<0.18 astor==0.8.1 -git+https://github.com/simphony/traitlet-documenter.git@v0.1.0#egg=traitlet_documenter +autodoc-traits From a207e7be1e9d3543f94d9bb4c249990fe3d404d4 Mon Sep 17 00:00:00 2001 From: Peter Zahemszky <63169891+peterzahemszky@users.noreply.github.com> Date: Tue, 23 Nov 2021 16:32:27 +0000 Subject: [PATCH 02/12] Update configuration of the Sphinx documentation --- doc/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index a6f3d6c1..14617509 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -43,7 +43,7 @@ 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode', - 'traitlet_documenter', + 'autodoc_traits', # patched autosummary for issue # https://github.com/sphinx-doc/sphinx/issues/1061 '_extensions' From 7f62b5815291855230c371f6faf0f7268f56cc2f Mon Sep 17 00:00:00 2001 From: Peter Zahemszky <63169891+peterzahemszky@users.noreply.github.com> Date: Fri, 26 Nov 2021 17:57:23 +0000 Subject: [PATCH 03/12] Fix missing runtime dependencies --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 91ab2672..98ef3582 100644 --- a/setup.py +++ b/setup.py @@ -59,7 +59,9 @@ def run(self): if not on_rtd: import subprocess subprocess.check_call(['npm', 'run', 'build']) - super().run() + # Workaround for setuptools ignoring `install_requires` when `cmdclass` is overridden + # See https://stackoverflow.com/q/21915469 + super().do_egg_install() # main setup configuration class From 2398f3e6772e0eeee56ce0bd9cd1425aaa9e46e6 Mon Sep 17 00:00:00 2001 From: Peter Zahemszky <63169891+peterzahemszky@users.noreply.github.com> Date: Fri, 26 Nov 2021 17:58:23 +0000 Subject: [PATCH 04/12] Remove Sphinx version constraint to resolve issue --- doc-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc-requirements.txt b/doc-requirements.txt index b3d8487e..30f66043 100644 --- a/doc-requirements.txt +++ b/doc-requirements.txt @@ -1,4 +1,4 @@ -sphinx==1.4.4 +sphinx # docutils version 0.18 is known to cause issues with Sphinx # See https://github.com/sphinx-doc/sphinx/issues/9727 docutils<0.18 From d4d4d2a7812ef147da3094ebf35a71c238bdb2d9 Mon Sep 17 00:00:00 2001 From: Peter Zahemszky <63169891+peterzahemszky@users.noreply.github.com> Date: Fri, 26 Nov 2021 17:59:01 +0000 Subject: [PATCH 05/12] Resolve Sphinx and docutils issues --- doc/source/_extensions/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/source/_extensions/__init__.py b/doc/source/_extensions/__init__.py index 98cedd8d..ea0ea946 100644 --- a/doc/source/_extensions/__init__.py +++ b/doc/source/_extensions/__init__.py @@ -61,13 +61,12 @@ from types import ModuleType from six import text_type -from docutils.parsers.rst import directives +from docutils.parsers.rst import Directive, directives from docutils.statemachine import ViewList from docutils import nodes import sphinx from sphinx import addnodes -from sphinx.util.compat import Directive from sphinx.pycode import ModuleAnalyzer, PycodeError from sphinx.ext.autodoc import Options @@ -555,7 +554,7 @@ def process_generate_options(app): from .generate import generate_autosummary_docs - ext = app.config.source_suffix[0] + ext = list(app.config.source_suffix)[0] genfiles = [genfile + (not genfile.endswith(ext) and ext or '') for genfile in genfiles] From e66a1fa68ba161978fa077dc0ba9f82f191fc3f0 Mon Sep 17 00:00:00 2001 From: Peter Zahemszky <63169891+peterzahemszky@users.noreply.github.com> Date: Fri, 26 Nov 2021 18:20:13 +0000 Subject: [PATCH 06/12] Fix flake8 issue --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 98ef3582..20bfed62 100644 --- a/setup.py +++ b/setup.py @@ -59,8 +59,8 @@ def run(self): if not on_rtd: import subprocess subprocess.check_call(['npm', 'run', 'build']) - # Workaround for setuptools ignoring `install_requires` when `cmdclass` is overridden - # See https://stackoverflow.com/q/21915469 + # Workaround for setuptools ignoring `install_requires` when + # `cmdclass` is overridden. See https://stackoverflow.com/q/21915469 super().do_egg_install() From 99cd76f4c44e35ab6612f8f8f96b5634cd1ec7e9 Mon Sep 17 00:00:00 2001 From: Peter Zahemszky <63169891+peterzahemszky@users.noreply.github.com> Date: Fri, 26 Nov 2021 18:33:38 +0000 Subject: [PATCH 07/12] Fix `autodoc_member_order` in Sphinx config --- doc/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 14617509..f4153e9c 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -455,7 +455,7 @@ del _config -autodoc_member_order = 'source' +autodoc_member_order = 'bysource' autoclass_content = 'both' autodoc_default_flags = [ 'show-inheritance', 'members', 'undoc-members'] From 49e06a15d383f3401b1cf1bc0d9bd44bc5317f0b Mon Sep 17 00:00:00 2001 From: Peter Zahemszky <63169891+peterzahemszky@users.noreply.github.com> Date: Fri, 26 Nov 2021 19:30:04 +0000 Subject: [PATCH 08/12] Use app.add_autodocumenter to resolve Sphinx failure --- doc/source/_extensions/__init__.py | 15 ++++++++++++++- doc/source/_extensions/generate.py | 14 -------------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/doc/source/_extensions/__init__.py b/doc/source/_extensions/__init__.py index ea0ea946..5b76b929 100644 --- a/doc/source/_extensions/__init__.py +++ b/doc/source/_extensions/__init__.py @@ -68,7 +68,10 @@ import sphinx from sphinx import addnodes from sphinx.pycode import ModuleAnalyzer, PycodeError -from sphinx.ext.autodoc import Options +from sphinx.ext.autodoc import Options, \ + ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter, \ + FunctionDocumenter, MethodDocumenter, AttributeDocumenter, \ + InstanceAttributeDocumenter # -- autosummary_toc node ------------------------------------------------------ @@ -554,6 +557,16 @@ def process_generate_options(app): from .generate import generate_autosummary_docs + # Add documenters to AutoDirective registry + app.add_autodocumenter(ModuleDocumenter) + app.add_autodocumenter(ClassDocumenter) + app.add_autodocumenter(ExceptionDocumenter) + app.add_autodocumenter(DataDocumenter) + app.add_autodocumenter(FunctionDocumenter) + app.add_autodocumenter(MethodDocumenter) + app.add_autodocumenter(AttributeDocumenter) + app.add_autodocumenter(InstanceAttributeDocumenter) + ext = list(app.config.source_suffix)[0] genfiles = [genfile + (not genfile.endswith(ext) and ext or '') for genfile in genfiles] diff --git a/doc/source/_extensions/generate.py b/doc/source/_extensions/generate.py index ba511f0a..f1e596aa 100644 --- a/doc/source/_extensions/generate.py +++ b/doc/source/_extensions/generate.py @@ -35,20 +35,6 @@ from sphinx.util.osutil import ensuredir from sphinx.util.inspect import safe_getattr -# Add documenters to AutoDirective registry -from sphinx.ext.autodoc import add_documenter, \ - ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter, \ - FunctionDocumenter, MethodDocumenter, AttributeDocumenter, \ - InstanceAttributeDocumenter -add_documenter(ModuleDocumenter) -add_documenter(ClassDocumenter) -add_documenter(ExceptionDocumenter) -add_documenter(DataDocumenter) -add_documenter(FunctionDocumenter) -add_documenter(MethodDocumenter) -add_documenter(AttributeDocumenter) -add_documenter(InstanceAttributeDocumenter) - def main(argv=sys.argv): usage = """%prog [OPTIONS] SOURCEFILE ...""" From 4fd167bc7eebd98852f90894b97c1f59d438a559 Mon Sep 17 00:00:00 2001 From: Peter Zahemszky <63169891+peterzahemszky@users.noreply.github.com> Date: Fri, 3 Dec 2021 16:54:52 +0000 Subject: [PATCH 09/12] Remove duplicate documenter classes --- doc/source/_extensions/__init__.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/doc/source/_extensions/__init__.py b/doc/source/_extensions/__init__.py index 5b76b929..8740a929 100644 --- a/doc/source/_extensions/__init__.py +++ b/doc/source/_extensions/__init__.py @@ -558,13 +558,6 @@ def process_generate_options(app): from .generate import generate_autosummary_docs # Add documenters to AutoDirective registry - app.add_autodocumenter(ModuleDocumenter) - app.add_autodocumenter(ClassDocumenter) - app.add_autodocumenter(ExceptionDocumenter) - app.add_autodocumenter(DataDocumenter) - app.add_autodocumenter(FunctionDocumenter) - app.add_autodocumenter(MethodDocumenter) - app.add_autodocumenter(AttributeDocumenter) app.add_autodocumenter(InstanceAttributeDocumenter) ext = list(app.config.source_suffix)[0] From 9c64a75ffeaaf473b1c8f4dae9777e00a2bac492 Mon Sep 17 00:00:00 2001 From: Peter Zahemszky <63169891+peterzahemszky@users.noreply.github.com> Date: Fri, 3 Dec 2021 17:08:50 +0000 Subject: [PATCH 10/12] Remove deprecated methods --- doc/source/_extensions/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/source/_extensions/__init__.py b/doc/source/_extensions/__init__.py index 8740a929..43b2a8dc 100644 --- a/doc/source/_extensions/__init__.py +++ b/doc/source/_extensions/__init__.py @@ -565,8 +565,7 @@ def process_generate_options(app): for genfile in genfiles] generate_autosummary_docs(genfiles, builder=app.builder, - warn=app.warn, info=app.info, suffix=ext, - base_path=app.srcdir) + suffix=ext, base_path=app.srcdir) def setup(app): From 193eda3a749ed7a04b095b81703e069137fb43d3 Mon Sep 17 00:00:00 2001 From: Peter Zahemszky <63169891+peterzahemszky@users.noreply.github.com> Date: Wed, 15 Dec 2021 19:26:14 +0000 Subject: [PATCH 11/12] Clean up imports --- doc/source/_extensions/__init__.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/doc/source/_extensions/__init__.py b/doc/source/_extensions/__init__.py index 43b2a8dc..6999da10 100644 --- a/doc/source/_extensions/__init__.py +++ b/doc/source/_extensions/__init__.py @@ -68,10 +68,7 @@ import sphinx from sphinx import addnodes from sphinx.pycode import ModuleAnalyzer, PycodeError -from sphinx.ext.autodoc import Options, \ - ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter, \ - FunctionDocumenter, MethodDocumenter, AttributeDocumenter, \ - InstanceAttributeDocumenter +from sphinx.ext.autodoc import Options, InstanceAttributeDocumenter # -- autosummary_toc node ------------------------------------------------------ From 991d4abe6760f58fd1ae76f249916612dc311707 Mon Sep 17 00:00:00 2001 From: Peter Zahemszky <63169891+peterzahemszky@users.noreply.github.com> Date: Wed, 15 Dec 2021 23:24:21 +0000 Subject: [PATCH 12/12] Remove redundant code --- doc/source/_extensions/__init__.py | 588 ----------------------------- doc/source/_extensions/generate.py | 334 ---------------- doc/source/conf.py | 5 +- 3 files changed, 1 insertion(+), 926 deletions(-) delete mode 100644 doc/source/_extensions/__init__.py delete mode 100644 doc/source/_extensions/generate.py diff --git a/doc/source/_extensions/__init__.py b/doc/source/_extensions/__init__.py deleted file mode 100644 index 6999da10..00000000 --- a/doc/source/_extensions/__init__.py +++ /dev/null @@ -1,588 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sphinx.ext.autosummary - ~~~~~~~~~~~~~~~~~~~~~~ - - Sphinx extension that adds an autosummary:: directive, which can be - used to generate function/method/attribute/etc. summary lists, similar - to those output eg. by Epydoc and other API doc generation tools. - - An :autolink: role is also provided. - - autosummary directive - --------------------- - - The autosummary directive has the form:: - - .. autosummary:: - :nosignatures: - :toctree: generated/ - - module.function_1 - module.function_2 - ... - - and it generates an output table (containing signatures, optionally) - - ======================== ============================================= - module.function_1(args) Summary line from the docstring of function_1 - module.function_2(args) Summary line from the docstring - ... - ======================== ============================================= - - If the :toctree: option is specified, files matching the function names - are inserted to the toctree with the given prefix: - - generated/module.function_1 - generated/module.function_2 - ... - - Note: The file names contain the module:: or currentmodule:: prefixes. - - .. seealso:: autosummary_generate.py - - - autolink role - ------------- - - The autolink role functions as ``:obj:`` when the name referred can be - resolved to a Python object, and otherwise it becomes simple emphasis. - This can be used as the default role to make links 'smart'. - - :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" - -import os -import re -import sys -import inspect -import posixpath -from types import ModuleType - -from six import text_type -from docutils.parsers.rst import Directive, directives -from docutils.statemachine import ViewList -from docutils import nodes - -import sphinx -from sphinx import addnodes -from sphinx.pycode import ModuleAnalyzer, PycodeError -from sphinx.ext.autodoc import Options, InstanceAttributeDocumenter - - -# -- autosummary_toc node ------------------------------------------------------ - -class autosummary_toc(nodes.comment): - pass - - -def process_autosummary_toc(app, doctree): - """Insert items described in autosummary:: to the TOC tree, but do - not generate the toctree:: list. - """ - env = app.builder.env - crawled = {} - - def crawl_toc(node, depth=1): - crawled[node] = True - for j, subnode in enumerate(node): - try: - if (isinstance(subnode, autosummary_toc) and - isinstance(subnode[0], addnodes.toctree)): - env.note_toctree(env.docname, subnode[0]) - continue - except IndexError: - continue - if not isinstance(subnode, nodes.section): - continue - if subnode not in crawled: - crawl_toc(subnode, depth+1) - crawl_toc(doctree) - - -def autosummary_toc_visit_html(self, node): - """Hide autosummary toctree list in HTML output.""" - raise nodes.SkipNode - - -def autosummary_noop(self, node): - pass - - -# -- autosummary_table node ---------------------------------------------------- - -class autosummary_table(nodes.comment): - pass - - -def autosummary_table_visit_html(self, node): - """Make the first column of the table non-breaking.""" - try: - tbody = node[0][0][-1] - for row in tbody: - col1_entry = row[0] - par = col1_entry[0] - for j, subnode in enumerate(list(par)): - if isinstance(subnode, nodes.Text): - new_text = text_type(subnode.astext()) - new_text = new_text.replace(u" ", u"\u00a0") - par[j] = nodes.Text(new_text) - except IndexError: - pass - - -# -- autodoc integration ------------------------------------------------------- - -class FakeDirective: - env = {} - genopt = Options() - - -def get_documenter(obj, parent): - """Get an autodoc.Documenter class suitable for documenting the given - object. - - *obj* is the Python object to be documented, and *parent* is an - another Python object (e.g. a module or a class) to which *obj* - belongs to. - """ - from sphinx.ext.autodoc import AutoDirective, DataDocumenter, \ - ModuleDocumenter - - if inspect.ismodule(obj): - # ModuleDocumenter.can_document_member always returns False - return ModuleDocumenter - - # Construct a fake documenter for *parent* - if parent is not None: - parent_doc_cls = get_documenter(parent, None) - else: - parent_doc_cls = ModuleDocumenter - - if hasattr(parent, '__name__'): - parent_doc = parent_doc_cls(FakeDirective(), parent.__name__) - else: - parent_doc = parent_doc_cls(FakeDirective(), "") - - # Get the corrent documenter class for *obj* - classes = [cls for cls in AutoDirective._registry.values() - if cls.can_document_member(obj, '', False, parent_doc)] - if classes: - classes.sort(key=lambda cls: cls.priority) - return classes[-1] - else: - return DataDocumenter - - -# -- .. autosummary:: ---------------------------------------------------------- - -class Autosummary(Directive): - """ - Pretty table containing short signatures and summaries of functions etc. - - autosummary can also optionally generate a hidden toctree:: node. - """ - - required_arguments = 0 - optional_arguments = 0 - final_argument_whitespace = False - has_content = True - option_spec = { - 'toctree': directives.unchanged, - 'nosignatures': directives.flag, - 'template': directives.unchanged, - } - - def warn(self, msg): - self.warnings.append(self.state.document.reporter.warning( - msg, line=self.lineno)) - - def run(self): - self.env = env = self.state.document.settings.env - self.genopt = Options() - self.warnings = [] - self.result = ViewList() - - names = [x.strip().split()[0] for x in self.content - if x.strip() and re.search(r'^[~a-zA-Z_]', x.strip()[0])] - items = self.get_items(names) - nodes = self.get_table(items) - - if 'toctree' in self.options: - dirname = posixpath.dirname(env.docname) - - tree_prefix = self.options['toctree'].strip() - docnames = [] - for name, sig, summary, real_name in items: - docname = posixpath.join(tree_prefix, real_name) - docname = posixpath.normpath(posixpath.join(dirname, docname)) - if docname not in env.found_docs: - self.warn('toctree references unknown document %r' - % docname) - docnames.append(docname) - - tocnode = addnodes.toctree() - tocnode['includefiles'] = docnames - tocnode['entries'] = [(None, docn) for docn in docnames] - tocnode['maxdepth'] = -1 - tocnode['glob'] = None - - tocnode = autosummary_toc('', '', tocnode) - nodes.append(tocnode) - - return self.warnings + nodes - - def get_items(self, names): - """Try to import the given names, and return a list of - ``[(name, signature, summary_string, real_name), ...]``. - """ - env = self.state.document.settings.env - - prefixes = get_import_prefixes_from_env(env) - - items = [] - - max_item_chars = 50 - - for name in names: - display_name = name - if name.startswith('~'): - name = name[1:] - display_name = name.split('.')[-1] - - try: - real_name, obj, parent, modname = import_by_name(name, prefixes=prefixes) - except ImportError: - self.warn('failed to import %s' % name) - items.append((name, '', '', name)) - continue - - self.result = ViewList() # initialize for each documenter - full_name = real_name - if not isinstance(obj, ModuleType): - # give explicitly separated module name, so that members - # of inner classes can be documented - full_name = modname + '::' + full_name[len(modname)+1:] - # NB. using full_name here is important, since Documenters - # handle module prefixes slightly differently - documenter = get_documenter(obj, parent)(self, full_name) - if not documenter.parse_name(): - self.warn('failed to parse name %s' % real_name) - items.append((display_name, '', '', real_name)) - continue - if not documenter.import_object(): - self.warn('failed to import object %s' % real_name) - items.append((display_name, '', '', real_name)) - continue - if documenter.options.members and not documenter.check_module(): - continue - - # try to also get a source code analyzer for attribute docs - try: - documenter.analyzer = ModuleAnalyzer.for_module( - documenter.get_real_modname()) - # parse right now, to get PycodeErrors on parsing (results will - # be cached anyway) - documenter.analyzer.find_attr_docs() - except PycodeError as err: - documenter.env.app.debug( - '[autodoc] module analyzer failed: %s', err) - # no source file -- e.g. for builtin and C modules - documenter.analyzer = None - - # -- Grab the signature - - sig = documenter.format_signature() - if not sig: - sig = '' - else: - max_chars = max(10, max_item_chars - len(display_name)) - sig = mangle_signature(sig, max_chars=max_chars) - sig = sig.replace('*', r'\*') - - # -- Grab the summary - - documenter.add_content(None) - doc = list(documenter.process_doc([self.result.data])) - - while doc and not doc[0].strip(): - doc.pop(0) - - # If there's a blank line, then we can assume the first sentence / - # paragraph has ended, so anything after shouldn't be part of the - # summary - for i, piece in enumerate(doc): - if not piece.strip(): - doc = doc[:i] - break - - # Try to find the "first sentence", which may span multiple lines - m = re.search(r"^([A-Z].*?\.)(?:\s|$)", " ".join(doc).strip()) - if m: - summary = m.group(1).strip() - elif doc: - summary = doc[0].strip() - else: - summary = '' - - items.append((display_name, sig, summary, real_name)) - - return items - - def get_table(self, items): - """Generate a proper list of table nodes for autosummary:: directive. - - *items* is a list produced by :meth:`get_items`. - """ - table_spec = addnodes.tabular_col_spec() - table_spec['spec'] = 'p{0.5\linewidth}p{0.5\linewidth}' - - table = autosummary_table('') - real_table = nodes.table('', classes=['longtable']) - table.append(real_table) - group = nodes.tgroup('', cols=2) - real_table.append(group) - group.append(nodes.colspec('', colwidth=10)) - group.append(nodes.colspec('', colwidth=90)) - body = nodes.tbody('') - group.append(body) - - def append_row(*column_texts): - row = nodes.row('') - for text in column_texts: - node = nodes.paragraph('') - vl = ViewList() - vl.append(text, '') - self.state.nested_parse(vl, 0, node) - try: - if isinstance(node[0], nodes.paragraph): - node = node[0] - except IndexError: - pass - row.append(nodes.entry('', node)) - body.append(row) - - for name, sig, summary, real_name in items: - qualifier = 'obj' - if 'nosignatures' not in self.options: - col1 = ':%s:`%s <%s>`\ %s' % (qualifier, name, real_name, sig) - else: - col1 = ':%s:`%s <%s>`' % (qualifier, name, real_name) - col2 = summary - append_row(col1, col2) - - return [table_spec, table] - - -def mangle_signature(sig, max_chars=30): - """Reformat a function signature to a more compact form.""" - s = re.sub(r"^\((.*)\)$", r"\1", sig).strip() - - # Strip strings (which can contain things that confuse the code below) - s = re.sub(r"\\\\", "", s) - s = re.sub(r"\\'", "", s) - s = re.sub(r"'[^']*'", "", s) - - # Parse the signature to arguments + options - args = [] - opts = [] - - opt_re = re.compile(r"^(.*, |)([a-zA-Z0-9_*]+)=") - while s: - m = opt_re.search(s) - if not m: - # The rest are arguments - args = s.split(', ') - break - - opts.insert(0, m.group(2)) - s = m.group(1)[:-2] - - # Produce a more compact signature - sig = limited_join(", ", args, max_chars=max_chars-2) - if opts: - if not sig: - sig = "[%s]" % limited_join(", ", opts, max_chars=max_chars-4) - elif len(sig) < max_chars - 4 - 2 - 3: - sig += "[, %s]" % limited_join(", ", opts, - max_chars=max_chars-len(sig)-4-2) - - return u"(%s)" % sig - - -def limited_join(sep, items, max_chars=30, overflow_marker="..."): - """Join a number of strings to one, limiting the length to *max_chars*. - - If the string overflows this limit, replace the last fitting item by - *overflow_marker*. - - Returns: joined_string - """ - full_str = sep.join(items) - if len(full_str) < max_chars: - return full_str - - n_chars = 0 - n_items = 0 - for j, item in enumerate(items): - n_chars += len(item) + len(sep) - if n_chars < max_chars - len(overflow_marker): - n_items += 1 - else: - break - - return sep.join(list(items[:n_items]) + [overflow_marker]) - - -# -- Importing items ----------------------------------------------------------- - -def get_import_prefixes_from_env(env): - """ - Obtain current Python import prefixes (for `import_by_name`) - from ``document.env`` - """ - prefixes = [None] - - currmodule = env.ref_context.get('py:module') - if currmodule: - prefixes.insert(0, currmodule) - - currclass = env.ref_context.get('py:class') - if currclass: - if currmodule: - prefixes.insert(0, currmodule + "." + currclass) - else: - prefixes.insert(0, currclass) - - return prefixes - - -def import_by_name(name, prefixes=[None]): - """Import a Python object that has the given *name*, under one of the - *prefixes*. The first name that succeeds is used. - """ - tried = [] - for prefix in prefixes: - try: - if prefix: - prefixed_name = '.'.join([prefix, name]) - else: - prefixed_name = name - obj, parent, modname = _import_by_name(prefixed_name) - return prefixed_name, obj, parent, modname - except ImportError: - tried.append(prefixed_name) - raise ImportError('no module named %s' % ' or '.join(tried)) - - -def _import_by_name(name): - """Import a Python object given its full name.""" - try: - name_parts = name.split('.') - - # try first interpret `name` as MODNAME.OBJ - modname = '.'.join(name_parts[:-1]) - if modname: - try: - __import__(modname) - mod = sys.modules[modname] - return getattr(mod, name_parts[-1]), mod, modname - except (ImportError, IndexError, AttributeError): - pass - - # ... then as MODNAME, MODNAME.OBJ1, MODNAME.OBJ1.OBJ2, ... - last_j = 0 - modname = None - for j in reversed(range(1, len(name_parts)+1)): - last_j = j - modname = '.'.join(name_parts[:j]) - try: - __import__(modname) - except ImportError: - continue - if modname in sys.modules: - break - - if last_j < len(name_parts): - parent = None - obj = sys.modules[modname] - for obj_name in name_parts[last_j:]: - parent = obj - obj = getattr(obj, obj_name) - return obj, parent, modname - else: - return sys.modules[modname], None, modname - except (ValueError, ImportError, AttributeError, KeyError) as e: - raise ImportError(*e.args) - - -# -- :autolink: (smart default role) ------------------------------------------- - -def autolink_role(typ, rawtext, etext, lineno, inliner, - options={}, content=[]): - """Smart linking role. - - Expands to ':obj:`text`' if `text` is an object that can be imported; - otherwise expands to '*text*'. - """ - env = inliner.document.settings.env - r = env.get_domain('py').role('obj')( - 'obj', rawtext, etext, lineno, inliner, options, content) - pnode = r[0][0] - - prefixes = get_import_prefixes_from_env(env) - try: - name, obj, parent, modname = import_by_name(pnode['reftarget'], prefixes) - except ImportError: - content = pnode[0] - r[0][0] = nodes.emphasis(rawtext, content[0].astext(), - classes=content['classes']) - return r - - -def process_generate_options(app): - genfiles = app.config.autosummary_generate - - if genfiles and not hasattr(genfiles, '__len__'): - env = app.builder.env - genfiles = [env.doc2path(x, base=None) for x in env.found_docs - if os.path.isfile(env.doc2path(x))] - - if not genfiles: - return - - from .generate import generate_autosummary_docs - - # Add documenters to AutoDirective registry - app.add_autodocumenter(InstanceAttributeDocumenter) - - ext = list(app.config.source_suffix)[0] - genfiles = [genfile + (not genfile.endswith(ext) and ext or '') - for genfile in genfiles] - - generate_autosummary_docs(genfiles, builder=app.builder, - suffix=ext, base_path=app.srcdir) - - -def setup(app): - # I need autodoc - app.setup_extension('sphinx.ext.autodoc') - app.add_node(autosummary_toc, - html=(autosummary_toc_visit_html, autosummary_noop), - latex=(autosummary_noop, autosummary_noop), - text=(autosummary_noop, autosummary_noop), - man=(autosummary_noop, autosummary_noop), - texinfo=(autosummary_noop, autosummary_noop)) - app.add_node(autosummary_table, - html=(autosummary_table_visit_html, autosummary_noop), - latex=(autosummary_noop, autosummary_noop), - text=(autosummary_noop, autosummary_noop), - man=(autosummary_noop, autosummary_noop), - texinfo=(autosummary_noop, autosummary_noop)) - app.add_directive('autosummary', Autosummary) - app.add_role('autolink', autolink_role) - app.connect('doctree-read', process_autosummary_toc) - app.connect('builder-inited', process_generate_options) - app.add_config_value('autosummary_generate', [], True, [bool]) - return {'version': sphinx.__display_version__, 'parallel_read_safe': True} diff --git a/doc/source/_extensions/generate.py b/doc/source/_extensions/generate.py deleted file mode 100644 index f1e596aa..00000000 --- a/doc/source/_extensions/generate.py +++ /dev/null @@ -1,334 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sphinx.ext.autosummary.generate - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Usable as a library or script to generate automatic RST source files for - items referred to in autosummary:: directives. - - Each generated RST file contains a single auto*:: directive which - extracts the docstring of the referred item. - - Example Makefile rule:: - - generate: - sphinx-autogen -o source/generated source/*.rst - - :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" -from __future__ import print_function - -import os -import re -import sys -import pydoc -import optparse -import codecs - -from jinja2 import FileSystemLoader, TemplateNotFound -from jinja2.sandbox import SandboxedEnvironment - -from sphinx import package_dir -from . import import_by_name, get_documenter -from sphinx.jinja2glue import BuiltinTemplateLoader -from sphinx.util.osutil import ensuredir -from sphinx.util.inspect import safe_getattr - - -def main(argv=sys.argv): - usage = """%prog [OPTIONS] SOURCEFILE ...""" - p = optparse.OptionParser(usage.strip()) - p.add_option("-o", "--output-dir", action="store", type="string", - dest="output_dir", default=None, - help="Directory to place all output in") - p.add_option("-s", "--suffix", action="store", type="string", - dest="suffix", default="rst", - help="Default suffix for files (default: %default)") - p.add_option("-t", "--templates", action="store", type="string", - dest="templates", default=None, - help="Custom template directory (default: %default)") - options, args = p.parse_args(argv[1:]) - - if len(args) < 1: - p.error('no input files given') - - generate_autosummary_docs(args, options.output_dir, - "." + options.suffix, - template_dir=options.templates) - - -def _simple_info(msg): - print(msg) - - -def _simple_warn(msg): - print('WARNING: ' + msg, file=sys.stderr) - - -# -- Generating output --------------------------------------------------------- - -def generate_autosummary_docs(sources, output_dir=None, suffix='.rst', - warn=_simple_warn, info=_simple_info, - base_path=None, builder=None, template_dir=None): - - showed_sources = list(sorted(sources)) - if len(showed_sources) > 20: - showed_sources = showed_sources[:10] + ['...'] + showed_sources[-10:] - info('[autosummary] generating autosummary for: %s' % - ', '.join(showed_sources)) - - if output_dir: - info('[autosummary] writing to %s' % output_dir) - - if base_path is not None: - sources = [os.path.join(base_path, filename) for filename in sources] - - # create our own templating environment - template_dirs = [os.path.join(package_dir, 'ext', - 'autosummary', 'templates')] - if builder is not None: - # allow the user to override the templates - template_loader = BuiltinTemplateLoader() - template_loader.init(builder, dirs=template_dirs) - else: - if template_dir: - template_dirs.insert(0, template_dir) - template_loader = FileSystemLoader(template_dirs) - template_env = SandboxedEnvironment(loader=template_loader) - - # read - items = find_autosummary_in_files(sources) - - # keep track of new files - new_files = [] - - # write - for name, path, template_name in sorted(set(items), key=str): - if path is None: - # The corresponding autosummary:: directive did not have - # a :toctree: option - continue - - path = output_dir or os.path.abspath(path) - ensuredir(path) - - try: - name, obj, parent, mod_name = import_by_name(name) - except ImportError as e: - warn('[autosummary] failed to import %r: %s' % (name, e)) - continue - - fn = os.path.join(path, name + suffix) - - # skip it if it exists - if os.path.isfile(fn): - continue - - new_files.append(fn) - - with open(fn, 'w') as f: - doc = get_documenter(obj, parent) - - if template_name is not None: - template = template_env.get_template(template_name) - else: - try: - template = template_env.get_template('autosummary/%s.rst' - % doc.objtype) - except TemplateNotFound: - template = template_env.get_template('autosummary/base.rst') - - # Patched get_members according to - # http://stackoverflow.com/questions/25405110/sphinx-autosummary-with-toctree-also-lists-imported-members/25460763#25460763 - def get_members(obj, typ, include_public=[], imported=False): - items = [] - for name in dir(obj): - try: - obj_name = safe_getattr(obj, name) - documenter = get_documenter(obj_name, obj) - except AttributeError: - continue - if documenter.objtype == typ: - try: - cond = ( - imported or - obj_name.__module__ == obj.__name__ - ) - except AttributeError: - cond = True - if cond: - items.append(name) - public = [x for x in items - if x in include_public or not x.startswith('_')] - return public, items - - ns = {} - - if doc.objtype == 'module': - ns['members'] = dir(obj) - ns['functions'], ns['all_functions'] = \ - get_members(obj, 'function') - ns['classes'], ns['all_classes'] = \ - get_members(obj, 'class') - ns['exceptions'], ns['all_exceptions'] = \ - get_members(obj, 'exception') - elif doc.objtype == 'class': - ns['members'] = dir(obj) - ns['methods'], ns['all_methods'] = \ - get_members(obj, 'method', ['__init__']) - ns['attributes'], ns['all_attributes'] = \ - get_members(obj, 'attribute') - - parts = name.split('.') - if doc.objtype in ('method', 'attribute'): - mod_name = '.'.join(parts[:-2]) - cls_name = parts[-2] - obj_name = '.'.join(parts[-2:]) - ns['class'] = cls_name - else: - mod_name, obj_name = '.'.join(parts[:-1]), parts[-1] - - ns['fullname'] = name - ns['module'] = mod_name - ns['objname'] = obj_name - ns['name'] = parts[-1] - - ns['objtype'] = doc.objtype - ns['underline'] = len(name) * '=' - - rendered = template.render(**ns) - f.write(rendered) - - # descend recursively to new files - if new_files: - generate_autosummary_docs(new_files, output_dir=output_dir, - suffix=suffix, warn=warn, info=info, - base_path=base_path, builder=builder, - template_dir=template_dir) - - -# -- Finding documented entries in files --------------------------------------- - -def find_autosummary_in_files(filenames): - """Find out what items are documented in source/*.rst. - - See `find_autosummary_in_lines`. - """ - documented = [] - for filename in filenames: - with codecs.open(filename, 'r', encoding='utf-8', - errors='ignore') as f: - lines = f.read().splitlines() - documented.extend(find_autosummary_in_lines(lines, - filename=filename)) - return documented - - -def find_autosummary_in_docstring(name, module=None, filename=None): - """Find out what items are documented in the given object's docstring. - - See `find_autosummary_in_lines`. - """ - try: - real_name, obj, parent, modname = import_by_name(name) - lines = pydoc.getdoc(obj).splitlines() - return find_autosummary_in_lines(lines, module=name, filename=filename) - except AttributeError: - pass - except ImportError as e: - print("Failed to import '%s': %s" % (name, e)) - except SystemExit as e: - print("Failed to import '%s'; the module executes module level " - "statement and it might call sys.exit()." % name) - return [] - - -def find_autosummary_in_lines(lines, module=None, filename=None): - """Find out what items appear in autosummary:: directives in the - given lines. - - Returns a list of (name, toctree, template) where *name* is a name - of an object and *toctree* the :toctree: path of the corresponding - autosummary directive (relative to the root of the file name), and - *template* the value of the :template: option. *toctree* and - *template* ``None`` if the directive does not have the - corresponding options set. - """ - autosummary_re = re.compile(r'^(\s*)\.\.\s+autosummary::\s*') - automodule_re = re.compile( - r'^\s*\.\.\s+automodule::\s*([A-Za-z0-9_.]+)\s*$') - module_re = re.compile( - r'^\s*\.\.\s+(current)?module::\s*([a-zA-Z0-9_.]+)\s*$') - autosummary_item_re = re.compile(r'^\s+(~?[_a-zA-Z][a-zA-Z0-9_.]*)\s*.*?') - toctree_arg_re = re.compile(r'^\s+:toctree:\s*(.*?)\s*$') - template_arg_re = re.compile(r'^\s+:template:\s*(.*?)\s*$') - - documented = [] - - toctree = None - template = None - current_module = module - in_autosummary = False - base_indent = "" - - for line in lines: - if in_autosummary: - m = toctree_arg_re.match(line) - if m: - toctree = m.group(1) - if filename: - toctree = os.path.join(os.path.dirname(filename), - toctree) - continue - - m = template_arg_re.match(line) - if m: - template = m.group(1).strip() - continue - - if line.strip().startswith(':'): - continue # skip options - - m = autosummary_item_re.match(line) - if m: - name = m.group(1).strip() - if name.startswith('~'): - name = name[1:] - if current_module and \ - not name.startswith(current_module + '.'): - name = "%s.%s" % (current_module, name) - documented.append((name, toctree, template)) - continue - - if not line.strip() or line.startswith(base_indent + " "): - continue - - in_autosummary = False - - m = autosummary_re.match(line) - if m: - in_autosummary = True - base_indent = m.group(1) - toctree = None - template = None - continue - - m = automodule_re.search(line) - if m: - current_module = m.group(1).strip() - # recurse into the automodule docstring - documented.extend(find_autosummary_in_docstring( - current_module, filename=filename)) - continue - - m = module_re.match(line) - if m: - current_module = m.group(2) - continue - - return documented - - -if __name__ == '__main__': - main() diff --git a/doc/source/conf.py b/doc/source/conf.py index f4153e9c..e0aa9ea4 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -44,10 +44,7 @@ 'sphinx.ext.coverage', 'sphinx.ext.viewcode', 'autodoc_traits', - # patched autosummary for issue - # https://github.com/sphinx-doc/sphinx/issues/1061 - '_extensions' - #'sphinx.ext.autosummary' + 'sphinx.ext.autosummary', ] # Add any paths that contain templates here, relative to this directory.