From 4eefb01759cf840a1bc801df7c6f1fcb4029bbc4 Mon Sep 17 00:00:00 2001 From: Eric Holscher Date: Wed, 2 Nov 2016 18:46:29 -0700 Subject: [PATCH 1/9] Build initial toctree implementation --- autoapi/extension.py | 53 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/autoapi/extension.py b/autoapi/extension.py index 7047e45..55b32f0 100644 --- a/autoapi/extension.py +++ b/autoapi/extension.py @@ -7,6 +7,8 @@ This extension allows you to automagically generate API documentation from your import os import shutil +from docutils import nodes +from sphinx import addnodes from sphinx.util.console import darkgreen, bold from sphinx.addnodes import toctree from sphinx.errors import ExtensionError @@ -127,10 +129,61 @@ def doctree_read(app, doctree): app.info(bold('[AutoAPI] ') + darkgreen('Adding AutoAPI TOCTree to index.rst')) +def _build_toc_node(docname, anchor='anchor', text='test text'): + reference = nodes.reference('', '', internal=True, refuri=docname, + anchorname='#' + anchor, *[nodes.Text(text, text)]) + para = addnodes.compact_paragraph('', '', reference) + return nodes.list_item('', para) + + +def _find_toc_node(toc, ref_id, objtype): + for check_node in toc.traverse(nodes.reference): + if objtype == nodes.section and check_node.attributes['refuri'] == ref_id: + return check_node + if objtype == addnodes.desc and check_node.attributes['anchorname'] == '#' + ref_id: + return check_node + return None + + +def _traverse_parent(node, objtypes): + curr_node = node.parent + while curr_node is not None: + if isinstance(curr_node, objtypes): + return curr_node + curr_node = curr_node.parent + return None + + +def doctree_resolved(app, doctree, docname): + "Add domain objects to the toctree" + + toc = app.env.tocs[docname] + for desc_node in doctree.traverse(addnodes.desc): + # objtype = desc_node.attributes.get('objtype') + # if objtype == 'class': + ref_id = desc_node.children[0].attributes['ids'][0] + ref_text = desc_node.children[0].astext() + to_add = _build_toc_node(docname, anchor=ref_id, text=ref_text) + parent_node = _traverse_parent(node=desc_node, objtypes=(addnodes.desc, nodes.section)) + if parent_node: + if isinstance(parent_node, nodes.section): + parent_ref_id = docname + toc_reference = _find_toc_node(toc, parent_ref_id, nodes.section) + else: + import ipdb; ipdb.set_trace() + parent_ref_id = parent_node.children[0].attributes['ids'][0] + toc_reference = _find_toc_node(toc, parent_ref_id, addnodes.desc) + if toc_reference: + toc_insertion_point = _traverse_parent(toc_reference, nodes.bullet_list) + if toc_insertion_point: + toc_insertion_point.children.append(to_add) + + def setup(app): app.connect('builder-inited', run_autoapi) app.connect('build-finished', build_finished) app.connect('doctree-read', doctree_read) + app.connect('doctree-resolved', doctree_resolved) app.add_config_value('autoapi_type', 'python', 'html') app.add_config_value('autoapi_root', API_ROOT, 'html') app.add_config_value('autoapi_ignore', [], 'html') From b6dc3d23d8f340e374eb7d25e6c43c5290d5a2bd Mon Sep 17 00:00:00 2001 From: Eric Holscher Date: Wed, 2 Nov 2016 19:31:52 -0700 Subject: [PATCH 2/9] Clean up auto-tocing --- README.rst | 1 + autoapi/extension.py | 41 ++++++++++++++++++++++-------- autoapi/templates/python/class.rst | 4 --- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/README.rst b/README.rst index 8cd5625..4989f59 100644 --- a/README.rst +++ b/README.rst @@ -41,6 +41,7 @@ Contents :maxdepth: 2 design + autoapi/index Basic Workflow -------------- diff --git a/autoapi/extension.py b/autoapi/extension.py index 55b32f0..ac14f05 100644 --- a/autoapi/extension.py +++ b/autoapi/extension.py @@ -129,16 +129,22 @@ def doctree_read(app, doctree): app.info(bold('[AutoAPI] ') + darkgreen('Adding AutoAPI TOCTree to index.rst')) -def _build_toc_node(docname, anchor='anchor', text='test text'): +def _build_toc_node(docname, anchor='anchor', text='test text', bullet=False): reference = nodes.reference('', '', internal=True, refuri=docname, anchorname='#' + anchor, *[nodes.Text(text, text)]) para = addnodes.compact_paragraph('', '', reference) - return nodes.list_item('', para) + ret_list = nodes.list_item('', para) + if not bullet: + return ret_list + else: + return nodes.bullet_list('', ret_list) def _find_toc_node(toc, ref_id, objtype): for check_node in toc.traverse(nodes.reference): - if objtype == nodes.section and check_node.attributes['refuri'] == ref_id: + if objtype == nodes.section and \ + (check_node.attributes['refuri'] == ref_id or + check_node.attributes['anchorname'] == '#' + ref_id): return check_node if objtype == addnodes.desc and check_node.attributes['anchorname'] == '#' + ref_id: return check_node @@ -162,21 +168,36 @@ def doctree_resolved(app, doctree, docname): # objtype = desc_node.attributes.get('objtype') # if objtype == 'class': ref_id = desc_node.children[0].attributes['ids'][0] - ref_text = desc_node.children[0].astext() - to_add = _build_toc_node(docname, anchor=ref_id, text=ref_text) + try: + ref_text = desc_node[0].attributes['fullname'].split('.')[-1].split('(')[0] + except: + ref_text = desc_node[0].astext().split('.')[-1].split('(')[0] parent_node = _traverse_parent(node=desc_node, objtypes=(addnodes.desc, nodes.section)) if parent_node: - if isinstance(parent_node, nodes.section): + if isinstance(parent_node, nodes.section) and \ + isinstance(parent_node.parent, nodes.document): + # Top Level Section header parent_ref_id = docname toc_reference = _find_toc_node(toc, parent_ref_id, nodes.section) + elif isinstance(parent_node, nodes.section): + # Nested Section header + parent_ref_id = parent_node.attributes['ids'][0] + toc_reference = _find_toc_node(toc, parent_ref_id, nodes.section) else: - import ipdb; ipdb.set_trace() + # Desc node parent_ref_id = parent_node.children[0].attributes['ids'][0] toc_reference = _find_toc_node(toc, parent_ref_id, addnodes.desc) + if toc_reference: - toc_insertion_point = _traverse_parent(toc_reference, nodes.bullet_list) - if toc_insertion_point: - toc_insertion_point.children.append(to_add) + # The last bit of the parent we're looking at + toc_insertion_point = _traverse_parent(toc_reference, nodes.bullet_list)[-1] + if toc_insertion_point and isinstance(toc_insertion_point[0], nodes.bullet_list): + new_insert = toc_insertion_point[0] + to_add = _build_toc_node(docname, anchor=ref_id, text=ref_text) + new_insert.append(to_add) + else: + to_add = _build_toc_node(docname, anchor=ref_id, text=ref_text, bullet=True) + toc_insertion_point.append(to_add) def setup(app): diff --git a/autoapi/templates/python/class.rst b/autoapi/templates/python/class.rst index 201a58b..1f4fe4f 100644 --- a/autoapi/templates/python/class.rst +++ b/autoapi/templates/python/class.rst @@ -1,7 +1,3 @@ -.. autoapi-hidden:: - {{ obj.short_name }} - {{ "=" * obj.short_name|length }} - .. py:class:: {{ obj.short_name }}{% if obj.args %}({{ obj.args|join(',') }}){% endif %} {%- if obj.docstring %} From cc41787b58e1f6a6c551236023b1f84006d8a491 Mon Sep 17 00:00:00 2001 From: Eric Holscher Date: Wed, 2 Nov 2016 19:36:46 -0700 Subject: [PATCH 3/9] Write up a more descriptive docstring --- autoapi/extension.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/autoapi/extension.py b/autoapi/extension.py index ac14f05..2a92730 100644 --- a/autoapi/extension.py +++ b/autoapi/extension.py @@ -161,7 +161,21 @@ def _traverse_parent(node, objtypes): def doctree_resolved(app, doctree, docname): - "Add domain objects to the toctree" + """ + Add domain objects to the toctree dynamically. + + This works by: + + * Finding each domain node (addnodes.desc) + * Figuring out it's parent that will be in the toctree (nodes.section, or a previously added addnodes.desc) + * Finding that parent in the TOC Tree based on it's ID + * Taking that element in the TOC Tree and finding it's parent that is a TOC Listing (nodes.bullet_list) + * Adding the new TOC element for our specific node as a child of that nodes.bullet_list + * This checks that bullet_list's last child, + and checks that it is also a nodes.bullet_list, + effectively nesting it under that element + + """ toc = app.env.tocs[docname] for desc_node in doctree.traverse(addnodes.desc): From 624eeff0307484435a11b390bcb0254a56de131d Mon Sep 17 00:00:00 2001 From: Eric Holscher Date: Thu, 3 Nov 2016 12:54:26 -0700 Subject: [PATCH 4/9] Break out toctree into separate file. Also add lots of docstrings, so hopefully someone can understand this some day. --- autoapi/extension.py | 94 ++------------------------- autoapi/mappers/python.py | 5 +- autoapi/toctree.py | 133 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+), 90 deletions(-) create mode 100644 autoapi/toctree.py diff --git a/autoapi/extension.py b/autoapi/extension.py index 2a92730..5b40700 100644 --- a/autoapi/extension.py +++ b/autoapi/extension.py @@ -7,8 +7,6 @@ This extension allows you to automagically generate API documentation from your import os import shutil -from docutils import nodes -from sphinx import addnodes from sphinx.util.console import darkgreen, bold from sphinx.addnodes import toctree from sphinx.errors import ExtensionError @@ -17,6 +15,7 @@ from docutils.parsers.rst import directives from .backends import default_file_mapping, default_ignore_patterns, default_backend_mapping from .directives import NestedParse from .settings import API_ROOT +from .toctree import add_domain_to_toctree default_options = ['members', 'undoc-members', 'private-members', 'special-members'] @@ -109,6 +108,10 @@ def build_finished(app, exception): def doctree_read(app, doctree): + """ + Inject AutoAPI into the TOC Tree dynamically. + """ + all_docs = set() insert = True if app.env.docname == 'index': @@ -129,96 +132,11 @@ def doctree_read(app, doctree): app.info(bold('[AutoAPI] ') + darkgreen('Adding AutoAPI TOCTree to index.rst')) -def _build_toc_node(docname, anchor='anchor', text='test text', bullet=False): - reference = nodes.reference('', '', internal=True, refuri=docname, - anchorname='#' + anchor, *[nodes.Text(text, text)]) - para = addnodes.compact_paragraph('', '', reference) - ret_list = nodes.list_item('', para) - if not bullet: - return ret_list - else: - return nodes.bullet_list('', ret_list) - - -def _find_toc_node(toc, ref_id, objtype): - for check_node in toc.traverse(nodes.reference): - if objtype == nodes.section and \ - (check_node.attributes['refuri'] == ref_id or - check_node.attributes['anchorname'] == '#' + ref_id): - return check_node - if objtype == addnodes.desc and check_node.attributes['anchorname'] == '#' + ref_id: - return check_node - return None - - -def _traverse_parent(node, objtypes): - curr_node = node.parent - while curr_node is not None: - if isinstance(curr_node, objtypes): - return curr_node - curr_node = curr_node.parent - return None - - -def doctree_resolved(app, doctree, docname): - """ - Add domain objects to the toctree dynamically. - - This works by: - - * Finding each domain node (addnodes.desc) - * Figuring out it's parent that will be in the toctree (nodes.section, or a previously added addnodes.desc) - * Finding that parent in the TOC Tree based on it's ID - * Taking that element in the TOC Tree and finding it's parent that is a TOC Listing (nodes.bullet_list) - * Adding the new TOC element for our specific node as a child of that nodes.bullet_list - * This checks that bullet_list's last child, - and checks that it is also a nodes.bullet_list, - effectively nesting it under that element - - """ - - toc = app.env.tocs[docname] - for desc_node in doctree.traverse(addnodes.desc): - # objtype = desc_node.attributes.get('objtype') - # if objtype == 'class': - ref_id = desc_node.children[0].attributes['ids'][0] - try: - ref_text = desc_node[0].attributes['fullname'].split('.')[-1].split('(')[0] - except: - ref_text = desc_node[0].astext().split('.')[-1].split('(')[0] - parent_node = _traverse_parent(node=desc_node, objtypes=(addnodes.desc, nodes.section)) - if parent_node: - if isinstance(parent_node, nodes.section) and \ - isinstance(parent_node.parent, nodes.document): - # Top Level Section header - parent_ref_id = docname - toc_reference = _find_toc_node(toc, parent_ref_id, nodes.section) - elif isinstance(parent_node, nodes.section): - # Nested Section header - parent_ref_id = parent_node.attributes['ids'][0] - toc_reference = _find_toc_node(toc, parent_ref_id, nodes.section) - else: - # Desc node - parent_ref_id = parent_node.children[0].attributes['ids'][0] - toc_reference = _find_toc_node(toc, parent_ref_id, addnodes.desc) - - if toc_reference: - # The last bit of the parent we're looking at - toc_insertion_point = _traverse_parent(toc_reference, nodes.bullet_list)[-1] - if toc_insertion_point and isinstance(toc_insertion_point[0], nodes.bullet_list): - new_insert = toc_insertion_point[0] - to_add = _build_toc_node(docname, anchor=ref_id, text=ref_text) - new_insert.append(to_add) - else: - to_add = _build_toc_node(docname, anchor=ref_id, text=ref_text, bullet=True) - toc_insertion_point.append(to_add) - - def setup(app): app.connect('builder-inited', run_autoapi) app.connect('build-finished', build_finished) app.connect('doctree-read', doctree_read) - app.connect('doctree-resolved', doctree_resolved) + app.connect('doctree-resolved', add_domain_to_toctree) app.add_config_value('autoapi_type', 'python', 'html') app.add_config_value('autoapi_root', API_ROOT, 'html') app.add_config_value('autoapi_ignore', [], 'html') diff --git a/autoapi/mappers/python.py b/autoapi/mappers/python.py index a5ed239..9463bf5 100644 --- a/autoapi/mappers/python.py +++ b/autoapi/mappers/python.py @@ -174,8 +174,9 @@ class PythonPythonMapper(PythonMapperBase): # exceptions, including SyntaxError try: parsed = ast.parse(source) - except: # noqa - return + except Exception, e: # noqa + print "Error parsing AST: %s" % str(e) + return [] parsed_args = parsed.body[0].args arg_names = [arg.id if sys.version_info < (3,) else arg.arg for arg in parsed_args.args] diff --git a/autoapi/toctree.py b/autoapi/toctree.py new file mode 100644 index 0000000..111672d --- /dev/null +++ b/autoapi/toctree.py @@ -0,0 +1,133 @@ +""" +A small Sphinx extension that adds Domain objects (eg. Python Classes & Methods) to the TOC Tree. + +It dynamically adds them to the already rendered ``app.env.tocs`` dict on the Sphinx environment. +Traditionally this only contains Section's, +we then nest our Domain references inside the already existing Sections. +""" + +from docutils import nodes +from sphinx import addnodes + + +def _build_toc_node(docname, anchor='anchor', text='test text', bullet=False): + """ + Create the node structure that Sphinx expects for TOC Tree entries. + The ``bullet`` argument wraps it in a ``nodes.bullet_list``, + which is how you nest TOC Tree entries. + """ + + reference = nodes.reference('', '', internal=True, refuri=docname, + anchorname='#' + anchor, *[nodes.Text(text, text)]) + para = addnodes.compact_paragraph('', '', reference) + ret_list = nodes.list_item('', para) + if not bullet: + return ret_list + else: + return nodes.bullet_list('', ret_list) + + +def _traverse_parent(node, objtypes): + """ + Traverse up the node's parents until you hit the ``objtypes`` referenced. + Can either be a single type, + or a tuple of types. + """ + + curr_node = node.parent + while curr_node is not None: + if isinstance(curr_node, objtypes): + return curr_node + curr_node = curr_node.parent + return None + + +def _find_toc_node(toc, ref_id, objtype): + """ + Find the actual TOC node for a ref_id. + Depends on the object type: + + * Section - First section (refuri) or 2nd+ level section (anchorname) + * Desc - Just use the anchor name + """ + for check_node in toc.traverse(nodes.reference): + if objtype == nodes.section and \ + (check_node.attributes['refuri'] == ref_id or + check_node.attributes['anchorname'] == '#' + ref_id): + return check_node + if objtype == addnodes.desc and check_node.attributes['anchorname'] == '#' + ref_id: + return check_node + return None + + +def _get_toc_reference(node, toc, docname): + """ + Logic that understands maps a specific node to it's part of the toctree. + It takes a specific incoming ``node``, + and returns the actual TOC Tree node that is said reference. + """ + + if isinstance(node, nodes.section) and \ + isinstance(node.parent, nodes.document): + # Top Level Section header + ref_id = docname + toc_reference = _find_toc_node(toc, ref_id, nodes.section) + elif isinstance(node, nodes.section): + # Nested Section header + ref_id = node.attributes['ids'][0] + toc_reference = _find_toc_node(toc, ref_id, nodes.section) + else: + # Desc node + ref_id = node.children[0].attributes['ids'][0] + toc_reference = _find_toc_node(toc, ref_id, addnodes.desc) + + return toc_reference + + +def add_domain_to_toctree(app, doctree, docname): + """ + Add domain objects to the toctree dynamically. + This should be attached to the ``doctree-resolved`` event. + + This works by: + + * Finding each domain node (addnodes.desc) + * Figuring out it's parent that will be in the toctree + (nodes.section, or a previously added addnodes.desc) + * Finding that parent in the TOC Tree based on it's ID + * Taking that element in the TOC Tree, + and finding it's parent that is a TOC Listing (nodes.bullet_list) + * Adding the new TOC element for our specific node as a child of that nodes.bullet_list + * This checks that bullet_list's last child, + and checks that it is also a nodes.bullet_list, + effectively nesting it under that element + + """ + + toc = app.env.tocs[docname] + for desc_node in doctree.traverse(addnodes.desc): + ref_id = desc_node.children[0].attributes['ids'][0] + try: + # Python domain object + ref_text = desc_node[0].attributes['fullname'].split('.')[-1].split('(')[0] + except: + # Use `astext` for other types of domain objects + ref_text = desc_node[0].astext().split('.')[-1].split('(')[0] + # This is the actual object that will exist in the TOC Tree + # Sections by default, and other Desc nodes that we've previously placed. + parent_node = _traverse_parent(node=desc_node, objtypes=(addnodes.desc, nodes.section)) + + if parent_node: + toc_reference = _get_toc_reference(parent_node, toc, docname) + if toc_reference: + # Get the last child of our parent's bullet list, this is where "we" live. + toc_insertion_point = _traverse_parent(toc_reference, nodes.bullet_list)[-1] + # Ensure we're added another bullet list so that we nest inside the parent, + # not next to it + if toc_insertion_point and isinstance(toc_insertion_point[0], nodes.bullet_list): + new_insert = toc_insertion_point[0] + to_add = _build_toc_node(docname, anchor=ref_id, text=ref_text) + new_insert.append(to_add) + else: + to_add = _build_toc_node(docname, anchor=ref_id, text=ref_text, bullet=True) + toc_insertion_point.append(to_add) From 66fd939395db39114a035e6bded2a4b84c800cb0 Mon Sep 17 00:00:00 2001 From: Eric Holscher Date: Thu, 3 Nov 2016 13:00:37 -0700 Subject: [PATCH 5/9] Add a bit more error handling. --- autoapi/toctree.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/autoapi/toctree.py b/autoapi/toctree.py index 111672d..e171531 100644 --- a/autoapi/toctree.py +++ b/autoapi/toctree.py @@ -106,7 +106,11 @@ def add_domain_to_toctree(app, doctree, docname): toc = app.env.tocs[docname] for desc_node in doctree.traverse(addnodes.desc): - ref_id = desc_node.children[0].attributes['ids'][0] + try: + ref_id = desc_node.children[0].attributes['ids'][0] + except IndexError, e: + print 'Invalid desc node: %s' % e + continue try: # Python domain object ref_text = desc_node[0].attributes['fullname'].split('.')[-1].split('(')[0] From c3db6e5bc849805d87b3b105c249d2709a17a6ad Mon Sep 17 00:00:00 2001 From: Eric Holscher Date: Thu, 3 Nov 2016 13:10:22 -0700 Subject: [PATCH 6/9] Python 3 fixes --- autoapi/mappers/python.py | 5 ++--- autoapi/toctree.py | 12 ++++++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/autoapi/mappers/python.py b/autoapi/mappers/python.py index 9463bf5..cf5639f 100644 --- a/autoapi/mappers/python.py +++ b/autoapi/mappers/python.py @@ -1,6 +1,5 @@ import sys import os -import re import textwrap import ast from collections import defaultdict @@ -174,8 +173,8 @@ class PythonPythonMapper(PythonMapperBase): # exceptions, including SyntaxError try: parsed = ast.parse(source) - except Exception, e: # noqa - print "Error parsing AST: %s" % str(e) + except Exception as e: # noqa + print("Error parsing AST: %s" % str(e)) return [] parsed_args = parsed.body[0].args arg_names = [arg.id if sys.version_info < (3,) else arg.arg diff --git a/autoapi/toctree.py b/autoapi/toctree.py index e171531..134c427 100644 --- a/autoapi/toctree.py +++ b/autoapi/toctree.py @@ -78,8 +78,12 @@ def _get_toc_reference(node, toc, docname): toc_reference = _find_toc_node(toc, ref_id, nodes.section) else: # Desc node - ref_id = node.children[0].attributes['ids'][0] - toc_reference = _find_toc_node(toc, ref_id, addnodes.desc) + try: + ref_id = node.children[0].attributes['ids'][0] + toc_reference = _find_toc_node(toc, ref_id, addnodes.desc) + except IndexError as e: + print('Invalid desc node: %s' % e) + toc_reference = None return toc_reference @@ -108,8 +112,8 @@ def add_domain_to_toctree(app, doctree, docname): for desc_node in doctree.traverse(addnodes.desc): try: ref_id = desc_node.children[0].attributes['ids'][0] - except IndexError, e: - print 'Invalid desc node: %s' % e + except IndexError as e: + print('Invalid desc node: %s' % e) continue try: # Python domain object From 73324fab7e5a7893eee39e78ac3f3e96c0879442 Mon Sep 17 00:00:00 2001 From: Eric Holscher Date: Thu, 3 Nov 2016 13:24:22 -0700 Subject: [PATCH 7/9] Remove unneeded index entry --- README.rst | 1 - autoapi/extension.py | 7 ++++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 4989f59..8cd5625 100644 --- a/README.rst +++ b/README.rst @@ -41,7 +41,6 @@ Contents :maxdepth: 2 design - autoapi/index Basic Workflow -------------- diff --git a/autoapi/extension.py b/autoapi/extension.py index 5b40700..4b96c8e 100644 --- a/autoapi/extension.py +++ b/autoapi/extension.py @@ -116,11 +116,13 @@ def doctree_read(app, doctree): insert = True if app.env.docname == 'index': nodes = doctree.traverse(toctree) + toc_entry = '%s/index' % app.config.autoapi_root if not nodes: return for node in nodes: for entry in node['entries']: all_docs.add(entry[1]) + # Don't insert if it's already present for doc in all_docs: if doc.find(app.config.autoapi_root) != -1: insert = False @@ -129,7 +131,10 @@ def doctree_read(app, doctree): (None, u'%s/index' % app.config.autoapi_root) ) nodes[-1]['includefiles'].append(u'%s/index' % app.config.autoapi_root) - app.info(bold('[AutoAPI] ') + darkgreen('Adding AutoAPI TOCTree to index.rst')) + app.info(bold('[AutoAPI] ') + + darkgreen('Adding AutoAPI TOCTree [%s] to index.rst' % toc_entry) + ) + app.env.build_toc_from(app.env.docname, doctree) def setup(app): From 215dd06e0ac01429ad269ba58f1a2d314af23e09 Mon Sep 17 00:00:00 2001 From: Eric Holscher Date: Thu, 3 Nov 2016 19:52:54 -0700 Subject: [PATCH 8/9] Add basic test for TOC Tree insertion --- tests/test_integration.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_integration.py b/tests/test_integration.py index aa25ca0..7cb4d7e 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -134,3 +134,13 @@ class TOCTreeTests(LanguageIntegrationTests): '_build/text/index.txt', 'AutoAPI Index' ) + + def test_toctree_domain_insertion(self): + """ + Test that the example_function gets added to the TOC Tree + """ + self._run_test( + 'toctreeexample', + '_build/text/index.txt', + '* example_function' + ) From c557182525a9230c3f8e72be211a5c249bf974a2 Mon Sep 17 00:00:00 2001 From: Eric Holscher Date: Fri, 4 Nov 2016 13:42:15 -0700 Subject: [PATCH 9/9] Fix review feedback --- autoapi/backends.py | 2 ++ autoapi/extension.py | 18 +++++++++--------- autoapi/toctree.py | 27 +++++++++++++-------------- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/autoapi/backends.py b/autoapi/backends.py index 623719f..d978da7 100644 --- a/autoapi/backends.py +++ b/autoapi/backends.py @@ -7,11 +7,13 @@ default_file_mapping = { 'javascript': ['*.js'], } + default_ignore_patterns = { 'dotnet': ['*toc.yml', '*index.yml'], 'python': ['*migrations*'], } + default_backend_mapping = { 'python': PythonSphinxMapper, 'dotnet': DotNetSphinxMapper, diff --git a/autoapi/extension.py b/autoapi/extension.py index 4b96c8e..959e991 100644 --- a/autoapi/extension.py +++ b/autoapi/extension.py @@ -55,9 +55,9 @@ def run_autoapi(app): app.env.autoapi_data = [] - domain = default_backend_mapping[app.config.autoapi_type] - domain_obj = domain(app, template_dir=app.config.autoapi_template_dir, - url_root=url_root) + sphinx_mapper = default_backend_mapping[app.config.autoapi_type] + sphinx_mapper_obj = sphinx_mapper(app, template_dir=app.config.autoapi_template_dir, + url_root=url_root) if app.config.autoapi_file_patterns: file_patterns = app.config.autoapi_file_patterns @@ -79,17 +79,17 @@ def run_autoapi(app): # Actual meat of the run. app.info(bold('[AutoAPI] ') + darkgreen('Loading Data')) - domain_obj.load( + sphinx_mapper_obj.load( patterns=file_patterns, dirs=normalized_dirs, ignore=ignore_patterns, ) app.info(bold('[AutoAPI] ') + darkgreen('Mapping Data')) - domain_obj.map(options=app.config.autoapi_options) + sphinx_mapper_obj.map(options=app.config.autoapi_options) app.info(bold('[AutoAPI] ') + darkgreen('Rendering Data')) - domain_obj.output_rst( + sphinx_mapper_obj.output_rst( root=normalized_root, source_suffix=out_suffix, ) @@ -102,9 +102,9 @@ def build_finished(app, exception): app.info(bold('[AutoAPI] ') + darkgreen('Cleaning generated .rst files')) shutil.rmtree(normalized_root) - mapper = default_backend_mapping[app.config.autoapi_type] - if hasattr(mapper, 'build_finished'): - mapper.build_finished(app, exception) + sphinx_mapper = default_backend_mapping[app.config.autoapi_type] + if hasattr(sphinx_mapper, 'build_finished'): + sphinx_mapper.build_finished(app, exception) def doctree_read(app, doctree): diff --git a/autoapi/toctree.py b/autoapi/toctree.py index 134c427..f04e87c 100644 --- a/autoapi/toctree.py +++ b/autoapi/toctree.py @@ -13,10 +13,10 @@ from sphinx import addnodes def _build_toc_node(docname, anchor='anchor', text='test text', bullet=False): """ Create the node structure that Sphinx expects for TOC Tree entries. + The ``bullet`` argument wraps it in a ``nodes.bullet_list``, which is how you nest TOC Tree entries. """ - reference = nodes.reference('', '', internal=True, refuri=docname, anchorname='#' + anchor, *[nodes.Text(text, text)]) para = addnodes.compact_paragraph('', '', reference) @@ -30,10 +30,10 @@ def _build_toc_node(docname, anchor='anchor', text='test text', bullet=False): def _traverse_parent(node, objtypes): """ Traverse up the node's parents until you hit the ``objtypes`` referenced. + Can either be a single type, or a tuple of types. """ - curr_node = node.parent while curr_node is not None: if isinstance(curr_node, objtypes): @@ -45,8 +45,8 @@ def _traverse_parent(node, objtypes): def _find_toc_node(toc, ref_id, objtype): """ Find the actual TOC node for a ref_id. - Depends on the object type: + Depends on the object type: * Section - First section (refuri) or 2nd+ level section (anchorname) * Desc - Just use the anchor name """ @@ -60,13 +60,13 @@ def _find_toc_node(toc, ref_id, objtype): return None -def _get_toc_reference(node, toc, docname): +def _get_toc_reference(app, node, toc, docname): """ Logic that understands maps a specific node to it's part of the toctree. + It takes a specific incoming ``node``, and returns the actual TOC Tree node that is said reference. """ - if isinstance(node, nodes.section) and \ isinstance(node.parent, nodes.document): # Top Level Section header @@ -81,8 +81,8 @@ def _get_toc_reference(node, toc, docname): try: ref_id = node.children[0].attributes['ids'][0] toc_reference = _find_toc_node(toc, ref_id, addnodes.desc) - except IndexError as e: - print('Invalid desc node: %s' % e) + except (KeyError, IndexError) as e: + app.warn('Invalid desc node: %s' % e) toc_reference = None return toc_reference @@ -91,8 +91,8 @@ def _get_toc_reference(node, toc, docname): def add_domain_to_toctree(app, doctree, docname): """ Add domain objects to the toctree dynamically. - This should be attached to the ``doctree-resolved`` event. + This should be attached to the ``doctree-resolved`` event. This works by: * Finding each domain node (addnodes.desc) @@ -105,20 +105,19 @@ def add_domain_to_toctree(app, doctree, docname): * This checks that bullet_list's last child, and checks that it is also a nodes.bullet_list, effectively nesting it under that element - """ - toc = app.env.tocs[docname] for desc_node in doctree.traverse(addnodes.desc): try: ref_id = desc_node.children[0].attributes['ids'][0] - except IndexError as e: - print('Invalid desc node: %s' % e) + except (KeyError, IndexError) as e: + app.warn('Invalid desc node: %s' % e) continue try: # Python domain object ref_text = desc_node[0].attributes['fullname'].split('.')[-1].split('(')[0] - except: + except (KeyError, IndexError): + # TODO[eric]: Support other Domains and ways of accessing this data # Use `astext` for other types of domain objects ref_text = desc_node[0].astext().split('.')[-1].split('(')[0] # This is the actual object that will exist in the TOC Tree @@ -126,7 +125,7 @@ def add_domain_to_toctree(app, doctree, docname): parent_node = _traverse_parent(node=desc_node, objtypes=(addnodes.desc, nodes.section)) if parent_node: - toc_reference = _get_toc_reference(parent_node, toc, docname) + toc_reference = _get_toc_reference(app, parent_node, toc, docname) if toc_reference: # Get the last child of our parent's bullet list, this is where "we" live. toc_insertion_point = _traverse_parent(toc_reference, nodes.bullet_list)[-1]