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
: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
'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
'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.
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 = ''
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.
# 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_]
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)):
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)):
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.
('__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 = ''
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
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)):
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
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
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
--- /dev/null
+{% 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 %}
--- /dev/null
+{% 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 %}
--- /dev/null
+{% 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 %}
--- /dev/null
+{% 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 %}
--- /dev/null
+<!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>
<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>
<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>
<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>
<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>
--- /dev/null
+<!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>
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)
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]
})
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)