Sphinx

# Source code for sphinxcontrib.serializinghtml

    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,
        }