1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
|
from __future__ import annotations
import os
import pickle
import types
from os import path
from typing import TYPE_CHECKING
from sphinx.application import ENV_PICKLE_FILENAME, Sphinx
from sphinx.builders.html import BuildInfo, StandaloneHTMLBuilder
from sphinx.locale import get_translation
from sphinx.util.osutil import SEP, copyfile, ensuredir, os_path
from sphinxcontrib.serializinghtml import jsonimpl
if TYPE_CHECKING:
from collections.abc import Sequence
from typing import Any, Protocol
class SerialisingImplementation(Protocol):
def dump(self, obj: Any, file: Any, *args: Any, **kwargs: Any) -> None: ...
def dumps(self, obj: Any, *args: Any, **kwargs: Any) -> str | bytes: ...
def load(self, file: Any, *args: Any, **kwargs: Any) -> Any: ...
def loads(self, data: Any, *args: Any, **kwargs: Any) -> Any: ...
__version__ = '2.0.0'
__version_info__ = (2, 0, 0)
package_dir = path.abspath(path.dirname(__file__))
__ = get_translation(__name__, 'console')
#: the filename for the "last build" file (for serializing builders)
LAST_BUILD_FILENAME = 'last_build'
class SerializingHTMLBuilder(StandaloneHTMLBuilder):
"""
An abstract builder that serializes the generated HTML.
"""
#: the serializing implementation to use. Set this to a module that
#: implements a `dump`, `load`, `dumps` and `loads` functions
#: (pickle, json etc.)
implementation: SerialisingImplementation
implementation_dumps_unicode = False
#: additional arguments for dump()
additional_dump_args: Sequence[Any] = ()
#: the filename for the global context file
globalcontext_filename: str = ''
supported_image_types = ['image/svg+xml', 'image/png',
'image/gif', 'image/jpeg']
def init(self) -> None:
self.build_info = BuildInfo(self.config, self.tags)
self.imagedir = '_images'
self.current_docname = ''
self.theme = None # type: ignore[assignment] # no theme necessary
self.templates = None # no template bridge necessary
self.init_templates()
self.init_highlighter()
self.init_css_files()
self.init_js_files()
self.use_index = self.get_builder_config('use_index', 'html')
def get_target_uri(self, docname: str, typ: str | None = None) -> str:
if docname == 'index':
return ''
if docname.endswith(SEP + 'index'):
return docname[:-5] # up to sep
return docname + SEP
def dump_context(self, context: dict[str, Any], filename: str | os.PathLike[str]) -> None:
context = context.copy()
if 'css_files' in context:
context['css_files'] = [css.filename for css in context['css_files']]
if 'script_files' in context:
context['script_files'] = [js.filename for js in context['script_files']]
if self.implementation_dumps_unicode:
with open(filename, 'w', encoding='utf-8') as ft:
self.implementation.dump(context, ft, *self.additional_dump_args)
else:
with open(filename, 'wb') as fb:
self.implementation.dump(context, fb, *self.additional_dump_args)
def handle_page(self, pagename: str, ctx: dict[str, Any], templatename: str = 'page.html',
outfilename: str | None = None, event_arg: Any = None) -> None:
ctx['current_page_name'] = pagename
ctx.setdefault('pathto', lambda p: p)
self.add_sidebars(pagename, ctx)
if not outfilename:
outfilename = path.join(self.outdir,
os_path(pagename) + self.out_suffix)
# we're not taking the return value here, since no template is
# actually rendered
self.app.emit('html-page-context', pagename, templatename, ctx, event_arg)
# make context object serializable
for key in list(ctx):
if isinstance(ctx[key], types.FunctionType):
del ctx[key]
ensuredir(path.dirname(outfilename))
self.dump_context(ctx, outfilename)
# if there is a source file, copy the source file for the
# "show source" link
if ctx.get('sourcename'):
source_name = path.join(self.outdir, '_sources',
os_path(ctx['sourcename']))
ensuredir(path.dirname(source_name))
copyfile(self.env.doc2path(pagename), source_name)
def handle_finish(self) -> None:
# dump the global context
outfilename = path.join(self.outdir, self.globalcontext_filename)
self.dump_context(self.globalcontext, outfilename)
# super here to dump the search index
super().handle_finish()
# copy the environment file from the doctree dir to the output dir
# as needed by the web app
copyfile(path.join(self.doctreedir, ENV_PICKLE_FILENAME),
path.join(self.outdir, ENV_PICKLE_FILENAME))
# touch 'last build' file, used by the web application to determine
# when to reload its environment and clear the cache
open(path.join(self.outdir, LAST_BUILD_FILENAME), 'w').close()
class PickleHTMLBuilder(SerializingHTMLBuilder):
"""
A Builder that dumps the generated HTML into pickle files.
"""
name = 'pickle'
epilog = __('You can now process the pickle files in %(outdir)s.')
implementation = pickle
implementation_dumps_unicode = False
additional_dump_args: tuple[Any] = (pickle.HIGHEST_PROTOCOL,)
indexer_format = pickle
indexer_dumps_unicode = False
out_suffix = '.fpickle'
globalcontext_filename = 'globalcontext.pickle'
searchindex_filename = 'searchindex.pickle'
class JSONHTMLBuilder(SerializingHTMLBuilder):
"""
A builder that dumps the generated HTML into JSON files.
"""
name = 'json'
epilog = __('You can now process the JSON files in %(outdir)s.')
implementation = jsonimpl
implementation_dumps_unicode = True
indexer_format = jsonimpl
indexer_dumps_unicode = True
out_suffix = '.fjson'
globalcontext_filename = 'globalcontext.json'
searchindex_filename = 'searchindex.json'
def setup(app: Sphinx) -> dict[str, Any]:
app.require_sphinx('5.0')
app.setup_extension('sphinx.builders.html')
app.add_builder(JSONHTMLBuilder)
app.add_builder(PickleHTMLBuilder)
app.add_message_catalog(__name__, path.join(package_dir, 'locales'))
return {
'version': __version__,
'parallel_read_safe': True,
'parallel_write_safe': True,
}
|