diff options
Diffstat (limited to 'sphinx/builders/html.py')
-rw-r--r-- | sphinx/builders/html.py | 521 |
1 files changed, 230 insertions, 291 deletions
diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index 1212229fb..e395144aa 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ sphinx.builders.html ~~~~~~~~~~~~~~~~~~~~ @@ -9,7 +8,8 @@ :license: BSD, see LICENSE for details. """ -import codecs +import html +import pickle import posixpath import re import sys @@ -25,14 +25,11 @@ from docutils.frontend import OptionParser from docutils.io import DocTreeInput, StringOutput from docutils.readers.doctree import Reader as DoctreeReader from docutils.utils import relative_path -from six import iteritems, text_type, string_types -from six.moves import cPickle as pickle from sphinx import package_dir, __display_version__ from sphinx.application import ENV_PICKLE_FILENAME from sphinx.builders import Builder -from sphinx.config import string_classes -from sphinx.deprecation import RemovedInSphinx20Warning, RemovedInSphinx30Warning +from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning from sphinx.environment.adapters.asset import ImageAdapter from sphinx.environment.adapters.indexentries import IndexEntries from sphinx.environment.adapters.toctree import TocTree @@ -51,15 +48,14 @@ from sphinx.util.matching import patmatch, Matcher, DOTFILES from sphinx.util.nodes import inline_all_toctrees from sphinx.util.osutil import SEP, os_path, relative_uri, ensuredir, \ movefile, copyfile -from sphinx.util.pycompat import htmlescape from sphinx.writers.html import HTMLWriter, HTMLTranslator if False: # For type annotation - from typing import Any, Dict, IO, Iterable, Iterator, List, Type, Tuple, Union # NOQA + from typing import Any, Dict, IO, Iterable, Iterator, List, Set, Type, Tuple, Union # NOQA from sphinx.application import Sphinx # NOQA from sphinx.config import Config # NOQA - from sphinx.domains import Domain, Index # NOQA + from sphinx.domains import Domain, Index, IndexEntry # NOQA from sphinx.util.tags import Tags # NOQA # Experimental HTML5 Writer @@ -79,7 +75,7 @@ return_codes_re = re.compile('[\r\n]+') def get_stable_hash(obj): - # type: (Any) -> unicode + # type: (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,69 +85,22 @@ def get_stable_hash(obj): return get_stable_hash(list(obj.items())) elif isinstance(obj, (list, tuple)): obj = sorted(get_stable_hash(o) for o in obj) - return md5(text_type(obj).encode('utf8')).hexdigest() + return md5(str(obj).encode()).hexdigest() -class CSSContainer(list): - """The container for stylesheets. - - To support the extensions which access the container directly, this wraps - the entry with Stylesheet class. - """ - def append(self, obj): - # type: (Union[unicode, Stylesheet]) -> None - if isinstance(obj, Stylesheet): - super(CSSContainer, self).append(obj) - else: - super(CSSContainer, self).append(Stylesheet(obj)) - - def insert(self, index, obj): - # type: (int, Union[unicode, Stylesheet]) -> None - warnings.warn('builder.css_files is deprecated. ' - 'Please use app.add_stylesheet() instead.', - RemovedInSphinx20Warning, stacklevel=2) - if isinstance(obj, Stylesheet): - super(CSSContainer, self).insert(index, obj) - else: - super(CSSContainer, self).insert(index, Stylesheet(obj)) - - def extend(self, other): # type: ignore - # type: (List[Union[unicode, Stylesheet]]) -> None - warnings.warn('builder.css_files is deprecated. ' - 'Please use app.add_stylesheet() instead.', - RemovedInSphinx20Warning, stacklevel=2) - for item in other: - self.append(item) - - def __iadd__(self, other): # type: ignore - # type: (List[Union[unicode, Stylesheet]]) -> CSSContainer - warnings.warn('builder.css_files is deprecated. ' - 'Please use app.add_stylesheet() instead.', - RemovedInSphinx20Warning, stacklevel=2) - for item in other: - self.append(item) - return self - - def __add__(self, other): - # type: (List[Union[unicode, Stylesheet]]) -> CSSContainer - ret = CSSContainer(self) - ret += other - return ret - - -class Stylesheet(text_type): +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[unicode, unicode] - filename = None # type: unicode + attributes = None # type: Dict[str, str] + filename = None # type: str def __new__(cls, filename, *args, **attributes): - # type: (unicode, unicode, unicode) -> None - self = text_type.__new__(cls, filename) # type: ignore + # type: (str, str, str) -> None + self = str.__new__(cls, filename) # type: ignore self.filename = filename self.attributes = attributes self.attributes.setdefault('rel', 'stylesheet') @@ -166,14 +115,14 @@ class Stylesheet(text_type): class JSContainer(list): """The container for JavaScript scripts.""" def insert(self, index, obj): - # type: (int, unicode) -> None + # type: (int, str) -> None warnings.warn('builder.script_files is deprecated. ' 'Please use app.add_js_file() instead.', RemovedInSphinx30Warning, stacklevel=2) - super(JSContainer, self).insert(index, obj) + super().insert(index, obj) def extend(self, other): # type: ignore - # type: (List[unicode]) -> None + # type: (List[str]) -> None warnings.warn('builder.script_files is deprecated. ' 'Please use app.add_js_file() instead.', RemovedInSphinx30Warning, stacklevel=2) @@ -181,7 +130,7 @@ class JSContainer(list): self.append(item) def __iadd__(self, other): # type: ignore - # type: (List[unicode]) -> JSContainer + # type: (List[str]) -> JSContainer warnings.warn('builder.script_files is deprecated. ' 'Please use app.add_js_file() instead.', RemovedInSphinx30Warning, stacklevel=2) @@ -190,25 +139,25 @@ class JSContainer(list): return self def __add__(self, other): - # type: (List[unicode]) -> JSContainer + # type: (List[str]) -> JSContainer ret = JSContainer(self) ret += other return ret -class JavaScript(text_type): +class JavaScript(str): """A metadata of javascript file. To keep compatibility with old themes, an instance of javascript behaves as its filename (str). """ - attributes = None # type: Dict[unicode, unicode] - filename = None # type: unicode + attributes = None # type: Dict[str, str] + filename = None # type: str def __new__(cls, filename, **attributes): - # type: (unicode, **unicode) -> None - self = text_type.__new__(cls, filename) # type: ignore + # type: (str, **str) -> None + self = str.__new__(cls, filename) # type: ignore self.filename = filename self.attributes = attributes self.attributes.setdefault('type', 'text/javascript') @@ -216,7 +165,7 @@ class JavaScript(text_type): return self -class BuildInfo(object): +class BuildInfo: """buildinfo file manipulator. HTMLBuilder and its family are storing their own envdata to ``.buildinfo``. @@ -240,9 +189,9 @@ class BuildInfo(object): raise ValueError(__('build info file is broken: %r') % exc) def __init__(self, config=None, tags=None, config_categories=[]): - # type: (Config, Tags, List[unicode]) -> None - self.config_hash = u'' - self.tags_hash = u'' + # type: (Config, Tags, List[str]) -> None + self.config_hash = '' + self.tags_hash = '' if config: values = dict((c.name, c.value) for c in config.filter(config_categories)) @@ -256,10 +205,6 @@ class BuildInfo(object): return (self.config_hash == other.config_hash and self.tags_hash == other.tags_hash) - def __ne__(self, other): # type: ignore - # type: (BuildInfo) -> bool - return not (self == other) # for py27 - def dump(self, f): # type: (IO) -> None f.write('# Sphinx build info version 1\n' @@ -300,18 +245,18 @@ class StandaloneHTMLBuilder(Builder): # use html5 translator by default default_html5_translator = False - imgpath = None # type: unicode - domain_indices = [] # type: List[Tuple[unicode, Type[Index], List[Tuple[unicode, List[List[Union[unicode, int]]]]], bool]] # NOQA + imgpath = None # type: str + domain_indices = [] # type: List[Tuple[str, Type[Index], List[Tuple[str, List[IndexEntry]]], bool]] # NOQA # cached publisher object for snippets _publisher = None def __init__(self, app): # type: (Sphinx) -> None - super(StandaloneHTMLBuilder, self).__init__(app) + super().__init__(app) # CSS files - self.css_files = CSSContainer() # type: List[Dict[unicode, unicode]] + self.css_files = [] # type: List[Dict[str, str]] # JS files self.script_files = JSContainer() # type: List[JavaScript] @@ -322,35 +267,38 @@ class StandaloneHTMLBuilder(Builder): # basename of images directory self.imagedir = '_images' # section numbers for headings in the currently visited document - self.secnumbers = {} # type: Dict[unicode, Tuple[int, ...]] + self.secnumbers = {} # type: Dict[str, Tuple[int, ...]] # currently written docname - self.current_docname = None # type: unicode + self.current_docname = None # type: str self.init_templates() self.init_highlighter() self.init_css_files() self.init_js_files() - if self.config.html_file_suffix is not None: - self.out_suffix = self.config.html_file_suffix - if self.config.html_link_suffix is not None: - self.link_suffix = self.config.html_link_suffix + html_file_suffix = self.get_builder_config('file_suffix', 'html') + if html_file_suffix is not None: + self.out_suffix = html_file_suffix + + html_link_suffix = self.get_builder_config('link_suffix', 'html') + if html_link_suffix is not None: + self.link_suffix = html_link_suffix else: self.link_suffix = self.out_suffix self.use_index = self.get_builder_config('use_index', 'html') if self.config.html_experimental_html5_writer and not html5_ready: - self.app.warn(('html_experimental_html5_writer is set, but current version ' - 'is old. Docutils\' version should be 0.13 or newer, but %s.') % - docutils.__version__) + logger.warning(__('html_experimental_html5_writer is set, but current version ' + 'is old. Docutils\' version should be 0.13 or newer, but %s.'), + docutils.__version__) def create_build_info(self): # type: () -> BuildInfo return BuildInfo(self.config, self.tags, ['html']) def _get_translations_js(self): - # type: () -> unicode + # type: () -> str candidates = [path.join(dir, self.config.language, 'LC_MESSAGES', 'sphinx.js') for dir in self.config.locale_dirs] + \ @@ -365,7 +313,7 @@ class StandaloneHTMLBuilder(Builder): return None def get_theme_config(self): - # type: () -> Tuple[unicode, Dict] + # type: () -> Tuple[str, Dict] return self.config.html_theme, self.config.html_theme_options def init_templates(self): @@ -397,7 +345,7 @@ class StandaloneHTMLBuilder(Builder): self.add_css_file(filename, **attrs) def add_css_file(self, filename, **kwargs): - # type: (unicode, **unicode) -> None + # type: (str, **str) -> None if '://' not in filename: filename = posixpath.join('_static', filename) @@ -420,15 +368,15 @@ class StandaloneHTMLBuilder(Builder): self.add_js_file('translations.js') def add_js_file(self, filename, **kwargs): - # type: (unicode, **unicode) -> None + # type: (str, **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: () -> nodes.NodeVisitor + def default_translator_class(self): # type: ignore + # type: () -> Type[nodes.NodeVisitor] use_html5_writer = self.config.html_experimental_html5_writer if use_html5_writer is None: use_html5_writer = self.default_html5_translator @@ -440,7 +388,7 @@ class StandaloneHTMLBuilder(Builder): @property def math_renderer_name(self): - # type: () -> unicode + # type: () -> str name = self.get_builder_config('math_renderer', 'html') if name is not None: # use given name @@ -460,18 +408,17 @@ class StandaloneHTMLBuilder(Builder): return None def get_outdated_docs(self): - # type: () -> Iterator[unicode] + # type: () -> Iterator[str] try: with open(path.join(self.outdir, '.buildinfo')) as fp: buildinfo = BuildInfo.load(fp) if self.build_info != buildinfo: - for docname in self.env.found_docs: - yield docname + yield from self.env.found_docs return except ValueError as exc: logger.warning(__('Failed to read build info file: %r'), exc) - except IOError: + except OSError: # ignore errors on reading pass @@ -493,20 +440,20 @@ class StandaloneHTMLBuilder(Builder): template_mtime) if srcmtime > targetmtime: yield docname - except EnvironmentError: + except OSError: # source doesn't exist anymore pass def get_asset_paths(self): - # type: () -> List[unicode] + # type: () -> List[str] return self.config.html_extra_path + self.config.html_static_path def render_partial(self, node): - # type: (nodes.Nodes) -> Dict[unicode, unicode] + # type: (nodes.Node) -> Dict[str, str] """Utility: Render a lone doctree node.""" if node is None: return {'fragment': ''} - doc = new_document(b'<partial node>') + doc = new_document('<partial node>') doc.append(node) if self._publisher is None: @@ -528,7 +475,7 @@ class StandaloneHTMLBuilder(Builder): return pub.writer.parts def prepare_writing(self, docnames): - # type: (Iterable[unicode]) -> nodes.Node + # type: (Set[str]) -> None # create the search indexer self.indexer = None if self.search: @@ -545,7 +492,7 @@ class StandaloneHTMLBuilder(Builder): self.docsettings = OptionParser( defaults=self.env.settings, components=(self.docwriter,), - read_config_files=True).get_default_values() + read_config_files=True).get_default_values() # type: Any self.docsettings.compact_lists = bool(self.config.html_compact_lists) # determine the additional indices to include @@ -557,7 +504,7 @@ class StandaloneHTMLBuilder(Builder): domain = None # type: Domain domain = self.env.domains[domain_name] for indexcls in domain.indices: - indexname = '%s-%s' % (domain.name, indexcls.name) # type: unicode + indexname = '%s-%s' % (domain.name, indexcls.name) if isinstance(indices_config, list): if indexname not in indices_config: continue @@ -581,12 +528,12 @@ class StandaloneHTMLBuilder(Builder): favicon = self.config.html_favicon and \ path.basename(self.config.html_favicon) or '' - if not isinstance(self.config.html_use_opensearch, string_types): + if not isinstance(self.config.html_use_opensearch, str): logger.warning(__('html_use_opensearch config value must now be a string')) self.relations = self.env.collect_relations() - rellinks = [] # type: List[Tuple[unicode, unicode, unicode, unicode]] + rellinks = [] # type: List[Tuple[str, str, str, str]] if self.use_index: rellinks.append(('genindex', _('General Index'), 'I', _('index'))) for indexname, indexcls, content, collapse in self.domain_indices: @@ -602,43 +549,43 @@ class StandaloneHTMLBuilder(Builder): else: stylename = 'default.css' - self.globalcontext = dict( - embedded = self.embedded, - project = self.config.project, - release = return_codes_re.sub('', self.config.release), - version = self.config.version, - last_updated = self.last_updated, - copyright = self.config.copyright, - master_doc = self.config.master_doc, - use_opensearch = self.config.html_use_opensearch, - docstitle = self.config.html_title, - shorttitle = self.config.html_short_title, - show_copyright = self.config.html_show_copyright, - show_sphinx = self.config.html_show_sphinx, - has_source = self.config.html_copy_source, - show_source = self.config.html_show_sourcelink, - sourcelink_suffix = self.config.html_sourcelink_suffix, - file_suffix = self.out_suffix, - script_files = self.script_files, - language = self.config.language, - css_files = self.css_files, - sphinx_version = __display_version__, - style = stylename, - rellinks = rellinks, - builder = self.name, - parents = [], - logo = logo, - favicon = favicon, - html5_doctype = self.config.html_experimental_html5_writer and html5_ready, - ) # type: Dict[unicode, Any] + self.globalcontext = { + 'embedded': self.embedded, + 'project': self.config.project, + 'release': return_codes_re.sub('', self.config.release), + 'version': self.config.version, + 'last_updated': self.last_updated, + 'copyright': self.config.copyright, + 'master_doc': self.config.master_doc, + 'use_opensearch': self.config.html_use_opensearch, + 'docstitle': self.config.html_title, + 'shorttitle': self.config.html_short_title, + 'show_copyright': self.config.html_show_copyright, + 'show_sphinx': self.config.html_show_sphinx, + 'has_source': self.config.html_copy_source, + 'show_source': self.config.html_show_sourcelink, + 'sourcelink_suffix': self.config.html_sourcelink_suffix, + 'file_suffix': self.out_suffix, + 'script_files': self.script_files, + 'language': self.config.language, + 'css_files': self.css_files, + 'sphinx_version': __display_version__, + 'style': stylename, + 'rellinks': rellinks, + 'builder': self.name, + 'parents': [], + 'logo': logo, + 'favicon': favicon, + 'html5_doctype': self.config.html_experimental_html5_writer and html5_ready, + } if self.theme: self.globalcontext.update( ('theme_' + key, val) for (key, val) in - iteritems(self.theme.get_options(self.theme_options))) + self.theme.get_options(self.theme_options).items()) self.globalcontext.update(self.config.html_context) def get_doc_context(self, docname, body, metatags): - # type: (unicode, unicode, Dict) -> Dict[unicode, Any] + # type: (str, str, str) -> Dict[str, Any] """Collect items for the template context of a page.""" # find out relations prev = next = None @@ -681,8 +628,8 @@ class StandaloneHTMLBuilder(Builder): parents.reverse() # title rendered as HTML - title = self.env.longtitles.get(docname) - title = title and self.render_partial(title)['title'] or '' + title_node = self.env.longtitles.get(docname) + title = title_node and self.render_partial(title_node)['title'] or '' # Suffix for the document source_suffix = path.splitext(self.env.doc2path(docname))[1] @@ -702,31 +649,31 @@ class StandaloneHTMLBuilder(Builder): self_toc = TocTree(self.env).get_toc_for(docname, self) toc = self.render_partial(self_toc)['fragment'] - return dict( - parents = parents, - prev = prev, - next = next, - title = title, - meta = meta, - body = body, - metatags = metatags, - rellinks = rellinks, - sourcename = sourcename, - toc = toc, + return { + 'parents': parents, + 'prev': prev, + 'next': next, + 'title': title, + 'meta': meta, + 'body': body, + 'metatags': metatags, + 'rellinks': rellinks, + 'sourcename': sourcename, + 'toc': toc, # only display a TOC if there's more than one item to show - display_toc = (self.env.toc_num_entries[docname] > 1), - page_source_suffix = source_suffix, - ) + 'display_toc': (self.env.toc_num_entries[docname] > 1), + 'page_source_suffix': source_suffix, + } def write_doc(self, docname, doctree): - # type: (unicode, nodes.Node) -> None + # type: (str, nodes.document) -> None destination = StringOutput(encoding='utf-8') doctree.settings = self.docsettings self.secnumbers = self.env.toc_secnumbers.get(docname, {}) - self.fignumbers = self.env.toc_fignumbers.get(docname, {}) # type: Dict[unicode, Dict[unicode, Tuple[int, ...]]] # NOQA + self.fignumbers = self.env.toc_fignumbers.get(docname, {}) self.imgpath = relative_uri(self.get_target_uri(docname), '_images') - self.dlpath = relative_uri(self.get_target_uri(docname), '_downloads') # type: unicode + self.dlpath = relative_uri(self.get_target_uri(docname), '_downloads') self.current_docname = docname self.docwriter.write(doctree, destination) self.docwriter.assemble_parts() @@ -737,11 +684,11 @@ class StandaloneHTMLBuilder(Builder): self.handle_page(docname, ctx, event_arg=doctree) def write_doc_serialized(self, docname, doctree): - # type: (unicode, nodes.Node) -> None + # type: (str, nodes.document) -> None self.imgpath = relative_uri(self.get_target_uri(docname), self.imagedir) self.post_process_images(doctree) - title = self.env.longtitles.get(docname) - title = title and self.render_partial(title)['title'] or '' + 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): @@ -807,11 +754,11 @@ class StandaloneHTMLBuilder(Builder): indexcounts.append(sum(1 + len(subitems) for _, (_, subitems, _) in entries)) - genindexcontext = dict( - genindexentries = genindex, - genindexcounts = indexcounts, - split_index = self.config.html_split_index, - ) + genindexcontext = { + 'genindexentries': genindex, + 'genindexcounts': indexcounts, + 'split_index': self.config.html_split_index, + } logger.info(' genindex', nonl=1) if self.config.html_split_index: @@ -830,11 +777,11 @@ class StandaloneHTMLBuilder(Builder): def write_domain_indices(self): # type: () -> None for indexname, indexcls, content, collapse in self.domain_indices: - indexcontext = dict( - indextitle = indexcls.localname, - content = content, - collapse_index = collapse, - ) + indexcontext = { + 'indextitle': indexcls.localname, + 'content': content, + 'collapse_index': collapse, + } logger.info(' ' + indexname, nonl=1) self.handle_page(indexname, indexcontext, 'domainindex.html') @@ -857,7 +804,7 @@ class StandaloneHTMLBuilder(Builder): def copy_download_files(self): # type: () -> None def to_relpath(f): - # type: (unicode) -> unicode + # type: (str) -> str return relative_path(self.srcdir, f) # copy downloadable files if self.env.dlfiles: @@ -869,7 +816,7 @@ class StandaloneHTMLBuilder(Builder): dest = path.join(self.outdir, '_downloads', self.env.dlfiles[src][1]) ensuredir(path.dirname(dest)) copyfile(path.join(self.srcdir, src), dest) - except EnvironmentError as err: + except OSError as err: logger.warning(__('cannot copy downloadable file %r: %s'), path.join(self.srcdir, src), err) @@ -881,7 +828,7 @@ class StandaloneHTMLBuilder(Builder): ensuredir(path.join(self.outdir, '_static')) # first, create pygments style file with open(path.join(self.outdir, '_static', 'pygments.css'), 'w') as f: - f.write(self.highlighter.get_stylesheet()) # type: ignore + f.write(self.highlighter.get_stylesheet()) # then, copy translations JavaScript file if self.config.language is not None: jsfile = self._get_translations_js() @@ -935,9 +882,7 @@ class StandaloneHTMLBuilder(Builder): copyfile(path.join(self.confdir, self.config.html_favicon), icontarget) logger.info('done') - except EnvironmentError as err: - # TODO: In py3, EnvironmentError (and IOError) was merged into OSError. - # So it should be replaced by IOError on dropping py2 support + except OSError as err: logger.warning(__('cannot copy static file %r'), err) def copy_extra_files(self): @@ -955,7 +900,7 @@ class StandaloneHTMLBuilder(Builder): copy_asset(entry, self.outdir, excluded) logger.info(__('done')) - except EnvironmentError as err: + except OSError as err: logger.warning(__('cannot copy extra file %r'), err) def write_buildinfo(self): @@ -963,7 +908,7 @@ class StandaloneHTMLBuilder(Builder): try: with open(path.join(self.outdir, '.buildinfo'), 'w') as fp: self.build_info.dump(fp) - except IOError as exc: + except OSError as exc: logger.warning(__('Failed to write build info file: %r'), exc) def cleanup(self): @@ -999,17 +944,17 @@ class StandaloneHTMLBuilder(Builder): reference.append(node) def load_indexer(self, docnames): - # type: (Iterable[unicode]) -> None + # type: (Iterable[str]) -> None keep = set(self.env.all_docs) - set(docnames) try: searchindexfn = path.join(self.outdir, self.searchindex_filename) if self.indexer_dumps_unicode: - f = codecs.open(searchindexfn, 'r', encoding='utf-8') # type: ignore + with open(searchindexfn, encoding='utf-8') as ft: + self.indexer.load(ft, self.indexer_format) else: - f = open(searchindexfn, 'rb') # type: ignore - with f: - self.indexer.load(f, self.indexer_format) - except (IOError, OSError, ValueError): + with open(searchindexfn, 'rb') as fb: + self.indexer.load(fb, self.indexer_format) + except (OSError, ValueError): if keep: logger.warning(__('search index couldn\'t be loaded, but not all ' 'documents will be built: the index will be ' @@ -1018,7 +963,7 @@ class StandaloneHTMLBuilder(Builder): self.indexer.prune(keep) def index_page(self, pagename, doctree, title): - # type: (unicode, nodes.Node, unicode) -> None + # type: (str, nodes.document, str) -> None # only index pages with title if self.indexer is not None and title: filename = self.env.doc2path(pagename, base=None) @@ -1027,22 +972,28 @@ class StandaloneHTMLBuilder(Builder): except TypeError: # fallback for old search-adapters self.indexer.feed(pagename, title, doctree) # type: ignore + indexer_name = self.indexer.__class__.__name__ + warnings.warn( + 'The %s.feed() method signature is deprecated. Update to ' + '%s.feed(docname, filename, title, doctree).' % ( + indexer_name, indexer_name), + RemovedInSphinx40Warning) def _get_local_toctree(self, docname, collapse=True, **kwds): - # type: (unicode, bool, Any) -> unicode + # type: (str, bool, Any) -> 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: (unicode) -> unicode + # type: (str) -> str return path.join(self.outdir, os_path(pagename) + self.out_suffix) def add_sidebars(self, pagename, ctx): - # type: (unicode, Dict) -> None + # type: (str, Dict) -> None def has_wildcard(pattern): - # type: (unicode) -> bool + # type: (str) -> bool return any(char in pattern for char in '*?[') sidebars = None matched = None @@ -1068,7 +1019,7 @@ class StandaloneHTMLBuilder(Builder): # user sidebar settings html_sidebars = self.get_builder_config('sidebars', 'html') - for pattern, patsidebars in iteritems(html_sidebars): + for pattern, patsidebars in html_sidebars.items(): if patmatch(pagename, pattern): if matched: if has_wildcard(pattern): @@ -1086,14 +1037,6 @@ class StandaloneHTMLBuilder(Builder): if sidebars is None: # keep defaults pass - elif isinstance(sidebars, string_types): - # 0.x compatible mode: insert custom sidebar before searchbox - customsidebar = sidebars - sidebars = None - warnings.warn('Now html_sidebars only allows list of sidebar ' - 'templates as a value. Support for a string value ' - 'will be removed at Sphinx-2.0.', - RemovedInSphinx20Warning, stacklevel=2) ctx['sidebars'] = sidebars ctx['customsidebar'] = customsidebar @@ -1101,12 +1044,12 @@ class StandaloneHTMLBuilder(Builder): # --------- these are overwritten by the serialization builder def get_target_uri(self, docname, typ=None): - # type: (unicode, unicode) -> unicode + # type: (str, str) -> str return docname + self.link_suffix def handle_page(self, pagename, addctx, templatename='page.html', outfilename=None, event_arg=None): - # type: (unicode, Dict, unicode, unicode, Any) -> None + # type: (str, Dict, str, str, Any) -> None ctx = self.globalcontext.copy() # current_page_name is backwards compatibility ctx['pagename'] = ctx['current_page_name'] = pagename @@ -1123,7 +1066,7 @@ class StandaloneHTMLBuilder(Builder): ctx['pageurl'] = None def pathto(otheruri, resource=False, baseuri=default_baseuri): - # type: (unicode, bool, unicode) -> unicode + # type: (str, bool, str) -> str if resource and '://' in otheruri: # allow non-local resources given by scheme return otheruri @@ -1136,18 +1079,18 @@ class StandaloneHTMLBuilder(Builder): ctx['pathto'] = pathto def css_tag(css): - # type: (Stylesheet) -> unicode + # type: (Stylesheet) -> str attrs = [] for key in sorted(css.attributes): value = css.attributes[key] if value is not None: - attrs.append('%s="%s"' % (key, htmlescape(value, True))) + attrs.append('%s="%s"' % (key, html.escape(value, True))) attrs.append('href="%s"' % pathto(css.filename, resource=True)) return '<link %s />' % ' '.join(attrs) ctx['css_tag'] = css_tag def hasdoc(name): - # type: (unicode) -> bool + # type: (str) -> bool if name in self.env.all_docs: return True elif name == 'search' and self.search: @@ -1158,12 +1101,12 @@ class StandaloneHTMLBuilder(Builder): ctx['hasdoc'] = hasdoc def warn(*args, **kwargs): - # type: (Any, Any) -> unicode + # type: (Any, Any) -> str """Simple warn() wrapper for themes.""" warnings.warn('The template function warn() was deprecated. ' 'Use warning() instead.', RemovedInSphinx30Warning, stacklevel=2) - self.warn(*args, **kwargs) + logger.warning(*args, **kwargs) return '' # return empty string ctx['warn'] = warn @@ -1193,9 +1136,10 @@ class StandaloneHTMLBuilder(Builder): # outfilename's path is in general different from self.outdir ensuredir(path.dirname(outfilename)) try: - with codecs.open(outfilename, 'w', ctx['encoding'], 'xmlcharrefreplace') as f: # type: ignore # NOQA + with open(outfilename, 'w', encoding=ctx['encoding'], + errors='xmlcharrefreplace') as f: f.write(output) - except (IOError, OSError) as err: + except OSError as err: logger.warning(__("error writing file %s: %s"), outfilename, err) if self.copysource and ctx.get('sourcename'): # copy the source file for the "show source" link @@ -1205,7 +1149,7 @@ class StandaloneHTMLBuilder(Builder): copyfile(self.env.doc2path(pagename), source_name) def update_page_context(self, pagename, templatename, ctx, event_arg): - # type: (unicode, unicode, Dict, Any) -> None + # type: (str, str, Dict, Any) -> None pass def handle_finish(self): @@ -1230,11 +1174,11 @@ class StandaloneHTMLBuilder(Builder): # first write to a temporary file, so that if dumping fails, # the existing index won't be overwritten if self.indexer_dumps_unicode: - f = codecs.open(searchindexfn + '.tmp', 'w', encoding='utf-8') # type: ignore + with open(searchindexfn + '.tmp', 'w', encoding='utf-8') as ft: + self.indexer.dump(ft, self.indexer_format) else: - f = open(searchindexfn + '.tmp', 'wb') # type: ignore - with f: - self.indexer.dump(f, self.indexer_format) + with open(searchindexfn + '.tmp', 'wb') as fb: + self.indexer.dump(fb, self.indexer_format) movefile(searchindexfn + '.tmp', searchindexfn) logger.info(__('done')) @@ -1248,7 +1192,7 @@ class DirectoryHTMLBuilder(StandaloneHTMLBuilder): name = 'dirhtml' def get_target_uri(self, docname, typ=None): - # type: (unicode, unicode) -> unicode + # type: (str, str) -> str if docname == 'index': return '' if docname.endswith(SEP + 'index'): @@ -1256,7 +1200,7 @@ class DirectoryHTMLBuilder(StandaloneHTMLBuilder): return docname + SEP def get_outfilename(self, pagename): - # type: (unicode) -> unicode + # type: (str) -> str if pagename == 'index' or pagename.endswith(SEP + 'index'): outfilename = path.join(self.outdir, os_path(pagename) + self.out_suffix) @@ -1267,8 +1211,8 @@ class DirectoryHTMLBuilder(StandaloneHTMLBuilder): return outfilename def prepare_writing(self, docnames): - # type: (Iterable[unicode]) -> None - StandaloneHTMLBuilder.prepare_writing(self, docnames) + # type: (Set[str]) -> None + super().prepare_writing(docnames) self.globalcontext['no_search_suffix'] = True @@ -1283,11 +1227,11 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder): copysource = False def get_outdated_docs(self): # type: ignore - # type: () -> Union[unicode, List[unicode]] + # type: () -> Union[str, List[str]] return 'all documents' def get_target_uri(self, docname, typ=None): - # type: (unicode, unicode) -> unicode + # type: (str, str) -> str if docname in self.env.all_docs: # all references are on the same page... return self.config.master_doc + self.out_suffix + \ @@ -1297,7 +1241,7 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder): return docname + self.out_suffix def get_relative_uri(self, from_, to, typ=None): - # type: (unicode, unicode, unicode) -> unicode + # type: (str, str, str) -> str # ignore source return self.get_target_uri(to, typ) @@ -1317,7 +1261,7 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder): refnode['refuri'] = fname + refuri[hashindex:] def _get_local_toctree(self, docname, collapse=True, **kwds): - # type: (unicode, bool, Any) -> unicode + # type: (str, bool, Any) -> str if 'includehidden' not in kwds: kwds['includehidden'] = False toctree = TocTree(self.env).get_toctree_for(docname, self, collapse, **kwds) @@ -1326,7 +1270,7 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder): return self.render_partial(toctree)['fragment'] def assemble_doctree(self): - # type: () -> nodes.Node + # type: () -> nodes.document master = self.config.master_doc tree = self.env.get_doctree(master) tree = inline_all_toctrees(self, set(), master, tree, darkgreen, [master]) @@ -1336,7 +1280,7 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder): return tree def assemble_toc_secnumbers(self): - # type: () -> Dict[unicode, Dict[unicode, Tuple[int, ...]]] + # type: () -> Dict[str, Dict[str, Tuple[int, ...]]] # Assemble toc_secnumbers to resolve section numbers on SingleHTML. # Merge all secnumbers to single secnumber. # @@ -1346,16 +1290,16 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder): # # There are related codes in inline_all_toctres() and # HTMLTranslter#add_secnumber(). - new_secnumbers = {} # type: Dict[unicode, Tuple[int, ...]] - for docname, secnums in iteritems(self.env.toc_secnumbers): - for id, secnum in iteritems(secnums): + new_secnumbers = {} # type: Dict[str, Tuple[int, ...]] + for docname, secnums in self.env.toc_secnumbers.items(): + for id, secnum in secnums.items(): alias = "%s/%s" % (docname, id) new_secnumbers[alias] = secnum return {self.config.master_doc: new_secnumbers} def assemble_toc_fignumbers(self): - # type: () -> Dict[unicode, Dict[unicode, Dict[unicode, Tuple[int, ...]]]] # NOQA + # type: () -> Dict[str, Dict[str, Dict[str, Tuple[int, ...]]]] # Assemble toc_fignumbers to resolve figure numbers on SingleHTML. # Merge all fignumbers to single fignumber. # @@ -1365,51 +1309,50 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder): # # There are related codes in inline_all_toctres() and # HTMLTranslter#add_fignumber(). - new_fignumbers = {} # type: Dict[unicode, Dict[unicode, Tuple[int, ...]]] - # {u'foo': {'figure': {'id2': (2,), 'id1': (1,)}}, u'bar': {'figure': {'id1': (3,)}}} - for docname, fignumlist in iteritems(self.env.toc_fignumbers): - for figtype, fignums in iteritems(fignumlist): + new_fignumbers = {} # type: 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 = "%s/%s" % (docname, figtype) new_fignumbers.setdefault(alias, {}) - for id, fignum in iteritems(fignums): + for id, fignum in fignums.items(): new_fignumbers[alias][id] = fignum return {self.config.master_doc: new_fignumbers} def get_doc_context(self, docname, body, metatags): - # type: (unicode, unicode, Dict) -> Dict + # type: (str, str, str) -> Dict # no relation links... - toc = TocTree(self.env).get_toctree_for(self.config.master_doc, - self, False) + toctree = TocTree(self.env).get_toctree_for(self.config.master_doc, self, False) # if there is no toctree, toc is None - if toc: - self.fix_refuris(toc) - toc = self.render_partial(toc)['fragment'] + if toctree: + self.fix_refuris(toctree) + toc = self.render_partial(toctree)['fragment'] display_toc = True else: toc = '' display_toc = False - return dict( - 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, - ) + 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): # type: (Any) -> None docnames = self.env.all_docs logger.info(bold(__('preparing documents... ')), nonl=True) - self.prepare_writing(docnames) + self.prepare_writing(docnames) # type: ignore logger.info(__('done')) logger.info(bold(__('assembling single document... ')), nonl=True) @@ -1460,7 +1403,7 @@ class SerializingHTMLBuilder(StandaloneHTMLBuilder): additional_dump_args = () # type: Tuple #: the filename for the global context file - globalcontext_filename = None # type: unicode + globalcontext_filename = None # type: str supported_image_types = ['image/svg+xml', 'image/png', 'image/gif', 'image/jpeg'] @@ -1479,7 +1422,7 @@ class SerializingHTMLBuilder(StandaloneHTMLBuilder): self.use_index = self.get_builder_config('use_index', 'html') def get_target_uri(self, docname, typ=None): - # type: (unicode, unicode) -> unicode + # type: (str, str) -> str if docname == 'index': return '' if docname.endswith(SEP + 'index'): @@ -1487,17 +1430,17 @@ class SerializingHTMLBuilder(StandaloneHTMLBuilder): return docname + SEP def dump_context(self, context, filename): - # type: (Dict, unicode) -> None + # type: (Dict, str) -> None if self.implementation_dumps_unicode: - f = codecs.open(filename, 'w', encoding='utf-8') # type: ignore + with open(filename, 'w', encoding='utf-8') as ft: + self.implementation.dump(context, ft, *self.additional_dump_args) else: - f = open(filename, 'wb') # type: ignore - with f: - self.implementation.dump(context, f, *self.additional_dump_args) + with open(filename, 'wb') as fb: + self.implementation.dump(context, fb, *self.additional_dump_args) def handle_page(self, pagename, ctx, templatename='page.html', outfilename=None, event_arg=None): - # type: (unicode, Dict, unicode, unicode, Any) -> None + # type: (str, Dict, str, str, Any) -> None ctx['current_page_name'] = pagename self.add_sidebars(pagename, ctx) @@ -1532,7 +1475,7 @@ class SerializingHTMLBuilder(StandaloneHTMLBuilder): self.dump_context(self.globalcontext, outfilename) # super here to dump the search index - StandaloneHTMLBuilder.handle_finish(self) + super().handle_finish() # copy the environment file from the doctree dir to the output dir # as needed by the web app @@ -1580,17 +1523,13 @@ class JSONHTMLBuilder(SerializingHTMLBuilder): globalcontext_filename = 'globalcontext.json' searchindex_filename = 'searchindex.json' - def init(self): - # type: () -> None - SerializingHTMLBuilder.init(self) - def convert_html_css_files(app, config): # type: (Sphinx, Config) -> None """This converts string styled html_css_files to tuple styled one.""" - html_css_files = [] # type: List[Tuple[unicode, Dict]] + html_css_files = [] # type: List[Tuple[str, Dict]] for entry in config.html_css_files: - if isinstance(entry, string_types): + if isinstance(entry, str): html_css_files.append((entry, {})) else: try: @@ -1606,9 +1545,9 @@ def convert_html_css_files(app, config): def convert_html_js_files(app, config): # type: (Sphinx, Config) -> None """This converts string styled html_js_files to tuple styled one.""" - html_js_files = [] # type: List[Tuple[unicode, Dict]] + html_js_files = [] # type: List[Tuple[str, Dict]] for entry in config.html_js_files: - if isinstance(entry, string_types): + if isinstance(entry, str): html_js_files.append((entry, {})) else: try: @@ -1622,7 +1561,7 @@ def convert_html_js_files(app, config): def setup_js_tag_helper(app, pagename, templatexname, context, doctree): - # type: (Sphinx, unicode, unicode, Dict, nodes.Node) -> None + # type: (Sphinx, str, str, Dict, nodes.Node) -> None """Set up js_tag() template helper. .. note:: This set up function is added to keep compatibility with webhelper. @@ -1630,9 +1569,9 @@ def setup_js_tag_helper(app, pagename, templatexname, context, doctree): pathto = context.get('pathto') def js_tag(js): - # type: (JavaScript) -> unicode + # type: (JavaScript) -> str attrs = [] - body = '' # type: unicode + body = '' if isinstance(js, JavaScript): for key in sorted(js.attributes): value = js.attributes[key] @@ -1640,7 +1579,7 @@ def setup_js_tag_helper(app, pagename, templatexname, context, doctree): if key == 'body': body = value else: - attrs.append('%s="%s"' % (key, htmlescape(value, True))) + attrs.append('%s="%s"' % (key, html.escape(value, True))) if js.filename: attrs.append('src="%s"' % pathto(js.filename, resource=True)) else: @@ -1666,7 +1605,7 @@ def validate_math_renderer(app): def setup(app): - # type: (Sphinx) -> Dict[unicode, Any] + # type: (Sphinx) -> Dict[str, Any] # builders app.add_builder(StandaloneHTMLBuilder) app.add_builder(DirectoryHTMLBuilder) @@ -1680,35 +1619,35 @@ def setup(app): app.add_config_value('html_theme_options', {}, 'html') app.add_config_value('html_title', lambda self: _('%s %s documentation') % (self.project, self.release), - 'html', string_classes) + 'html', [str]) app.add_config_value('html_short_title', lambda self: self.html_title, 'html') - app.add_config_value('html_style', None, 'html', string_classes) - app.add_config_value('html_logo', None, 'html', string_classes) - app.add_config_value('html_favicon', None, 'html', string_classes) + 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', string_classes) + 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', u'\u00B6', 'html') + 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', string_classes) - app.add_config_value('html_link_suffix', None, 'html', string_classes) + 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', string_classes) + 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') |