summaryrefslogtreecommitdiff
path: root/sphinx/builders/html.py
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/builders/html.py')
-rw-r--r--sphinx/builders/html.py203
1 files changed, 72 insertions, 131 deletions
diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py
index 81c64d445..f5b8fa0f4 100644
--- a/sphinx/builders/html.py
+++ b/sphinx/builders/html.py
@@ -15,16 +15,21 @@ import sys
import warnings
from hashlib import md5
from os import path
+from typing import Any, Dict, IO, Iterable, Iterator, List, Set, Type, 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 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
@@ -41,16 +46,9 @@ 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 Any, Dict, IO, Iterable, Iterator, List, Set, Type, Tuple # NOQA
- from sphinx.application import Sphinx # NOQA
- from sphinx.config import Config # NOQA
- from sphinx.domains import Domain, Index, IndexEntry # NOQA
- from sphinx.util.tags import Tags # NOQA
-
# HTML5 Writer is avialable or not
if is_html5_writer_available():
from sphinx.writers.html5 import HTML5Translator
@@ -65,8 +63,7 @@ logger = logging.getLogger(__name__)
return_codes_re = re.compile('[\r\n]+')
-def get_stable_hash(obj):
- # type: (Any) -> str
+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
@@ -89,8 +86,7 @@ class Stylesheet(str):
attributes = None # type: Dict[str, str]
filename = None # type: str
- def __new__(cls, filename, *args, **attributes):
- # type: (str, str, str) -> None
+ def __new__(cls, filename: str, *args: str, **attributes: str) -> None:
self = str.__new__(cls, filename) # type: ignore
self.filename = filename
self.attributes = attributes
@@ -113,8 +109,7 @@ class JavaScript(str):
attributes = None # type: Dict[str, str]
filename = None # type: str
- def __new__(cls, filename, **attributes):
- # type: (str, **str) -> None
+ def __new__(cls, filename: str, **attributes: str) -> None:
self = str.__new__(cls, filename) # type: ignore
self.filename = filename
self.attributes = attributes
@@ -131,8 +126,7 @@ class BuildInfo:
"""
@classmethod
- def load(cls, f):
- # type: (IO) -> BuildInfo
+ def load(cls, f: IO) -> "BuildInfo":
try:
lines = f.readlines()
assert lines[0].rstrip() == '# Sphinx build info version 1'
@@ -146,8 +140,7 @@ class BuildInfo:
except Exception as exc:
raise ValueError(__('build info file is broken: %r') % exc)
- def __init__(self, config=None, tags=None, config_categories=[]):
- # type: (Config, Tags, List[str]) -> None
+ def __init__(self, config: Config = None, tags: Tags = None, config_categories: List[str] = []) -> None: # NOQA
self.config_hash = ''
self.tags_hash = ''
@@ -158,13 +151,11 @@ class BuildInfo:
if tags:
self.tags_hash = get_stable_hash(sorted(tags))
- def __eq__(self, other): # type: ignore
- # type: (BuildInfo) -> bool
+ def __eq__(self, other: "BuildInfo") -> bool: # type: ignore
return (self.config_hash == other.config_hash and
self.tags_hash == other.tags_hash)
- def dump(self, f):
- # type: (IO) -> None
+ def dump(self, f: IO) -> None:
f.write('# Sphinx build info version 1\n'
'# This file hashes the configuration used when building these files.'
' When it is not found, a full rebuild will be done.\n'
@@ -204,8 +195,7 @@ class StandaloneHTMLBuilder(Builder):
imgpath = None # type: str
domain_indices = [] # type: List[Tuple[str, Type[Index], List[Tuple[str, List[IndexEntry]]], bool]] # NOQA
- def __init__(self, app):
- # type: (Sphinx) -> None
+ def __init__(self, app: Sphinx) -> None:
super().__init__(app)
# CSS files
@@ -214,8 +204,7 @@ class StandaloneHTMLBuilder(Builder):
# JS files
self.script_files = [] # type: List[JavaScript]
- def init(self):
- # type: () -> None
+ def init(self) -> None:
self.build_info = self.create_build_info()
# basename of images directory
self.imagedir = '_images'
@@ -241,12 +230,10 @@ class StandaloneHTMLBuilder(Builder):
self.use_index = self.get_builder_config('use_index', 'html')
- def create_build_info(self):
- # type: () -> BuildInfo
+ def create_build_info(self) -> BuildInfo:
return BuildInfo(self.config, self.tags, ['html'])
- def _get_translations_js(self):
- # type: () -> str
+ def _get_translations_js(self) -> str:
candidates = [path.join(dir, self.config.language,
'LC_MESSAGES', 'sphinx.js')
for dir in self.config.locale_dirs] + \
@@ -260,12 +247,10 @@ class StandaloneHTMLBuilder(Builder):
return jsfile
return None
- def get_theme_config(self):
- # type: () -> Tuple[str, Dict]
+ def get_theme_config(self) -> Tuple[str, Dict]:
return self.config.html_theme, self.config.html_theme_options
- def init_templates(self):
- # type: () -> None
+ def init_templates(self) -> None:
theme_factory = HTMLThemeFactory(self.app)
themename, themeoptions = self.get_theme_config()
self.theme = theme_factory.create(themename)
@@ -273,8 +258,7 @@ class StandaloneHTMLBuilder(Builder):
self.create_template_bridge()
self.templates.init(self, self.theme)
- def init_highlighter(self):
- # type: () -> None
+ def init_highlighter(self) -> None:
# determine Pygments style and create the highlighter
if self.config.pygments_style is not None:
style = self.config.pygments_style
@@ -284,23 +268,20 @@ class StandaloneHTMLBuilder(Builder):
style = 'sphinx'
self.highlighter = PygmentsBridge('html', style)
- def init_css_files(self):
- # type: () -> None
+ def init_css_files(self) -> None:
for filename, attrs in self.app.registry.css_files:
self.add_css_file(filename, **attrs)
for filename, attrs in self.get_builder_config('css_files', 'html'):
self.add_css_file(filename, **attrs)
- def add_css_file(self, filename, **kwargs):
- # type: (str, **str) -> None
+ def add_css_file(self, filename: str, **kwargs: str) -> None:
if '://' not in filename:
filename = posixpath.join('_static', filename)
self.css_files.append(Stylesheet(filename, **kwargs)) # type: ignore
- def init_js_files(self):
- # type: () -> None
+ def init_js_files(self) -> None:
self.add_js_file('jquery.js')
self.add_js_file('underscore.js')
self.add_js_file('doctools.js')
@@ -315,24 +296,21 @@ class StandaloneHTMLBuilder(Builder):
if self.config.language and self._get_translations_js():
self.add_js_file('translations.js')
- def add_js_file(self, filename, **kwargs):
- # type: (str, **str) -> None
+ def add_js_file(self, filename: str, **kwargs: str) -> None:
if filename and '://' not in filename:
filename = posixpath.join('_static', filename)
self.script_files.append(JavaScript(filename, **kwargs))
@property
- def default_translator_class(self): # type: ignore
- # type: () -> Type[nodes.NodeVisitor]
+ def default_translator_class(self) -> Type[nodes.NodeVisitor]: # type: ignore
if not html5_ready or self.config.html4_writer:
return HTMLTranslator
else:
return HTML5Translator
@property
- def math_renderer_name(self):
- # type: () -> str
+ def math_renderer_name(self) -> str:
name = self.get_builder_config('math_renderer', 'html')
if name is not None:
# use given name
@@ -351,8 +329,7 @@ class StandaloneHTMLBuilder(Builder):
# many math_renderers are registered. can't choose automatically!
return None
- def get_outdated_docs(self):
- # type: () -> Iterator[str]
+ def get_outdated_docs(self) -> Iterator[str]:
try:
with open(path.join(self.outdir, '.buildinfo')) as fp:
buildinfo = BuildInfo.load(fp)
@@ -388,12 +365,10 @@ class StandaloneHTMLBuilder(Builder):
# source doesn't exist anymore
pass
- def get_asset_paths(self):
- # type: () -> List[str]
+ def get_asset_paths(self) -> List[str]:
return self.config.html_extra_path + self.config.html_static_path
- def render_partial(self, node):
- # type: (nodes.Node) -> Dict[str, str]
+ def render_partial(self, node: Node) -> Dict[str, str]:
"""Utility: Render a lone doctree node."""
if node is None:
return {'fragment': ''}
@@ -407,8 +382,7 @@ class StandaloneHTMLBuilder(Builder):
settings_overrides={'output_encoding': 'unicode'},
source=doc)
- def prepare_writing(self, docnames):
- # type: (Set[str]) -> None
+ def prepare_writing(self, docnames: Set[str]) -> None:
# create the search indexer
self.indexer = None
if self.search:
@@ -517,8 +491,7 @@ class StandaloneHTMLBuilder(Builder):
self.theme.get_options(self.theme_options).items())
self.globalcontext.update(self.config.html_context)
- def get_doc_context(self, docname, body, metatags):
- # type: (str, str, str) -> Dict[str, Any]
+ def get_doc_context(self, docname: str, body: str, metatags: str) -> Dict[str, Any]:
"""Collect items for the template context of a page."""
# find out relations
prev = next = None
@@ -598,8 +571,7 @@ class StandaloneHTMLBuilder(Builder):
'page_source_suffix': source_suffix,
}
- def write_doc(self, docname, doctree):
- # type: (str, nodes.document) -> None
+ def write_doc(self, docname: str, doctree: nodes.document) -> None:
destination = StringOutput(encoding='utf-8')
doctree.settings = self.docsettings
@@ -616,16 +588,14 @@ class StandaloneHTMLBuilder(Builder):
ctx = self.get_doc_context(docname, body, metatags)
self.handle_page(docname, ctx, event_arg=doctree)
- def write_doc_serialized(self, docname, doctree):
- # type: (str, nodes.document) -> None
+ def write_doc_serialized(self, docname: str, doctree: nodes.document) -> None:
self.imgpath = relative_uri(self.get_target_uri(docname), self.imagedir)
self.post_process_images(doctree)
title_node = self.env.longtitles.get(docname)
title = title_node and self.render_partial(title_node)['title'] or ''
self.index_page(docname, doctree, title)
- def finish(self):
- # type: () -> None
+ def finish(self) -> None:
self.finish_tasks.add_task(self.gen_indices)
self.finish_tasks.add_task(self.gen_additional_pages)
self.finish_tasks.add_task(self.copy_image_files)
@@ -637,8 +607,7 @@ class StandaloneHTMLBuilder(Builder):
# dump the search index
self.handle_finish()
- def gen_indices(self):
- # type: () -> None
+ def gen_indices(self) -> None:
logger.info(bold(__('generating indices...')), nonl=True)
# the global general index
@@ -650,8 +619,7 @@ class StandaloneHTMLBuilder(Builder):
logger.info('')
- def gen_additional_pages(self):
- # type: () -> None
+ def gen_additional_pages(self) -> None:
# pages from extensions
for pagelist in self.events.emit('html-collect-pages'):
for pagename, context, template in pagelist:
@@ -677,8 +645,7 @@ class StandaloneHTMLBuilder(Builder):
logger.info('')
- def write_genindex(self):
- # type: () -> None
+ def write_genindex(self) -> None:
# the total count of lines for each index letter, used to distribute
# the entries into two columns
genindex = IndexEntries(self.env).create_index(self)
@@ -707,8 +674,7 @@ class StandaloneHTMLBuilder(Builder):
else:
self.handle_page('genindex', genindexcontext, 'genindex.html')
- def write_domain_indices(self):
- # type: () -> None
+ def write_domain_indices(self) -> None:
for indexname, indexcls, content, collapse in self.domain_indices:
indexcontext = {
'indextitle': indexcls.localname,
@@ -718,8 +684,7 @@ class StandaloneHTMLBuilder(Builder):
logger.info(' ' + indexname, nonl=True)
self.handle_page(indexname, indexcontext, 'domainindex.html')
- def copy_image_files(self):
- # type: () -> None
+ def copy_image_files(self) -> None:
if self.images:
stringify_func = ImageAdapter(self.app.env).get_original_image_uri
ensuredir(path.join(self.outdir, self.imagedir))
@@ -734,11 +699,10 @@ class StandaloneHTMLBuilder(Builder):
logger.warning(__('cannot copy image file %r: %s'),
path.join(self.srcdir, src), err)
- def copy_download_files(self):
- # type: () -> None
- def to_relpath(f):
- # type: (str) -> str
+ def copy_download_files(self) -> None:
+ def to_relpath(f: str) -> str:
return relative_path(self.srcdir, f)
+
# copy downloadable files
if self.env.dlfiles:
ensuredir(path.join(self.outdir, '_downloads'))
@@ -753,8 +717,7 @@ class StandaloneHTMLBuilder(Builder):
logger.warning(__('cannot copy downloadable file %r: %s'),
path.join(self.srcdir, src), err)
- def copy_static_files(self):
- # type: () -> None
+ def copy_static_files(self) -> None:
try:
# copy static files
logger.info(bold(__('copying static files... ')), nonl=True)
@@ -818,8 +781,7 @@ class StandaloneHTMLBuilder(Builder):
except OSError as err:
logger.warning(__('cannot copy static file %r'), err)
- def copy_extra_files(self):
- # type: () -> None
+ def copy_extra_files(self) -> None:
try:
# copy html_extra_path files
logger.info(bold(__('copying extra files... ')), nonl=True)
@@ -836,22 +798,19 @@ class StandaloneHTMLBuilder(Builder):
except OSError as err:
logger.warning(__('cannot copy extra file %r'), err)
- def write_buildinfo(self):
- # type: () -> None
+ def write_buildinfo(self) -> None:
try:
with open(path.join(self.outdir, '.buildinfo'), 'w') as fp:
self.build_info.dump(fp)
except OSError as exc:
logger.warning(__('Failed to write build info file: %r'), exc)
- def cleanup(self):
- # type: () -> None
+ def cleanup(self) -> None:
# clean up theme stuff
if self.theme:
self.theme.cleanup()
- def post_process_images(self, doctree):
- # type: (nodes.Node) -> None
+ def post_process_images(self, doctree: Node) -> None:
"""Pick the best candidate for an image and link down-scaled images to
their high res version.
"""
@@ -876,8 +835,7 @@ class StandaloneHTMLBuilder(Builder):
node.replace_self(reference)
reference.append(node)
- def load_indexer(self, docnames):
- # type: (Iterable[str]) -> None
+ def load_indexer(self, docnames: Iterable[str]) -> None:
keep = set(self.env.all_docs) - set(docnames)
try:
searchindexfn = path.join(self.outdir, self.searchindex_filename)
@@ -895,8 +853,7 @@ class StandaloneHTMLBuilder(Builder):
# delete all entries for files that will be rebuilt
self.indexer.prune(keep)
- def index_page(self, pagename, doctree, title):
- # type: (str, nodes.document, str) -> None
+ def index_page(self, pagename: str, doctree: nodes.document, title: str) -> None:
# only index pages with title
if self.indexer is not None and title:
filename = self.env.doc2path(pagename, base=None)
@@ -912,22 +869,19 @@ class StandaloneHTMLBuilder(Builder):
indexer_name, indexer_name),
RemovedInSphinx40Warning)
- def _get_local_toctree(self, docname, collapse=True, **kwds):
- # type: (str, bool, Any) -> str
+ def _get_local_toctree(self, docname: str, collapse: bool = True, **kwds) -> str:
if 'includehidden' not in kwds:
kwds['includehidden'] = False
return self.render_partial(TocTree(self.env).get_toctree_for(
docname, self, collapse, **kwds))['fragment']
- def get_outfilename(self, pagename):
- # type: (str) -> str
+ def get_outfilename(self, pagename: str) -> str:
return path.join(self.outdir, os_path(pagename) + self.out_suffix)
- def add_sidebars(self, pagename, ctx):
- # type: (str, Dict) -> None
- def has_wildcard(pattern):
- # type: (str) -> bool
+ def add_sidebars(self, pagename: str, ctx: Dict) -> None:
+ def has_wildcard(pattern: str) -> bool:
return any(char in pattern for char in '*?[')
+
sidebars = None
matched = None
customsidebar = None
@@ -976,13 +930,11 @@ class StandaloneHTMLBuilder(Builder):
# --------- these are overwritten by the serialization builder
- def get_target_uri(self, docname, typ=None):
- # type: (str, str) -> str
+ def get_target_uri(self, docname: str, typ: str = None) -> str:
return docname + self.link_suffix
- def handle_page(self, pagename, addctx, templatename='page.html',
- outfilename=None, event_arg=None):
- # type: (str, Dict, str, str, Any) -> None
+ def handle_page(self, pagename: str, addctx: Dict, templatename: str = 'page.html',
+ outfilename: str = None, event_arg: Any = None) -> None:
ctx = self.globalcontext.copy()
# current_page_name is backwards compatibility
ctx['pagename'] = ctx['current_page_name'] = pagename
@@ -998,8 +950,7 @@ class StandaloneHTMLBuilder(Builder):
else:
ctx['pageurl'] = None
- def pathto(otheruri, resource=False, baseuri=default_baseuri):
- # type: (str, bool, str) -> str
+ def pathto(otheruri: str, resource: bool = False, baseuri: str = default_baseuri) -> str: # NOQA
if resource and '://' in otheruri:
# allow non-local resources given by scheme
return otheruri
@@ -1011,8 +962,7 @@ class StandaloneHTMLBuilder(Builder):
return uri
ctx['pathto'] = pathto
- def css_tag(css):
- # type: (Stylesheet) -> str
+ def css_tag(css: Stylesheet) -> str:
attrs = []
for key in sorted(css.attributes):
value = css.attributes[key]
@@ -1022,8 +972,7 @@ class StandaloneHTMLBuilder(Builder):
return '<link %s />' % ' '.join(attrs)
ctx['css_tag'] = css_tag
- def hasdoc(name):
- # type: (str) -> bool
+ def hasdoc(name: str) -> bool:
if name in self.env.all_docs:
return True
elif name == 'search' and self.search:
@@ -1071,24 +1020,21 @@ class StandaloneHTMLBuilder(Builder):
ensuredir(path.dirname(source_name))
copyfile(self.env.doc2path(pagename), source_name)
- def update_page_context(self, pagename, templatename, ctx, event_arg):
- # type: (str, str, Dict, Any) -> None
+ def update_page_context(self, pagename: str, templatename: str,
+ ctx: Dict, event_arg: Any) -> None:
pass
- def handle_finish(self):
- # type: () -> None
+ def handle_finish(self) -> None:
if self.indexer:
self.finish_tasks.add_task(self.dump_search_index)
self.finish_tasks.add_task(self.dump_inventory)
- def dump_inventory(self):
- # type: () -> None
+ def dump_inventory(self) -> None:
logger.info(bold(__('dumping object inventory... ')), nonl=True)
InventoryFile.dump(path.join(self.outdir, INVENTORY_FILENAME), self.env, self)
logger.info(__('done'))
- def dump_search_index(self):
- # type: () -> None
+ def dump_search_index(self) -> None:
logger.info(
bold(__('dumping search index in %s ... ') % self.indexer.label()),
nonl=True)
@@ -1106,8 +1052,7 @@ class StandaloneHTMLBuilder(Builder):
logger.info(__('done'))
-def convert_html_css_files(app, config):
- # type: (Sphinx, Config) -> None
+def convert_html_css_files(app: Sphinx, config: Config) -> None:
"""This converts string styled html_css_files to tuple styled one."""
html_css_files = [] # type: List[Tuple[str, Dict]]
for entry in config.html_css_files:
@@ -1124,8 +1069,7 @@ def convert_html_css_files(app, config):
config.html_css_files = html_css_files # type: ignore
-def convert_html_js_files(app, config):
- # type: (Sphinx, Config) -> None
+def convert_html_js_files(app: Sphinx, config: Config) -> None:
"""This converts string styled html_js_files to tuple styled one."""
html_js_files = [] # type: List[Tuple[str, Dict]]
for entry in config.html_js_files:
@@ -1142,16 +1086,15 @@ def convert_html_js_files(app, config):
config.html_js_files = html_js_files # type: ignore
-def setup_js_tag_helper(app, pagename, templatexname, context, doctree):
- # type: (Sphinx, str, str, Dict, nodes.Node) -> None
+def setup_js_tag_helper(app: Sphinx, pagename: str, templatexname: str,
+ context: Dict, doctree: Node) -> None:
"""Set up js_tag() template helper.
.. note:: This set up function is added to keep compatibility with webhelper.
"""
pathto = context.get('pathto')
- def js_tag(js):
- # type: (JavaScript) -> str
+ def js_tag(js: JavaScript) -> str:
attrs = []
body = ''
if isinstance(js, JavaScript):
@@ -1173,8 +1116,7 @@ def setup_js_tag_helper(app, pagename, templatexname, context, doctree):
context['js_tag'] = js_tag
-def validate_math_renderer(app):
- # type: (Sphinx) -> None
+def validate_math_renderer(app: Sphinx) -> None:
if app.builder.format != 'html':
return
@@ -1192,8 +1134,7 @@ import sphinx.builders.singlehtml # NOQA
import sphinxcontrib.serializinghtml # NOQA
-def setup(app):
- # type: (Sphinx) -> Dict[str, Any]
+def setup(app: Sphinx) -> Dict[str, Any]:
# builders
app.add_builder(StandaloneHTMLBuilder)