""" sphinx.builders.html ~~~~~~~~~~~~~~~~~~~~ Several HTML builders. :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import html import posixpath import re import sys import warnings from hashlib import md5 from os import path from typing import Any, Dict, IO, Iterable, Iterator, List, Set, Tuple from docutils import nodes from docutils.core import publish_parts from docutils.frontend import OptionParser from docutils.io import DocTreeInput, StringOutput from docutils.nodes import Node from docutils.utils import relative_path from sphinx import package_dir, __display_version__ from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.config import Config from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning from sphinx.domains import Domain, Index, IndexEntry from sphinx.environment.adapters.asset import ImageAdapter from sphinx.environment.adapters.indexentries import IndexEntries from sphinx.environment.adapters.toctree import TocTree from sphinx.errors import ConfigError, ThemeError from sphinx.highlighting import PygmentsBridge from sphinx.locale import _, __ from sphinx.search import js_index from sphinx.theming import HTMLThemeFactory from sphinx.util import logging, progress_message, status_iterator from sphinx.util.docutils import is_html5_writer_available, new_document from sphinx.util.fileutil import copy_asset from sphinx.util.i18n import format_date from sphinx.util.inventory import InventoryFile from sphinx.util.matching import patmatch, Matcher, DOTFILES from sphinx.util.osutil import os_path, relative_uri, ensuredir, movefile, copyfile from sphinx.util.tags import Tags from sphinx.writers.html import HTMLWriter, HTMLTranslator if False: # For type annotation from typing import Type # for python3.5.1 # HTML5 Writer is avialable or not if is_html5_writer_available(): from sphinx.writers.html5 import HTML5Translator html5_ready = True else: html5_ready = False #: the filename for the inventory of objects INVENTORY_FILENAME = 'objects.inv' logger = logging.getLogger(__name__) return_codes_re = re.compile('[\r\n]+') def get_stable_hash(obj: Any) -> str: """ Return a stable hash for a Python data structure. We can't just use the md5 of str(obj) since for example dictionary items are enumerated in unpredictable order due to hash randomization in newer Pythons. """ if isinstance(obj, dict): return get_stable_hash(list(obj.items())) elif isinstance(obj, (list, tuple)): obj = sorted(get_stable_hash(o) for o in obj) return md5(str(obj).encode()).hexdigest() class Stylesheet(str): """A metadata of stylesheet. To keep compatibility with old themes, an instance of stylesheet behaves as its filename (str). """ attributes = None # type: Dict[str, str] filename = None # type: str def __new__(cls, filename: str, *args: str, **attributes: str) -> "Stylesheet": self = str.__new__(cls, filename) # type: ignore self.filename = filename self.attributes = attributes self.attributes.setdefault('rel', 'stylesheet') self.attributes.setdefault('type', 'text/css') if args: # old style arguments (rel, title) self.attributes['rel'] = args[0] self.attributes['title'] = args[1] return self class JSContainer(list): """The container for JavaScript scripts.""" def insert(self, index: int, obj: str) -> None: warnings.warn('To modify script_files in the theme is deprecated. ' 'Please insert a ' % (' '.join(attrs), body) context['js_tag'] = js_tag def validate_math_renderer(app: Sphinx) -> None: if app.builder.format != 'html': return name = app.builder.math_renderer_name # type: ignore if name is None: raise ConfigError(__('Many math_renderers are registered. ' 'But no math_renderer is selected.')) elif name not in app.registry.html_inline_math_renderers: raise ConfigError(__('Unknown math_renderer %r is given.') % name) def validate_html_extra_path(app: Sphinx, config: Config) -> None: """Check html_extra_paths setting.""" for entry in config.html_extra_path[:]: extra_path = path.normpath(path.join(app.confdir, entry)) if not path.exists(extra_path): logger.warning(__('html_extra_path entry %r does not exist'), entry) config.html_extra_path.remove(entry) elif (path.splitdrive(app.outdir)[0] == path.splitdrive(extra_path)[0] and path.commonpath([app.outdir, extra_path]) == app.outdir): logger.warning(__('html_extra_path entry %r is placed inside outdir'), entry) config.html_extra_path.remove(entry) def validate_html_static_path(app: Sphinx, config: Config) -> None: """Check html_static_paths setting.""" for entry in config.html_static_path[:]: static_path = path.normpath(path.join(app.confdir, entry)) if not path.exists(static_path): logger.warning(__('html_static_path entry %r does not exist'), entry) config.html_static_path.remove(entry) elif (path.splitdrive(app.outdir)[0] == path.splitdrive(static_path)[0] and path.commonpath([app.outdir, static_path]) == app.outdir): logger.warning(__('html_static_path entry %r is placed inside outdir'), entry) config.html_static_path.remove(entry) def validate_html_logo(app: Sphinx, config: Config) -> None: """Check html_logo setting.""" if config.html_logo and not path.isfile(path.join(app.confdir, config.html_logo)): logger.warning(__('logo file %r does not exist'), config.html_logo) config.html_logo = None # type: ignore def validate_html_favicon(app: Sphinx, config: Config) -> None: """Check html_favicon setting.""" if config.html_favicon and not path.isfile(path.join(app.confdir, config.html_favicon)): logger.warning(__('favicon file %r does not exist'), config.html_favicon) config.html_favicon = None # type: ignore # for compatibility import sphinx.builders.dirhtml # NOQA import sphinx.builders.singlehtml # NOQA import sphinxcontrib.serializinghtml # NOQA def setup(app: Sphinx) -> Dict[str, Any]: # builders app.add_builder(StandaloneHTMLBuilder) # config values app.add_config_value('html_theme', 'alabaster', 'html') app.add_config_value('html_theme_path', [], 'html') app.add_config_value('html_theme_options', {}, 'html') app.add_config_value('html_title', lambda self: _('%s %s documentation') % (self.project, self.release), 'html', [str]) app.add_config_value('html_short_title', lambda self: self.html_title, 'html') app.add_config_value('html_style', None, 'html', [str]) app.add_config_value('html_logo', None, 'html', [str]) app.add_config_value('html_favicon', None, 'html', [str]) app.add_config_value('html_css_files', [], 'html') app.add_config_value('html_js_files', [], 'html') app.add_config_value('html_static_path', [], 'html') app.add_config_value('html_extra_path', [], 'html') app.add_config_value('html_last_updated_fmt', None, 'html', [str]) app.add_config_value('html_sidebars', {}, 'html') app.add_config_value('html_additional_pages', {}, 'html') app.add_config_value('html_domain_indices', True, 'html', [list]) app.add_config_value('html_add_permalinks', 'ΒΆ', 'html') app.add_config_value('html_use_index', True, 'html') app.add_config_value('html_split_index', False, 'html') app.add_config_value('html_copy_source', True, 'html') app.add_config_value('html_show_sourcelink', True, 'html') app.add_config_value('html_sourcelink_suffix', '.txt', 'html') app.add_config_value('html_use_opensearch', '', 'html') app.add_config_value('html_file_suffix', None, 'html', [str]) app.add_config_value('html_link_suffix', None, 'html', [str]) app.add_config_value('html_show_copyright', True, 'html') app.add_config_value('html_show_sphinx', True, 'html') app.add_config_value('html_context', {}, 'html') app.add_config_value('html_output_encoding', 'utf-8', 'html') app.add_config_value('html_compact_lists', True, 'html') app.add_config_value('html_secnumber_suffix', '. ', 'html') app.add_config_value('html_search_language', None, 'html', [str]) app.add_config_value('html_search_options', {}, 'html') app.add_config_value('html_search_scorer', '', None) app.add_config_value('html_scaled_image_link', True, 'html') app.add_config_value('html_baseurl', '', 'html') app.add_config_value('html_math_renderer', None, 'env') app.add_config_value('html4_writer', False, 'html') # event handlers app.connect('config-inited', convert_html_css_files) app.connect('config-inited', convert_html_js_files) app.connect('config-inited', validate_html_extra_path) app.connect('config-inited', validate_html_static_path) app.connect('config-inited', validate_html_logo) app.connect('config-inited', validate_html_favicon) app.connect('builder-inited', validate_math_renderer) app.connect('html-page-context', setup_js_tag_helper) # load default math renderer app.setup_extension('sphinx.ext.mathjax') return { 'version': 'builtin', 'parallel_read_safe': True, 'parallel_write_safe': True, }