sphinx_nervproject_theme/sphinx_nervproject_theme/__init__.py

170 lines
5.9 KiB
Python

from os import path
from docutils import nodes
from sphinx.environment.collectors import EnvironmentCollector
from sphinx import addnodes
from sphinx.util.osutil import relative_uri
__version__ = (2, 0, 6)
class SimpleTocTreeCollector(EnvironmentCollector):
"""A TocTree collector that saves toctrees in a simple dict.
sphinx.environment.collectors.toctree.TocTreeCollector saves
TocTree as docutils.nodes which are hard to work with...
Executed once per document/page, at sphinx's "read" phase.
Saved data example:
>>> {
>>> 'sections': [{'title': 'Demo', 'href': '#demo'}],
>>> 'toctrees': [<toctree: >]
>>> }
"""
def enable(self, app):
super().enable(app)
# env is populated from cache, if not cache create/initalize attibute
if not hasattr(app.env, 'toc_dict'):
app.env.toc_dict = {}
def clear_doc(self, app, env, docname):
env.toc_dict.pop(docname, None)
def merge_other(self, app, env, docnames, other):
for docname in docnames:
env.toc_dict[docname] = other.toc_dict[docname]
def process_doc(self, app, doctree):
docname = app.env.docname
section_nodes = [s for s in doctree if isinstance(s, nodes.section)]
# if first level is a single section,
# ignore it and use second level of sections
if len(section_nodes) == 1:
section2_nodes = [s for s in section_nodes[0]
if isinstance(s, nodes.section)]
if section2_nodes: # do not replace with level-2 sections if None
section_nodes = section2_nodes
sections = []
for node in section_nodes:
sections.append({
'title': node[0].astext(),
'href': '#{}'.format(node['ids'][0]),
})
app.env.toc_dict[docname] = {
'sections': sections,
'toctrees': doctree.traverse(addnodes.toctree)
}
def add_toctree_data(app, pagename, templatename, context, doctree):
"""Create toctree_data, used to build sidebar navigation
:param pagename: The name of the page
:type pagename: str
:param templatename: The name of the templatename
:type templatename: str
:param context: The context
:type context: dict
:param doctree: A doctree
:type doctree: docutils.nodes.document
Add to `toctree_data` to `context` that will be available on templates.
Although data is "global", it is called once per page because current
page is "highlighted", and some part of TOC might be collapsed.
:return: None
"""
# start from master_doc
master = app.env.get_doctree(app.env.config.master_doc)
# each toctree will create navigation section
res = [] # list of top level toctrees in master_doc
for tree in master.traverse(addnodes.toctree):
# special case for toctree that includes a single item
# that contains a nested toctree.
# In this case, just use the referenced toctree directly
if len(tree['entries']) == 1:
entry_docname = tree['entries'][0][1]
toctrees = app.env.toc_dict[entry_docname]['toctrees']
for tree in toctrees:
pass
current0 = False # same page might have multiple tocs
# add toc tree items, expand one more level if toctree is current page
entries = []
for title, name in tree['entries']:
if not title:
title = app.env.titles[name].astext()
current1 = (pagename == name)
children = []
if current1:
current0 = True
# if current, add another level
children = app.env.toc_dict[name]['sections']
# add page_toc for current page
entries.append({
'name': name,
'title': title,
'current': current1,
'children': children,
})
toc_docname = tree['parent'] # docname where this toc appears
title = tree['caption']
toc_caption = tree['caption']
# Anchor element is the section containing the toc,
# as the toc itself does not contain ID.
anchor_id = ''
# tree.parent is the parent docutils node.
# First parent is "compound" node toctree-wrapper,
# second parent is the section containing the toctree
toc_section = tree.parent.parent
if toc_section['ids']: # no id means toc actually not in a section
# TODO: should we be strict about toc being inside a section
anchor_id = toc_section['ids'][0]
if not title:
title = toc_section['names'][0]
# sphinx `pathto` does not play nice with anchors when
# `allow_sharp_as_current_path` is True
baseuri = app.builder.get_target_uri(pagename).rsplit('#', 1)[0]
toc_uri = app.builder.get_target_uri(toc_docname).rsplit('#', 1)[0]
toc_href = '{}#{}'.format(relative_uri(baseuri, toc_uri), anchor_id)
res.append({
'docname': toc_docname,
'href': toc_href,
'caption': toc_caption,
'title': title,
'current': current0,
'entries': entries,
})
context['toctree_data'] = res
def on_config_inited(app, conf):
exclude_files = list(app.config.get('epub_exclude_files', []))
exclude_files.append('_static/sphinx_nervproject_theme.js')
exclude_files.append('_static/sphinx_nervproject_theme.css')
exclude_files.append('_static/basic.css')
app.config.exclude_files = exclude_files
def setup(app):
app.add_env_collector(SimpleTocTreeCollector)
app.connect('html-page-context', add_toctree_data)
app.connect('config-inited', on_config_inited)
app.add_html_theme('nervproject', path.abspath(path.dirname(__file__)))
locale_path = path.join(path.abspath(path.dirname(__file__)), 'locale')
app.add_message_catalog('sphinx', locale_path)