161 lines
5.9 KiB
Python
161 lines
5.9 KiB
Python
"""Inspired by https://github.com/pandas-dev/pydata-sphinx-theme
|
|
|
|
BSD 3-Clause License
|
|
|
|
Copyright (c) 2018, pandas
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are met:
|
|
|
|
* Redistributions of source code must retain the above copyright notice, this
|
|
list of conditions and the following disclaimer.
|
|
|
|
* Redistributions in binary form must reproduce the above copyright notice,
|
|
this list of conditions and the following disclaimer in the documentation
|
|
and/or other materials provided with the distribution.
|
|
|
|
* Neither the name of the copyright holder nor the names of its
|
|
contributors may be used to endorse or promote products derived from
|
|
this software without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
"""
|
|
|
|
import docutils
|
|
|
|
|
|
def add_toctree_functions(app, pagename, templatename, context, doctree):
|
|
"""Add functions so Jinja templates can add toctree objects.
|
|
|
|
This converts the docutils nodes into a nested dictionary that Jinja can
|
|
use in our templating.
|
|
"""
|
|
from sphinx.environment.adapters.toctree import TocTree
|
|
|
|
def get_nav_object(maxdepth=None, collapse=True, numbered=False, **kwargs):
|
|
"""Return a list of nav links that can be accessed from Jinja.
|
|
|
|
Parameters
|
|
----------
|
|
maxdepth: int
|
|
How many layers of TocTree will be returned
|
|
collapse: bool
|
|
Whether to only include sub-pages of the currently-active page,
|
|
instead of sub-pages of all top-level pages of the site.
|
|
numbered: bool
|
|
Whether to add section number to title
|
|
kwargs: key/val pairs
|
|
Passed to the `TocTree.get_toctree_for` Sphinx method
|
|
"""
|
|
# The TocTree will contain the full site TocTree including sub-pages.
|
|
# "collapse=True" collapses sub-pages of non-active TOC pages.
|
|
# maxdepth controls how many TOC levels are returned
|
|
toctree = TocTree(app.env).get_toctree_for(
|
|
pagename, app.builder, collapse=collapse, maxdepth=maxdepth, **kwargs
|
|
)
|
|
# If no toctree is defined (AKA a single-page site), skip this
|
|
if toctree is None:
|
|
return []
|
|
|
|
# toctree has this structure
|
|
# <caption>
|
|
# <bullet_list>
|
|
# <list_item classes="toctree-l1">
|
|
# <list_item classes="toctree-l1">
|
|
# `list_item`s are the actual TOC links and are the only thing we want
|
|
toc_items = [
|
|
item
|
|
for child in toctree.children
|
|
for item in child
|
|
if isinstance(item, docutils.nodes.list_item)
|
|
]
|
|
|
|
# Now convert our docutils nodes into dicts that Jinja can use
|
|
nav = [
|
|
docutils_node_to_jinja(child, only_pages=True, numbered=numbered)
|
|
for child in toc_items
|
|
]
|
|
|
|
return nav
|
|
|
|
context["get_nav_object"] = get_nav_object
|
|
|
|
|
|
def docutils_node_to_jinja(list_item, only_pages=False, numbered=False):
|
|
"""Convert a docutils node to a structure that can be read by Jinja.
|
|
|
|
Parameters
|
|
----------
|
|
list_item : docutils list_item node
|
|
A parent item, potentially with children, corresponding to the level
|
|
of a TocTree.
|
|
only_pages : bool
|
|
Only include items for full pages in the output dictionary. Exclude
|
|
anchor links (TOC items with a URL that starts with #)
|
|
numbered: bool
|
|
Whether to add section number to title
|
|
|
|
Returns
|
|
-------
|
|
nav : dict
|
|
The TocTree, converted into a dictionary with key/values that work
|
|
within Jinja.
|
|
"""
|
|
if not list_item.children:
|
|
return None
|
|
|
|
# We assume this structure of a list item:
|
|
# <list_item>
|
|
# <compact_paragraph >
|
|
# <reference> <-- the thing we want
|
|
reference = list_item.children[0].children[0]
|
|
title = reference.astext()
|
|
url = reference.attributes["refuri"]
|
|
active = "current" in list_item.attributes["classes"]
|
|
|
|
secnumber = reference.attributes.get("secnumber", None)
|
|
if numbered and secnumber is not None:
|
|
secnumber = ".".join(str(n) for n in secnumber)
|
|
title = f"{secnumber}. {title}"
|
|
|
|
# If we've got an anchor link, skip it if we wish
|
|
if only_pages and "#" in url:
|
|
return None
|
|
|
|
# Converting the docutils attributes into jinja-friendly objects
|
|
nav = {}
|
|
nav["title"] = title
|
|
nav["url"] = url
|
|
nav["active"] = active
|
|
|
|
# Recursively convert children as well
|
|
# If there are sub-pages for this list_item, there should be two children:
|
|
# a paragraph, and a bullet_list.
|
|
nav["children"] = []
|
|
if len(list_item.children) > 1:
|
|
# The `.children` of the bullet_list has the nodes of the sub-pages.
|
|
subpage_list = list_item.children[1].children
|
|
for sub_page in subpage_list:
|
|
child_nav = docutils_node_to_jinja(
|
|
sub_page, only_pages=only_pages, numbered=numbered
|
|
)
|
|
if child_nav is not None:
|
|
nav["children"].append(child_nav)
|
|
return nav
|
|
|
|
|
|
def setup(app):
|
|
app.connect("html-page-context", add_toctree_functions)
|
|
|
|
return {"parallel_read_safe": True, "parallel_write_safe": True}
|