You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
203 lines
7.6 KiB
203 lines
7.6 KiB
"""Single HTML builders."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from os import path
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
from docutils import nodes
|
|
|
|
from sphinx.builders.html import StandaloneHTMLBuilder
|
|
from sphinx.environment.adapters.toctree import global_toctree_for_doc
|
|
from sphinx.locale import __
|
|
from sphinx.util import logging
|
|
from sphinx.util.console import darkgreen
|
|
from sphinx.util.display import progress_message
|
|
from sphinx.util.nodes import inline_all_toctrees
|
|
|
|
if TYPE_CHECKING:
|
|
from docutils.nodes import Node
|
|
|
|
from sphinx.application import Sphinx
|
|
from sphinx.util.typing import ExtensionMetadata
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class SingleFileHTMLBuilder(StandaloneHTMLBuilder):
|
|
"""
|
|
A StandaloneHTMLBuilder subclass that puts the whole document tree on one
|
|
HTML page.
|
|
"""
|
|
|
|
name = 'singlehtml'
|
|
epilog = __('The HTML page is in %(outdir)s.')
|
|
|
|
copysource = False
|
|
|
|
def get_outdated_docs(self) -> str | list[str]: # type: ignore[override]
|
|
return 'all documents'
|
|
|
|
def get_target_uri(self, docname: str, typ: str | None = None) -> str:
|
|
if docname in self.env.all_docs:
|
|
# all references are on the same page...
|
|
return '#document-' + docname
|
|
else:
|
|
# chances are this is a html_additional_page
|
|
return docname + self.out_suffix
|
|
|
|
def get_relative_uri(self, from_: str, to: str, typ: str | None = None) -> str:
|
|
# ignore source
|
|
return self.get_target_uri(to, typ)
|
|
|
|
def fix_refuris(self, tree: Node) -> None:
|
|
# fix refuris with double anchor
|
|
fname = self.config.root_doc + self.out_suffix
|
|
for refnode in tree.findall(nodes.reference):
|
|
if 'refuri' not in refnode:
|
|
continue
|
|
refuri = refnode['refuri']
|
|
hashindex = refuri.find('#')
|
|
if hashindex < 0:
|
|
continue
|
|
hashindex = refuri.find('#', hashindex + 1)
|
|
if hashindex >= 0:
|
|
refnode['refuri'] = fname + refuri[hashindex:]
|
|
|
|
def _get_local_toctree(self, docname: str, collapse: bool = True, **kwargs: Any) -> str:
|
|
if isinstance(includehidden := kwargs.get('includehidden'), str):
|
|
if includehidden.lower() == 'false':
|
|
kwargs['includehidden'] = False
|
|
elif includehidden.lower() == 'true':
|
|
kwargs['includehidden'] = True
|
|
if kwargs.get('maxdepth') == '':
|
|
kwargs.pop('maxdepth')
|
|
toctree = global_toctree_for_doc(self.env, docname, self, collapse=collapse, **kwargs)
|
|
if toctree is not None:
|
|
self.fix_refuris(toctree)
|
|
return self.render_partial(toctree)['fragment']
|
|
|
|
def assemble_doctree(self) -> nodes.document:
|
|
master = self.config.root_doc
|
|
tree = self.env.get_doctree(master)
|
|
tree = inline_all_toctrees(self, set(), master, tree, darkgreen, [master])
|
|
tree['docname'] = master
|
|
self.env.resolve_references(tree, master, self)
|
|
self.fix_refuris(tree)
|
|
return tree
|
|
|
|
def assemble_toc_secnumbers(self) -> dict[str, dict[str, tuple[int, ...]]]:
|
|
# Assemble toc_secnumbers to resolve section numbers on SingleHTML.
|
|
# Merge all secnumbers to single secnumber.
|
|
#
|
|
# Note: current Sphinx has refid confliction in singlehtml mode.
|
|
# To avoid the problem, it replaces key of secnumbers to
|
|
# tuple of docname and refid.
|
|
#
|
|
# There are related codes in inline_all_toctres() and
|
|
# HTMLTranslter#add_secnumber().
|
|
new_secnumbers: dict[str, tuple[int, ...]] = {}
|
|
for docname, secnums in self.env.toc_secnumbers.items():
|
|
for id, secnum in secnums.items():
|
|
alias = f"{docname}/{id}"
|
|
new_secnumbers[alias] = secnum
|
|
|
|
return {self.config.root_doc: new_secnumbers}
|
|
|
|
def assemble_toc_fignumbers(self) -> dict[str, dict[str, dict[str, tuple[int, ...]]]]:
|
|
# Assemble toc_fignumbers to resolve figure numbers on SingleHTML.
|
|
# Merge all fignumbers to single fignumber.
|
|
#
|
|
# Note: current Sphinx has refid confliction in singlehtml mode.
|
|
# To avoid the problem, it replaces key of secnumbers to
|
|
# tuple of docname and refid.
|
|
#
|
|
# There are related codes in inline_all_toctres() and
|
|
# HTMLTranslter#add_fignumber().
|
|
new_fignumbers: dict[str, dict[str, tuple[int, ...]]] = {}
|
|
# {'foo': {'figure': {'id2': (2,), 'id1': (1,)}}, 'bar': {'figure': {'id1': (3,)}}}
|
|
for docname, fignumlist in self.env.toc_fignumbers.items():
|
|
for figtype, fignums in fignumlist.items():
|
|
alias = f"{docname}/{figtype}"
|
|
new_fignumbers.setdefault(alias, {})
|
|
for id, fignum in fignums.items():
|
|
new_fignumbers[alias][id] = fignum
|
|
|
|
return {self.config.root_doc: new_fignumbers}
|
|
|
|
def get_doc_context(self, docname: str, body: str, metatags: str) -> dict[str, Any]:
|
|
# no relation links...
|
|
toctree = global_toctree_for_doc(self.env, self.config.root_doc, self, collapse=False)
|
|
# if there is no toctree, toc is None
|
|
if toctree:
|
|
self.fix_refuris(toctree)
|
|
toc = self.render_partial(toctree)['fragment']
|
|
display_toc = True
|
|
else:
|
|
toc = ''
|
|
display_toc = False
|
|
return {
|
|
'parents': [],
|
|
'prev': None,
|
|
'next': None,
|
|
'docstitle': None,
|
|
'title': self.config.html_title,
|
|
'meta': None,
|
|
'body': body,
|
|
'metatags': metatags,
|
|
'rellinks': [],
|
|
'sourcename': '',
|
|
'toc': toc,
|
|
'display_toc': display_toc,
|
|
}
|
|
|
|
def write(self, *ignored: Any) -> None:
|
|
docnames = self.env.all_docs
|
|
|
|
with progress_message(__('preparing documents')):
|
|
self.prepare_writing(docnames) # type: ignore[arg-type]
|
|
|
|
with progress_message(__('assembling single document')):
|
|
doctree = self.assemble_doctree()
|
|
self.env.toc_secnumbers = self.assemble_toc_secnumbers()
|
|
self.env.toc_fignumbers = self.assemble_toc_fignumbers()
|
|
|
|
with progress_message(__('writing')):
|
|
self.write_doc_serialized(self.config.root_doc, doctree)
|
|
self.write_doc(self.config.root_doc, doctree)
|
|
|
|
def finish(self) -> None:
|
|
self.write_additional_files()
|
|
self.copy_image_files()
|
|
self.copy_download_files()
|
|
self.copy_static_files()
|
|
self.copy_extra_files()
|
|
self.write_buildinfo()
|
|
self.dump_inventory()
|
|
|
|
@progress_message(__('writing additional files'))
|
|
def write_additional_files(self) -> None:
|
|
# no indices or search pages are supported
|
|
|
|
# additional pages from conf.py
|
|
for pagename, template in self.config.html_additional_pages.items():
|
|
logger.info(' ' + pagename, nonl=True)
|
|
self.handle_page(pagename, {}, template)
|
|
|
|
if self.config.html_use_opensearch:
|
|
logger.info(' opensearch', nonl=True)
|
|
fn = path.join(self.outdir, '_static', 'opensearch.xml')
|
|
self.handle_page('opensearch', {}, 'opensearch.xml', outfilename=fn)
|
|
|
|
|
|
def setup(app: Sphinx) -> ExtensionMetadata:
|
|
app.setup_extension('sphinx.builders.html')
|
|
|
|
app.add_builder(SingleFileHTMLBuilder)
|
|
app.add_config_value('singlehtml_sidebars', lambda self: self.html_sidebars, 'html')
|
|
|
|
return {
|
|
'version': 'builtin',
|
|
'parallel_read_safe': True,
|
|
'parallel_write_safe': True,
|
|
}
|
|
|