chiark / gitweb /
documentation/python: module and class index.
authorVladimír Vondruš <mosra@centrum.cz>
Sat, 20 Apr 2019 18:29:53 +0000 (20:29 +0200)
committerVladimír Vondruš <mosra@centrum.cz>
Mon, 22 Apr 2019 15:53:36 +0000 (17:53 +0200)
13 files changed:
doc/documentation/python.rst
documentation/python.py
documentation/templates/python/base-index.html [new file with mode: 0644]
documentation/templates/python/classes.html [new file with mode: 0644]
documentation/templates/python/modules.html [new file with mode: 0644]
documentation/templates/python/pages.html [new file with mode: 0644]
documentation/test_python/inspect_string/classes.html [new file with mode: 0644]
documentation/test_python/inspect_string/inspect_string.Foo.html
documentation/test_python/inspect_string/inspect_string.Specials.html
documentation/test_python/inspect_string/inspect_string.another_module.html
documentation/test_python/inspect_string/inspect_string.html
documentation/test_python/inspect_string/modules.html [new file with mode: 0644]
documentation/test_python/test_inspect.py

index 9096379672f4d7bbe1a40aa3c1499ea85ed14f41..20b72a0f9e6c4c69639f3889ba8fd9259c20c578 100644 (file)
@@ -187,6 +187,13 @@ Variable                            Description
                                     put into the footer. If not set, a default
                                     generic text is used. If empty, no footer
                                     is rendered at all.
+:py:`CLASS_INDEX_EXPAND_LEVELS`     How many levels of the class index tree to
+                                    expand. :py:`0` means only the top-level
+                                    symbols are shown. If not set, :py:`1` is
+                                    used.
+:py:`CLASS_INDEX_EXPAND_INNER`      Whether to expand inner classes in the
+                                    class index. If not set, :py:`False` is
+                                    used.
 :py:`SEARCH_DISABLED: bool`         Disable search functionality. If this
                                     option is set, no search data is compiled
                                     and the rendered HTML does not contain
@@ -815,6 +822,54 @@ Property                            Description
                                     :py:`False`. [2]_
 =================================== ===========================================
 
+`Index page templates`_
+-----------------------
+
+The following index pages are provided, showing a expandable tree of the
+contents:
+
+.. class:: m-table m-fullwidth
+
+======================= =======================================================
+Filename                Use
+======================= =======================================================
+``classes.html``        Class listing
+``modules.html``        Module listing
+``pages.html``          Page listing
+======================= =======================================================
+
+Each template is passed all configuration values from the `Configuration`_
+table as-is, together with a :py:`FILENAME`, as above. The navigation tree is
+provided in an :py:`index` object, which has the following properties:
+
+.. class:: m-table m-fullwidth
+
+=========================== ===================================================
+Property                    Description
+=========================== ===================================================
+:py:`index.classes`         List of all modules + classes
+:py:`index.pages`           List of all pages
+=========================== ===================================================
+
+The form of each list entry is the same:
+
+.. class:: m-table m-fullwidth
+
+=============================== ===============================================
+Property                        Description
+=============================== ===============================================
+:py:`i.kind`                    Entry kind (one of :py:`'module'`,
+                                :py:`'class'` or :py:`'page'`)
+:py:`i.name`                    Name
+:py:`i.url`                     URL of the file with detailed documentation
+:py:`i.brief`                   Brief documentation
+:py:`i.has_nestable_children`   If the list has nestable children (i.e., dirs
+                                or namespaces)
+:py:`i.children`                Recursive list of child entries
+=============================== ===============================================
+
+Module/class list is ordered in a way that all modules are before all classes.
+
 -------------------------------
 
 .. [1] :py:`i.type` is extracted out of function annotation. If the types
index 5901270405893fbe61bf18c16958bd11de54976b..d57addf39ca1760e663231b46ba112904a13144f 100755 (executable)
@@ -69,6 +69,8 @@ default_config = {
     'LINKS_NAVBAR2': [],
     'PAGE_HEADER': None,
     'FINE_PRINT': '[default]',
+    'CLASS_INDEX_EXPAND_LEVELS': 1,
+    'CLASS_INDEX_EXPAND_INNER': False,
     'SEARCH_DISABLED': False,
     'SEARCH_DOWNLOAD_BINARY': False,
     'SEARCH_HELP': """.. raw:: html
@@ -88,6 +90,21 @@ default_config = {
     'SEARCH_EXTERNAL_URL': None,
 }
 
+class IndexEntry:
+    def __init__(self):
+        self.kind: str
+        self.name: str
+        self.url: str
+        self.brief: str
+        self.has_nestaable_children: bool = False
+        self.children: List[IndexEntry] = []
+
+class State:
+    def __init__(self, config):
+        self.config = config
+        self.class_index: List[IndexEntry] = []
+        self.page_index: List[IndexEntry] = []
+
 def is_internal_function_name(name: str) -> bool:
     """If the function name is internal.
 
@@ -298,7 +315,7 @@ def extract_data_doc(parent, path: List[str], data):
 
     return out
 
-def render_module(config, path, module, env):
+def render_module(state: State, path, module, env):
     logging.debug("generating %s.html", '.'.join(path))
 
     url_base = ''
@@ -319,6 +336,13 @@ def render_module(config, path, module, env):
     page.data = []
     page.has_enum_details = False
 
+    # Index entry for this module, returned together with children at the end
+    index_entry = IndexEntry()
+    index_entry.kind = 'module'
+    index_entry.name = breadcrumb[-1][0]
+    index_entry.url = page.url
+    index_entry.brief = page.brief
+
     # This is actually complicated -- if the module defines __all__, use that.
     # The __all__ is meant to expose the public API, so we don't filter out
     # underscored things.
@@ -334,10 +358,10 @@ def render_module(config, path, module, env):
             # submodules and subclasses recursively.
             if inspect.ismodule(object):
                 page.modules += [extract_module_doc(subpath, object)]
-                render_module(config, subpath, object, env)
+                index_entry.children += [render_module(state, subpath, object, env)]
             elif inspect.isclass(object) and not issubclass(object, enum.Enum):
                 page.classes += [extract_class_doc(subpath, object)]
-                render_class(config, subpath, object, env)
+                index_entry.children += [render_class(state, subpath, object, env)]
             elif inspect.isclass(object) and issubclass(object, enum.Enum):
                 enum_ = extract_enum_doc(subpath, object)
                 page.enums += [enum_]
@@ -363,7 +387,7 @@ def render_module(config, path, module, env):
 
             subpath = path + [name]
             page.modules += [extract_module_doc(subpath, object)]
-            render_module(config, subpath, object, env)
+            index_entry.children += [render_module(state, subpath, object, env)]
 
         # Get (and render) inner classes
         for name, object in inspect.getmembers(module, lambda o: inspect.isclass(o) and not issubclass(o, enum.Enum)):
@@ -373,7 +397,7 @@ def render_module(config, path, module, env):
             if not object.__doc__: logging.warning("%s is undocumented", '.'.join(subpath))
 
             page.classes += [extract_class_doc(subpath, object)]
-            render_class(config, subpath, object, env)
+            index_entry.children += [render_class(state, subpath, object, env)]
 
         # Get enums
         for name, object in inspect.getmembers(module, lambda o: inspect.isclass(o) and issubclass(o, enum.Enum)):
@@ -402,7 +426,8 @@ def render_module(config, path, module, env):
 
             page.data += [extract_data_doc(module, path + [name], object)]
 
-    render(config, 'module.html', page, env)
+    render(state.config, 'module.html', page, env)
+    return index_entry
 
 # Builtin dunder functions have hardcoded docstrings. This is totally useless
 # to have in the docs, so filter them out. Uh... kinda ugly.
@@ -456,7 +481,7 @@ _filtered_builtin_properties = set([
     ('__weakref__', "list of weak references to the object (if defined)")
 ])
 
-def render_class(config, path, class_, env):
+def render_class(state: State, path, class_, env):
     logging.debug("generating %s.html", '.'.join(path))
 
     url_base = ''
@@ -480,6 +505,13 @@ def render_class(config, path, class_, env):
     page.data = []
     page.has_enum_details = False
 
+    # Index entry for this module, returned together with children at the end
+    index_entry = IndexEntry()
+    index_entry.kind = 'class'
+    index_entry.name = breadcrumb[-1][0]
+    index_entry.url = page.url
+    index_entry.brief = page.brief
+
     # Get inner classes
     for name, object in inspect.getmembers(class_, lambda o: inspect.isclass(o) and not issubclass(o, enum.Enum)):
         if name in ['__base__', '__class__']: continue # TODO
@@ -489,7 +521,7 @@ def render_class(config, path, class_, env):
         if not object.__doc__: logging.warning("%s is undocumented", '.'.join(subpath))
 
         page.classes += [extract_class_doc(subpath, object)]
-        render_class(config, subpath, object, env)
+        index_entry.children += [render_class(state, subpath, object, env)]
 
     # Get enums
     for name, object in inspect.getmembers(class_, lambda o: inspect.isclass(o) and issubclass(o, enum.Enum)):
@@ -545,7 +577,8 @@ def render_class(config, path, class_, env):
         subpath = path + [name]
         page.data += [extract_data_doc(class_, subpath, object)]
 
-    render(config, 'class.html', page, env)
+    render(state.config, 'class.html', page, env)
+    return index_entry
 
 def run(basedir, config, templates):
     # Prepare Jinja environment
@@ -573,6 +606,8 @@ def run(basedir, config, templates):
     if config['FAVICON']:
         config['FAVICON'] = (config['FAVICON'], mimetypes.guess_type(config['FAVICON'])[0])
 
+    state = State(config)
+
     for module in config['INPUT_MODULES']:
         if isinstance(module, str):
             module_name = module
@@ -580,7 +615,33 @@ def run(basedir, config, templates):
         else:
             module_name = module.__name__
 
-        render_module(config, [module_name], module, env)
+        state.class_index += [render_module(state, [module_name], module, env)]
+
+    # Recurse into the tree and mark every node that has nested modules with
+    # has_nestaable_children.
+    def mark_nested_modules(list: List[IndexEntry]):
+        has_nestaable_children = False
+        for i in list:
+            if i.kind != 'module': continue
+            has_nestaable_children = True
+            mark_nested_modules(i.children)
+        return has_nestaable_children
+    mark_nested_modules(state.class_index)
+
+    # Create module and class index
+    index = Empty()
+    index.classes = state.class_index
+    index.pages = state.page_index
+    for file in ['modules.html', 'classes.html', 'pages.html']:
+        template = env.get_template(file)
+        rendered = template.render(index=index, FILENAME=file, **config)
+        with open(os.path.join(config['OUTPUT'], file), 'wb') as f:
+            f.write(rendered.encode('utf-8'))
+            # Add back a trailing newline so we don't need to bother with
+            # patching test files to include a trailing newline to make Git
+            # happy
+            # TODO could keep_trailing_newline fix this better?
+            f.write(b'\n')
 
     # Create index.html
     # TODO: use actual reST source and have this just as a fallback
diff --git a/documentation/templates/python/base-index.html b/documentation/templates/python/base-index.html
new file mode 100644 (file)
index 0000000..ce4c343
--- /dev/null
@@ -0,0 +1,20 @@
+{% extends 'base.html' %}
+
+{% block main %}
+        <script>
+        function toggle(e) {
+            e.parentElement.className = e.parentElement.className == 'm-doc-collapsible' ?
+                'm-doc-expansible' : 'm-doc-collapsible';
+            return false;
+        }
+        /* Collapse all nodes marked as such. Doing it via JS instead of
+           directly in markup so disabling it doesn't harm usability. The list
+           is somehow regenerated on every iteration and shrinks as I change
+           the classes. It's not documented anywhere and I'm not sure if this
+           is the same across browsers, so I am going backwards in that list to
+           be sure. */
+        var collapsed = document.getElementsByClassName("collapsed");
+        for(var i = collapsed.length - 1; i >= 0; --i)
+            collapsed[i].className = 'm-doc-expansible';
+        </script>
+{% endblock %}
diff --git a/documentation/templates/python/classes.html b/documentation/templates/python/classes.html
new file mode 100644 (file)
index 0000000..af1d68b
--- /dev/null
@@ -0,0 +1,21 @@
+{% set navbar_current = 'classes' %}
+{% extends 'base-index.html' %}
+
+{% block main %}
+        <h1>Classes</h2>
+        <ul class="m-doc">
+          {% for i in index.classes recursive %}
+          {% if i.children %}
+          <li class="m-doc-collapsible{% if loop.depth > CLASS_INDEX_EXPAND_LEVELS or (i.kind != 'module' and not CLASS_INDEX_EXPAND_INNER) %} collapsed{% endif %}">
+            <a href="#" onclick="return toggle(this)">{{ i.kind }}</a> <a href="{{ i.url }}" class="m-doc">{{ i.name }}</a> <span class="m-doc">{{ i.brief }}</span>
+            <ul class="m-doc">
+{{ loop(i.children)|indent(4, true) }}
+            </ul>
+          </li>
+          {% else %}
+          <li>{{ i.kind }} <a href="{{ i.url }}" class="m-doc">{{ i.name }}</a>{% if i.is_final %} <span class="m-label m-flat m-warning">final</span>{% endif %} <span class="m-doc">{{ i.brief }}</span></li>
+          {% endif %}
+          {% endfor %}
+        </ul>
+{{ super() -}}
+{% endblock %}
diff --git a/documentation/templates/python/modules.html b/documentation/templates/python/modules.html
new file mode 100644 (file)
index 0000000..2a83347
--- /dev/null
@@ -0,0 +1,21 @@
+{% set navbar_current = 'modules' %}
+{% extends 'base-index.html' %}
+
+{% block main %}
+        <h1>Modules</h2>
+        <ul class="m-doc">
+          {% for i in index.classes|selectattr('kind', 'equalto', 'module') recursive %}
+          {% if i.children %}
+          <li class="m-doc-collapsible">
+            <a href="#" onclick="return toggle(this)">{{ i.kind }}</a> <a href="{{ i.url }}" class="m-doc">{{ i.name }}</a> <span class="m-doc">{{ i.brief }}</span>
+            <ul class="m-doc">
+{{ loop(i.children|selectattr('kind', 'equalto', 'module'))|indent(4, true) }}
+            </ul>
+          </li>
+          {% else %}
+          <li>{{ i.kind }} <a href="{{ i.url }}" class="m-doc">{{ i.name }}</a> <span class="m-doc">{{ i.brief }}</span></li>
+          {% endif %}
+          {% endfor %}
+        </ul>
+{{ super() -}}
+{% endblock %}
diff --git a/documentation/templates/python/pages.html b/documentation/templates/python/pages.html
new file mode 100644 (file)
index 0000000..e109dfd
--- /dev/null
@@ -0,0 +1,21 @@
+{% set navbar_current = 'pages' %}
+{% extends 'base-index.html' %}
+
+{% block main %}
+        <h1>Pages</h2>
+        <ul class="m-doc">
+          {% for i in index.pages recursive %}
+          {% if i.children %}
+          <li class="m-doc-collapsible">
+            <a href="#" onclick="return toggle(this)"></a><a href="{{ i.url }}" class="m-doc">{{ i.name }}</a> <span class="m-doc">{{ i.brief }}</span>
+            <ul class="m-doc">
+{{ loop(i.children)|indent(4, true) }}
+            </ul>
+          </li>
+          {% else %}
+          <li><a href="{{ i.url }}" class="m-doc">{{ i.name }}</a> <span class="m-doc">{{ i.brief }}</span></li>
+          {% endif %}
+          {% endfor %}
+        </ul>
+{{ super() -}}
+{% endblock %}
diff --git a/documentation/test_python/inspect_string/classes.html b/documentation/test_python/inspect_string/classes.html
new file mode 100644 (file)
index 0000000..e736647
--- /dev/null
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>My Python Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+      <div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
+        <a id="m-navbar-show" href="#navigation" title="Show navigation"></a>
+        <a id="m-navbar-hide" href="#" title="Hide navigation"></a>
+      </div>
+      <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+        <div class="m-row">
+          <ol class="m-col-t-12 m-col-m-none">
+            <li><a href="modules.html">Modules</a></li>
+            <li><a href="classes.html" id="m-navbar-current">Classes</a></li>
+          </ol>
+        </div>
+      </div>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>Classes</h2>
+        <ul class="m-doc">
+          <li class="m-doc-collapsible">
+            <a href="#" onclick="return toggle(this)">module</a> <a href="inspect_string.html" class="m-doc">inspect_string</a> <span class="m-doc">A module</span>
+            <ul class="m-doc">
+              <li>module <a href="inspect_string.another_module.html" class="m-doc">another_module</a> <span class="m-doc">Another module</span></li>
+              <li>module <a href="inspect_string.subpackage.html" class="m-doc">subpackage</a> <span class="m-doc">A subpackage</span></li>
+              <li class="m-doc-collapsible collapsed">
+                <a href="#" onclick="return toggle(this)">class</a> <a href="inspect_string.Foo.html" class="m-doc">Foo</a> <span class="m-doc">The foo class</span>
+                <ul class="m-doc">
+                  <li>class <a href="inspect_string.Foo.Subclass.html" class="m-doc">Subclass</a> <span class="m-doc">A subclass of Foo</span></li>
+                </ul>
+              </li>
+              <li>class <a href="inspect_string.Specials.html" class="m-doc">Specials</a> <span class="m-doc">Special class members</span></li>
+            </ul>
+          </li>
+        </ul>
+        <script>
+        function toggle(e) {
+            e.parentElement.className = e.parentElement.className == 'm-doc-collapsible' ?
+                'm-doc-expansible' : 'm-doc-collapsible';
+            return false;
+        }
+        /* Collapse all nodes marked as such. Doing it via JS instead of
+           directly in markup so disabling it doesn't harm usability. The list
+           is somehow regenerated on every iteration and shrinks as I change
+           the classes. It's not documented anywhere and I'm not sure if this
+           is the same across browsers, so I am going backwards in that list to
+           be sure. */
+        var collapsed = document.getElementsByClassName("collapsed");
+        for(var i = collapsed.length - 1; i >= 0; --i)
+            collapsed[i].className = 'm-doc-expansible';
+        </script>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
index 234cc9a4cc4db8af05d1fa52222e7af5891590f5..206fba512b238368ed6108f93b6acb9b84e30d6b 100644 (file)
   <div class="m-container">
     <div class="m-row">
       <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+      <div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
+        <a id="m-navbar-show" href="#navigation" title="Show navigation"></a>
+        <a id="m-navbar-hide" href="#" title="Hide navigation"></a>
+      </div>
+      <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+        <div class="m-row">
+          <ol class="m-col-t-12 m-col-m-none">
+            <li><a href="modules.html">Modules</a></li>
+            <li><a href="classes.html">Classes</a></li>
+          </ol>
+        </div>
+      </div>
     </div>
   </div>
 </nav></header>
index 23e86741d266e21337fa62c350b2ec83565b89f4..65e68fe4fa797ac48725133e3144e01cbc08b8e9 100644 (file)
   <div class="m-container">
     <div class="m-row">
       <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+      <div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
+        <a id="m-navbar-show" href="#navigation" title="Show navigation"></a>
+        <a id="m-navbar-hide" href="#" title="Hide navigation"></a>
+      </div>
+      <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+        <div class="m-row">
+          <ol class="m-col-t-12 m-col-m-none">
+            <li><a href="modules.html">Modules</a></li>
+            <li><a href="classes.html">Classes</a></li>
+          </ol>
+        </div>
+      </div>
     </div>
   </div>
 </nav></header>
index 78053490e03d68c37024e4101b3c5499b37a98d3..30ba8504ae1488281a16180edbe5f92e9a8a8ec0 100644 (file)
   <div class="m-container">
     <div class="m-row">
       <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+      <div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
+        <a id="m-navbar-show" href="#navigation" title="Show navigation"></a>
+        <a id="m-navbar-hide" href="#" title="Hide navigation"></a>
+      </div>
+      <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+        <div class="m-row">
+          <ol class="m-col-t-12 m-col-m-none">
+            <li><a href="modules.html">Modules</a></li>
+            <li><a href="classes.html">Classes</a></li>
+          </ol>
+        </div>
+      </div>
     </div>
   </div>
 </nav></header>
index 1687bf519e2f8f68b4b17ca5c93ce85ea1362465..72155565b6239a8d09c9173fd1e2f7fd8b27f108 100644 (file)
   <div class="m-container">
     <div class="m-row">
       <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+      <div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
+        <a id="m-navbar-show" href="#navigation" title="Show navigation"></a>
+        <a id="m-navbar-hide" href="#" title="Hide navigation"></a>
+      </div>
+      <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+        <div class="m-row">
+          <ol class="m-col-t-12 m-col-m-none">
+            <li><a href="modules.html">Modules</a></li>
+            <li><a href="classes.html">Classes</a></li>
+          </ol>
+        </div>
+      </div>
     </div>
   </div>
 </nav></header>
diff --git a/documentation/test_python/inspect_string/modules.html b/documentation/test_python/inspect_string/modules.html
new file mode 100644 (file)
index 0000000..c7b32cd
--- /dev/null
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>My Python Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+      <div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
+        <a id="m-navbar-show" href="#navigation" title="Show navigation"></a>
+        <a id="m-navbar-hide" href="#" title="Hide navigation"></a>
+      </div>
+      <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+        <div class="m-row">
+          <ol class="m-col-t-12 m-col-m-none">
+            <li><a href="modules.html" id="m-navbar-current">Modules</a></li>
+            <li><a href="classes.html">Classes</a></li>
+          </ol>
+        </div>
+      </div>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>Modules</h2>
+        <ul class="m-doc">
+          <li class="m-doc-collapsible">
+            <a href="#" onclick="return toggle(this)">module</a> <a href="inspect_string.html" class="m-doc">inspect_string</a> <span class="m-doc">A module</span>
+            <ul class="m-doc">
+              <li>module <a href="inspect_string.another_module.html" class="m-doc">another_module</a> <span class="m-doc">Another module</span></li>
+              <li>module <a href="inspect_string.subpackage.html" class="m-doc">subpackage</a> <span class="m-doc">A subpackage</span></li>
+            </ul>
+          </li>
+        </ul>
+        <script>
+        function toggle(e) {
+            e.parentElement.className = e.parentElement.className == 'm-doc-collapsible' ?
+                'm-doc-expansible' : 'm-doc-collapsible';
+            return false;
+        }
+        /* Collapse all nodes marked as such. Doing it via JS instead of
+           directly in markup so disabling it doesn't harm usability. The list
+           is somehow regenerated on every iteration and shrinks as I change
+           the classes. It's not documented anywhere and I'm not sure if this
+           is the same across browsers, so I am going backwards in that list to
+           be sure. */
+        var collapsed = document.getElementsByClassName("collapsed");
+        for(var i = collapsed.length - 1; i >= 0; --i)
+            collapsed[i].className = 'm-doc-expansible';
+        </script>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
index e483e76f563d72bfcd1a8994f865d4d80758501f..4457bc4a2779060a7db70f0b939a170319fed0c0 100644 (file)
@@ -36,12 +36,19 @@ class String(BaseTestCase):
         super().__init__(__file__, 'string', *args, **kwargs)
 
     def test(self):
-        self.run_python()
+        self.run_python({
+            'LINKS_NAVBAR1': [
+                ('Modules', 'modules', []),
+                ('Classes', 'classes', [])],
+        })
         self.assertEqual(*self.actual_expected_contents('inspect_string.html'))
         self.assertEqual(*self.actual_expected_contents('inspect_string.another_module.html'))
         self.assertEqual(*self.actual_expected_contents('inspect_string.Foo.html'))
         self.assertEqual(*self.actual_expected_contents('inspect_string.Specials.html'))
 
+        self.assertEqual(*self.actual_expected_contents('classes.html'))
+        self.assertEqual(*self.actual_expected_contents('modules.html'))
+
 class Object(BaseTestCase):
     def __init__(self, *args, **kwargs):
         super().__init__(__file__, 'object', *args, **kwargs)
@@ -52,6 +59,9 @@ class Object(BaseTestCase):
         sys.path.append(os.path.join(os.path.dirname(self.path), 'inspect_string'))
         import inspect_string
         self.run_python({
+            'LINKS_NAVBAR1': [
+                ('Modules', 'modules', []),
+                ('Classes', 'classes', [])],
             'INPUT_MODULES': [inspect_string]
         })
 
@@ -61,6 +71,9 @@ class Object(BaseTestCase):
         self.assertEqual(*self.actual_expected_contents('inspect_string.Foo.html', '../inspect_string/inspect_string.Foo.html'))
         self.assertEqual(*self.actual_expected_contents('inspect_string.Specials.html', '../inspect_string/inspect_string.Specials.html'))
 
+        self.assertEqual(*self.actual_expected_contents('classes.html', '../inspect_string/classes.html'))
+        self.assertEqual(*self.actual_expected_contents('modules.html', '../inspect_string/modules.html'))
+
 class AllProperty(BaseTestCase):
     def __init__(self, *args, **kwargs):
         super().__init__(__file__, 'all_property', *args, **kwargs)