mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Format `sphinx/builders/html/
`
This commit is contained in:
parent
e50e1db203
commit
ecbdca0b65
@ -120,8 +120,7 @@ class StandaloneHTMLBuilder(Builder):
|
||||
indexer_dumps_unicode = True
|
||||
# create links to original images from images [True/False]
|
||||
html_scaled_image_link = True
|
||||
supported_image_types = ['image/svg+xml', 'image/png',
|
||||
'image/gif', 'image/jpeg']
|
||||
supported_image_types = ['image/svg+xml', 'image/png', 'image/gif', 'image/jpeg']
|
||||
supported_remote_images = True
|
||||
supported_data_uri_images = True
|
||||
searchindex_filename = 'searchindex.js'
|
||||
@ -188,13 +187,17 @@ class StandaloneHTMLBuilder(Builder):
|
||||
return BuildInfo(self.config, self.tags, frozenset({'html'}))
|
||||
|
||||
def _get_translations_js(self) -> str:
|
||||
candidates = [path.join(dir, self.config.language,
|
||||
'LC_MESSAGES', 'sphinx.js')
|
||||
for dir in self.config.locale_dirs] + \
|
||||
[path.join(package_dir, 'locale', self.config.language,
|
||||
'LC_MESSAGES', 'sphinx.js'),
|
||||
path.join(sys.prefix, 'share/sphinx/locale',
|
||||
self.config.language, 'sphinx.js')]
|
||||
candidates = [
|
||||
path.join(dir, self.config.language, 'LC_MESSAGES', 'sphinx.js')
|
||||
for dir in self.config.locale_dirs
|
||||
] + [
|
||||
path.join(
|
||||
package_dir, 'locale', self.config.language, 'LC_MESSAGES', 'sphinx.js'
|
||||
),
|
||||
path.join(
|
||||
sys.prefix, 'share/sphinx/locale', self.config.language, 'sphinx.js'
|
||||
),
|
||||
]
|
||||
|
||||
for jsfile in candidates:
|
||||
if path.isfile(jsfile):
|
||||
@ -242,15 +245,19 @@ class StandaloneHTMLBuilder(Builder):
|
||||
self.dark_highlighter: PygmentsBridge | None
|
||||
if dark_style is not None:
|
||||
self.dark_highlighter = PygmentsBridge('html', dark_style)
|
||||
self.app.add_css_file('pygments_dark.css',
|
||||
media='(prefers-color-scheme: dark)',
|
||||
id='pygments_dark_css')
|
||||
self.app.add_css_file(
|
||||
'pygments_dark.css',
|
||||
media='(prefers-color-scheme: dark)',
|
||||
id='pygments_dark_css',
|
||||
)
|
||||
else:
|
||||
self.dark_highlighter = None
|
||||
|
||||
@property
|
||||
def css_files(self) -> list[_CascadingStyleSheet]:
|
||||
_deprecation_warning(__name__, f'{self.__class__.__name__}.css_files', remove=(9, 0))
|
||||
_deprecation_warning(
|
||||
__name__, f'{self.__class__.__name__}.css_files', remove=(9, 0)
|
||||
)
|
||||
return self._css_files
|
||||
|
||||
def init_css_files(self) -> None:
|
||||
@ -264,7 +271,8 @@ class StandaloneHTMLBuilder(Builder):
|
||||
self.add_css_file(filename, **attrs)
|
||||
|
||||
for filename, attrs in self.get_builder_config('css_files', 'html'):
|
||||
attrs.setdefault('priority', 800) # User's CSSs are loaded after extensions'
|
||||
# User's CSSs are loaded after extensions'
|
||||
attrs.setdefault('priority', 800)
|
||||
self.add_css_file(filename, **attrs)
|
||||
|
||||
def add_css_file(self, filename: str, **kwargs: Any) -> None:
|
||||
@ -343,7 +351,9 @@ class StandaloneHTMLBuilder(Builder):
|
||||
pass # ignore errors
|
||||
else:
|
||||
# only log on success
|
||||
msg = __('build_info mismatch, copying .buildinfo to .buildinfo.bak')
|
||||
msg = __(
|
||||
'build_info mismatch, copying .buildinfo to .buildinfo.bak'
|
||||
)
|
||||
logger.info(bold(__('building [html]: ')) + msg)
|
||||
|
||||
yield from self.env.found_docs
|
||||
@ -359,10 +369,10 @@ class StandaloneHTMLBuilder(Builder):
|
||||
# Let users know they have a newer template
|
||||
if template_mtime > old_mtime:
|
||||
logger.info(
|
||||
bold("building [html]: ") +
|
||||
__(
|
||||
"template %s has been changed since the previous build, "
|
||||
"all docs will be rebuilt"
|
||||
bold('building [html]: ')
|
||||
+ __(
|
||||
'template %s has been changed since the previous build, '
|
||||
'all docs will be rebuilt'
|
||||
),
|
||||
self.templates.newest_template_name(),
|
||||
)
|
||||
@ -379,7 +389,8 @@ class StandaloneHTMLBuilder(Builder):
|
||||
except Exception:
|
||||
targetmtime = 0
|
||||
try:
|
||||
srcmtime = max(_last_modified_time(self.env.doc2path(docname)), template_mtime)
|
||||
doc_mtime = _last_modified_time(self.env.doc2path(docname))
|
||||
srcmtime = max(doc_mtime, template_mtime)
|
||||
if srcmtime > targetmtime:
|
||||
logger.debug(
|
||||
'[build target] targetname %r(%s), template(%s), docname %r(%s)',
|
||||
@ -387,7 +398,7 @@ class StandaloneHTMLBuilder(Builder):
|
||||
_format_rfc3339_microseconds(targetmtime),
|
||||
_format_rfc3339_microseconds(template_mtime),
|
||||
docname,
|
||||
_format_rfc3339_microseconds(_last_modified_time(self.env.doc2path(docname))),
|
||||
_format_rfc3339_microseconds(doc_mtime),
|
||||
)
|
||||
yield docname
|
||||
except OSError:
|
||||
@ -413,10 +424,14 @@ class StandaloneHTMLBuilder(Builder):
|
||||
self.indexer = None
|
||||
if self.search:
|
||||
from sphinx.search import IndexBuilder
|
||||
|
||||
lang = self.config.html_search_language or self.config.language
|
||||
self.indexer = IndexBuilder(self.env, lang,
|
||||
self.config.html_search_options,
|
||||
self.config.html_search_scorer)
|
||||
self.indexer = IndexBuilder(
|
||||
self.env,
|
||||
lang,
|
||||
self.config.html_search_options,
|
||||
self.config.html_search_scorer,
|
||||
)
|
||||
self.load_indexer(docnames)
|
||||
|
||||
self.docwriter = HTMLWriter(self)
|
||||
@ -427,7 +442,8 @@ class StandaloneHTMLBuilder(Builder):
|
||||
self.docsettings: Any = OptionParser(
|
||||
defaults=self.env.settings,
|
||||
components=(self.docwriter,),
|
||||
read_config_files=True).get_default_values()
|
||||
read_config_files=True,
|
||||
).get_default_values()
|
||||
self.docsettings.compact_lists = bool(self.config.html_compact_lists)
|
||||
|
||||
# determine the additional indices to include
|
||||
@ -446,8 +462,12 @@ class StandaloneHTMLBuilder(Builder):
|
||||
continue
|
||||
content, collapse = index_cls(domain).generate()
|
||||
if content:
|
||||
self.domain_indices.append(
|
||||
(index_name, index_cls, content, collapse))
|
||||
self.domain_indices.append((
|
||||
index_name,
|
||||
index_cls,
|
||||
content,
|
||||
collapse,
|
||||
))
|
||||
|
||||
# format the "last updated on" string, only once is enough since it
|
||||
# typically doesn't include the time of day
|
||||
@ -479,8 +499,7 @@ class StandaloneHTMLBuilder(Builder):
|
||||
for indexname, indexcls, _content, _collapse in self.domain_indices:
|
||||
# if it has a short name
|
||||
if indexcls.shortname:
|
||||
rellinks.append((indexname, indexcls.localname,
|
||||
'', indexcls.shortname))
|
||||
rellinks.append((indexname, indexcls.localname, '', indexcls.shortname))
|
||||
|
||||
# add assets registered after ``Builder.init()``.
|
||||
for css_filename, attrs in self.app.registry.css_files:
|
||||
@ -530,8 +549,8 @@ class StandaloneHTMLBuilder(Builder):
|
||||
}
|
||||
if self.theme:
|
||||
self.globalcontext |= {
|
||||
f'theme_{key}': val for key, val in
|
||||
self.theme.get_options(self.theme_options).items()
|
||||
f'theme_{key}': val
|
||||
for key, val in self.theme.get_options(self.theme_options).items()
|
||||
}
|
||||
self.globalcontext |= self.config.html_context
|
||||
|
||||
@ -565,9 +584,10 @@ class StandaloneHTMLBuilder(Builder):
|
||||
prev = None
|
||||
while related and related[0]:
|
||||
with contextlib.suppress(KeyError):
|
||||
parents.append(
|
||||
{'link': self.get_relative_uri(docname, related[0]),
|
||||
'title': self.render_partial(titles[related[0]])['title']})
|
||||
parents.append({
|
||||
'link': self.get_relative_uri(docname, related[0]),
|
||||
'title': self.render_partial(titles[related[0]])['title'],
|
||||
})
|
||||
|
||||
related = self.relations.get(related[0])
|
||||
if parents:
|
||||
@ -581,7 +601,7 @@ class StandaloneHTMLBuilder(Builder):
|
||||
title = self.render_partial(title_node)['title'] if title_node else ''
|
||||
|
||||
# Suffix for the document
|
||||
source_suffix = str(self.env.doc2path(docname, False))[len(docname):]
|
||||
source_suffix = str(self.env.doc2path(docname, False))[len(docname) :]
|
||||
|
||||
# the name for the copied source
|
||||
if self.config.html_copy_source:
|
||||
@ -691,8 +711,10 @@ class StandaloneHTMLBuilder(Builder):
|
||||
# the total count of lines for each index letter, used to distribute
|
||||
# the entries into two columns
|
||||
genindex = IndexEntries(self.env).create_index(self)
|
||||
indexcounts = [sum(1 + len(subitems) for _, (_, subitems, _) in entries)
|
||||
for _k, entries in genindex]
|
||||
indexcounts = [
|
||||
sum(1 + len(subitems) for _, (_, subitems, _) in entries)
|
||||
for _k, entries in genindex
|
||||
]
|
||||
|
||||
genindexcontext = {
|
||||
'genindexentries': genindex,
|
||||
@ -702,15 +724,16 @@ class StandaloneHTMLBuilder(Builder):
|
||||
logger.info('genindex ', nonl=True)
|
||||
|
||||
if self.config.html_split_index:
|
||||
self.handle_page('genindex', genindexcontext,
|
||||
'genindex-split.html')
|
||||
self.handle_page('genindex-all', genindexcontext,
|
||||
'genindex.html')
|
||||
self.handle_page('genindex', genindexcontext, 'genindex-split.html')
|
||||
self.handle_page('genindex-all', genindexcontext, 'genindex.html')
|
||||
for (key, entries), count in zip(genindex, indexcounts, strict=True):
|
||||
ctx = {'key': key, 'entries': entries, 'count': count,
|
||||
'genindexentries': genindex}
|
||||
self.handle_page('genindex-' + key, ctx,
|
||||
'genindex-single.html')
|
||||
ctx = {
|
||||
'key': key,
|
||||
'entries': entries,
|
||||
'count': count,
|
||||
'genindexentries': genindex,
|
||||
}
|
||||
self.handle_page('genindex-' + key, ctx, 'genindex-single.html')
|
||||
else:
|
||||
self.handle_page('genindex', genindexcontext, 'genindex.html')
|
||||
|
||||
@ -728,9 +751,14 @@ class StandaloneHTMLBuilder(Builder):
|
||||
if self.images:
|
||||
stringify_func = ImageAdapter(self.app.env).get_original_image_uri
|
||||
ensuredir(self.outdir / self.imagedir)
|
||||
for src in status_iterator(self.images, __('copying images... '), "brown",
|
||||
len(self.images), self.app.verbosity,
|
||||
stringify_func=stringify_func):
|
||||
for src in status_iterator(
|
||||
self.images,
|
||||
__('copying images... '),
|
||||
'brown',
|
||||
len(self.images),
|
||||
self.app.verbosity,
|
||||
stringify_func=stringify_func,
|
||||
):
|
||||
dest = self.images[src]
|
||||
try:
|
||||
copyfile(
|
||||
@ -739,8 +767,9 @@ class StandaloneHTMLBuilder(Builder):
|
||||
force=True,
|
||||
)
|
||||
except Exception as err:
|
||||
logger.warning(__("cannot copy image file '%s': %s"),
|
||||
self.srcdir / src, err)
|
||||
logger.warning(
|
||||
__("cannot copy image file '%s': %s"), self.srcdir / src, err
|
||||
)
|
||||
|
||||
def copy_download_files(self) -> None:
|
||||
def to_relpath(f: str) -> str:
|
||||
@ -749,26 +778,34 @@ class StandaloneHTMLBuilder(Builder):
|
||||
# copy downloadable files
|
||||
if self.env.dlfiles:
|
||||
ensuredir(self.outdir / '_downloads')
|
||||
for src in status_iterator(self.env.dlfiles, __('copying downloadable files... '),
|
||||
"brown", len(self.env.dlfiles), self.app.verbosity,
|
||||
stringify_func=to_relpath):
|
||||
for src in status_iterator(
|
||||
self.env.dlfiles,
|
||||
__('copying downloadable files... '),
|
||||
'brown',
|
||||
len(self.env.dlfiles),
|
||||
self.app.verbosity,
|
||||
stringify_func=to_relpath,
|
||||
):
|
||||
try:
|
||||
dest = self.outdir / '_downloads' / self.env.dlfiles[src][1]
|
||||
ensuredir(dest.parent)
|
||||
copyfile(self.srcdir / src, dest, force=True)
|
||||
except OSError as err:
|
||||
logger.warning(__('cannot copy downloadable file %r: %s'),
|
||||
self.srcdir / src, err)
|
||||
logger.warning(
|
||||
__('cannot copy downloadable file %r: %s'),
|
||||
self.srcdir / src,
|
||||
err,
|
||||
)
|
||||
|
||||
def create_pygments_style_file(self) -> None:
|
||||
"""Create a style file for pygments."""
|
||||
with open(path.join(self.outdir, '_static', 'pygments.css'), 'w',
|
||||
encoding="utf-8") as f:
|
||||
pyg_path = path.join(self.outdir, '_static', 'pygments.css')
|
||||
with open(pyg_path, 'w', encoding='utf-8') as f:
|
||||
f.write(self.highlighter.get_stylesheet())
|
||||
|
||||
if self.dark_highlighter:
|
||||
with open(path.join(self.outdir, '_static', 'pygments_dark.css'), 'w',
|
||||
encoding="utf-8") as f:
|
||||
dark_path = path.join(self.outdir, '_static', 'pygments_dark.css')
|
||||
with open(dark_path, 'w', encoding='utf-8') as f:
|
||||
f.write(self.dark_highlighter.get_stylesheet())
|
||||
|
||||
def copy_translation_js(self) -> None:
|
||||
@ -810,23 +847,28 @@ class StandaloneHTMLBuilder(Builder):
|
||||
copy_asset(
|
||||
Path(entry) / 'static',
|
||||
self.outdir / '_static',
|
||||
excluded=DOTFILES, context=context,
|
||||
renderer=self.templates, onerror=onerror,
|
||||
excluded=DOTFILES,
|
||||
context=context,
|
||||
renderer=self.templates,
|
||||
onerror=onerror,
|
||||
force=True,
|
||||
)
|
||||
|
||||
def copy_html_static_files(self, context: dict[str, Any]) -> None:
|
||||
def onerror(filename: str, error: Exception) -> None:
|
||||
logger.warning(__('Failed to copy a file in html_static_file: %s: %r'),
|
||||
filename, error)
|
||||
logger.warning(
|
||||
__('Failed to copy a file in html_static_file: %s: %r'), filename, error
|
||||
)
|
||||
|
||||
excluded = Matcher([*self.config.exclude_patterns, '**/.*'])
|
||||
for entry in self.config.html_static_path:
|
||||
copy_asset(
|
||||
self.confdir / entry,
|
||||
self.outdir / '_static',
|
||||
excluded=excluded, context=context,
|
||||
renderer=self.templates, onerror=onerror,
|
||||
excluded=excluded,
|
||||
context=context,
|
||||
renderer=self.templates,
|
||||
onerror=onerror,
|
||||
force=True,
|
||||
)
|
||||
|
||||
@ -916,8 +958,7 @@ class StandaloneHTMLBuilder(Builder):
|
||||
uri = node['uri']
|
||||
reference = nodes.reference('', '', internal=True)
|
||||
if uri in self.images:
|
||||
reference['refuri'] = posixpath.join(self.imgpath,
|
||||
self.images[uri])
|
||||
reference['refuri'] = posixpath.join(self.imgpath, self.images[uri])
|
||||
else:
|
||||
reference['refuri'] = uri
|
||||
node.replace_self(reference)
|
||||
@ -936,9 +977,13 @@ class StandaloneHTMLBuilder(Builder):
|
||||
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 '
|
||||
'incomplete.'))
|
||||
logger.warning(
|
||||
__(
|
||||
"search index couldn't be loaded, but not all "
|
||||
'documents will be built: the index will be '
|
||||
'incomplete.'
|
||||
)
|
||||
)
|
||||
# delete all entries for files that will be rebuilt
|
||||
self.indexer.prune(keep)
|
||||
|
||||
@ -952,12 +997,16 @@ class StandaloneHTMLBuilder(Builder):
|
||||
else:
|
||||
self.indexer.feed(pagename, filename, title, doctree)
|
||||
|
||||
def _get_local_toctree(self, docname: str, collapse: bool = True, **kwargs: Any) -> str:
|
||||
def _get_local_toctree(
|
||||
self, docname: str, collapse: bool = True, **kwargs: Any
|
||||
) -> str:
|
||||
if 'includehidden' not in kwargs:
|
||||
kwargs['includehidden'] = False
|
||||
if kwargs.get('maxdepth') == '':
|
||||
kwargs.pop('maxdepth')
|
||||
toctree = global_toctree_for_doc(self.env, docname, self, collapse=collapse, **kwargs)
|
||||
toctree = global_toctree_for_doc(
|
||||
self.env, docname, self, collapse=collapse, **kwargs
|
||||
)
|
||||
return self.render_partial(toctree)['fragment']
|
||||
|
||||
def get_outfilename(self, pagename: str) -> str:
|
||||
@ -995,7 +1044,8 @@ class StandaloneHTMLBuilder(Builder):
|
||||
return quote(docname) + self.link_suffix
|
||||
|
||||
def handle_page(
|
||||
self, pagename: str,
|
||||
self,
|
||||
pagename: str,
|
||||
addctx: dict[str, Any],
|
||||
templatename: str = 'page.html',
|
||||
outfilename: str | None = None,
|
||||
@ -1011,13 +1061,16 @@ class StandaloneHTMLBuilder(Builder):
|
||||
default_baseuri = default_baseuri.rsplit('#', 1)[0]
|
||||
|
||||
if self.config.html_baseurl:
|
||||
ctx['pageurl'] = posixpath.join(self.config.html_baseurl,
|
||||
pagename + self.out_suffix)
|
||||
ctx['pageurl'] = posixpath.join(
|
||||
self.config.html_baseurl, pagename + self.out_suffix
|
||||
)
|
||||
else:
|
||||
ctx['pageurl'] = None
|
||||
|
||||
def pathto(
|
||||
otheruri: str, resource: bool = False, baseuri: str = default_baseuri,
|
||||
otheruri: str,
|
||||
resource: bool = False,
|
||||
baseuri: str = default_baseuri,
|
||||
) -> str:
|
||||
if resource and '://' in otheruri:
|
||||
# allow non-local resources given by scheme
|
||||
@ -1028,6 +1081,7 @@ class StandaloneHTMLBuilder(Builder):
|
||||
if uri == '#' and not self.allow_sharp_as_current_path:
|
||||
uri = baseuri
|
||||
return uri
|
||||
|
||||
ctx['pathto'] = pathto
|
||||
|
||||
def hasdoc(name: str) -> bool:
|
||||
@ -1036,6 +1090,7 @@ class StandaloneHTMLBuilder(Builder):
|
||||
if name == 'search' and self.search:
|
||||
return True
|
||||
return name == 'genindex' and self.get_builder_config('use_index', 'html')
|
||||
|
||||
ctx['hasdoc'] = hasdoc
|
||||
|
||||
ctx['toctree'] = lambda **kwargs: self._get_local_toctree(pagename, **kwargs)
|
||||
@ -1048,9 +1103,11 @@ class StandaloneHTMLBuilder(Builder):
|
||||
outdir = self.app.outdir
|
||||
|
||||
def css_tag(css: _CascadingStyleSheet) -> str:
|
||||
attrs = [f'{key}="{html.escape(value, quote=True)}"'
|
||||
for key, value in css.attributes.items()
|
||||
if value is not None]
|
||||
attrs = [
|
||||
f'{key}="{html.escape(value, quote=True)}"'
|
||||
for key, value in css.attributes.items()
|
||||
if value is not None
|
||||
]
|
||||
uri = pathto(os.fspath(css.filename), resource=True)
|
||||
# the EPUB format does not allow the use of query components
|
||||
# the Windows help compiler requires that css links
|
||||
@ -1068,9 +1125,11 @@ class StandaloneHTMLBuilder(Builder):
|
||||
return f'<script src="{pathto(js, resource=True)}"></script>'
|
||||
|
||||
body = js.attributes.get('body', '')
|
||||
attrs = [f'{key}="{html.escape(value, quote=True)}"'
|
||||
for key, value in js.attributes.items()
|
||||
if key != 'body' and value is not None]
|
||||
attrs = [
|
||||
f'{key}="{html.escape(value, quote=True)}"'
|
||||
for key, value in js.attributes.items()
|
||||
if key != 'body' and value is not None
|
||||
]
|
||||
|
||||
if not js.filename:
|
||||
if attrs:
|
||||
@ -1099,15 +1158,18 @@ class StandaloneHTMLBuilder(Builder):
|
||||
self._js_files[:] = self._orig_js_files
|
||||
|
||||
self.update_page_context(pagename, templatename, ctx, event_arg)
|
||||
newtmpl = self.app.emit_firstresult('html-page-context', pagename,
|
||||
templatename, ctx, event_arg)
|
||||
newtmpl = self.app.emit_firstresult(
|
||||
'html-page-context', pagename, templatename, ctx, event_arg
|
||||
)
|
||||
if newtmpl:
|
||||
templatename = newtmpl
|
||||
|
||||
# sort JS/CSS before rendering HTML
|
||||
try: # NoQA: SIM105
|
||||
# Convert script_files to list to support non-list script_files (refs: #8889)
|
||||
ctx['script_files'] = sorted(ctx['script_files'], key=lambda js: js.priority)
|
||||
ctx['script_files'] = sorted(
|
||||
ctx['script_files'], key=lambda js: js.priority
|
||||
)
|
||||
except AttributeError:
|
||||
# Skip sorting if users modifies script_files directly (maybe via `html_context`).
|
||||
# refs: #8885
|
||||
@ -1121,33 +1183,42 @@ class StandaloneHTMLBuilder(Builder):
|
||||
try:
|
||||
output = self.templates.render(templatename, ctx)
|
||||
except UnicodeError:
|
||||
logger.warning(__("a Unicode error occurred when rendering the page %s. "
|
||||
"Please make sure all config values that contain "
|
||||
"non-ASCII content are Unicode strings."), pagename)
|
||||
logger.warning(
|
||||
__(
|
||||
'a Unicode error occurred when rendering the page %s. '
|
||||
'Please make sure all config values that contain '
|
||||
'non-ASCII content are Unicode strings.'
|
||||
),
|
||||
pagename,
|
||||
)
|
||||
return
|
||||
except Exception as exc:
|
||||
raise ThemeError(__("An error happened in rendering the page %s.\nReason: %r") %
|
||||
(pagename, exc)) from exc
|
||||
msg = __('An error happened in rendering the page %s.\nReason: %r') % (
|
||||
pagename,
|
||||
exc,
|
||||
)
|
||||
raise ThemeError(msg) from exc
|
||||
|
||||
if not outfilename:
|
||||
outfilename = self.get_outfilename(pagename)
|
||||
# outfilename's path is in general different from self.outdir
|
||||
ensuredir(path.dirname(outfilename))
|
||||
try:
|
||||
with open(outfilename, 'w', encoding=ctx['encoding'],
|
||||
errors='xmlcharrefreplace') as f:
|
||||
with open(
|
||||
outfilename, 'w', encoding=ctx['encoding'], errors='xmlcharrefreplace'
|
||||
) as f:
|
||||
f.write(output)
|
||||
except OSError as err:
|
||||
logger.warning(__("error writing file %s: %s"), outfilename, 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
|
||||
source_name = path.join(self.outdir, '_sources',
|
||||
os_path(ctx['sourcename']))
|
||||
source_name = path.join(self.outdir, '_sources', os_path(ctx['sourcename']))
|
||||
ensuredir(path.dirname(source_name))
|
||||
copyfile(self.env.doc2path(pagename), source_name, force=True)
|
||||
|
||||
def update_page_context(self, pagename: str, templatename: str,
|
||||
ctx: dict[str, Any], event_arg: Any) -> None:
|
||||
def update_page_context(
|
||||
self, pagename: str, templatename: str, ctx: dict[str, Any], event_arg: Any
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
def handle_finish(self) -> None:
|
||||
@ -1210,8 +1281,13 @@ def convert_html_js_files(app: Sphinx, config: Config) -> None:
|
||||
config.html_js_files = html_js_files
|
||||
|
||||
|
||||
def setup_resource_paths(app: Sphinx, pagename: str, templatename: str,
|
||||
context: dict[str, Any], doctree: Node) -> None:
|
||||
def setup_resource_paths(
|
||||
app: Sphinx,
|
||||
pagename: str,
|
||||
templatename: str,
|
||||
context: dict[str, Any],
|
||||
doctree: Node,
|
||||
) -> None:
|
||||
"""Set up relative resource paths."""
|
||||
pathto = context['pathto']
|
||||
|
||||
@ -1232,8 +1308,10 @@ def validate_math_renderer(app: Sphinx) -> None:
|
||||
|
||||
name = app.builder.math_renderer_name # type: ignore[attr-defined]
|
||||
if name is None:
|
||||
raise ConfigError(__('Many math_renderers are registered. '
|
||||
'But no math_renderer is selected.'))
|
||||
msg = __(
|
||||
'Many math_renderers are registered. But no math_renderer is selected.'
|
||||
)
|
||||
raise ConfigError(msg)
|
||||
if name not in app.registry.html_inline_math_renderers:
|
||||
raise ConfigError(__('Unknown math_renderer %r is given.') % name)
|
||||
|
||||
@ -1245,9 +1323,13 @@ def validate_html_extra_path(app: Sphinx, config: Config) -> None:
|
||||
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)) == path.normpath(app.outdir)):
|
||||
logger.warning(__('html_extra_path entry %r is placed inside outdir'), entry)
|
||||
elif (
|
||||
path.splitdrive(app.outdir)[0] == path.splitdrive(extra_path)[0]
|
||||
and path.commonpath((app.outdir, extra_path)) == path.normpath(app.outdir)
|
||||
): # fmt: skip
|
||||
logger.warning(
|
||||
__('html_extra_path entry %r is placed inside outdir'), entry
|
||||
)
|
||||
config.html_extra_path.remove(entry)
|
||||
|
||||
|
||||
@ -1258,26 +1340,34 @@ def validate_html_static_path(app: Sphinx, config: Config) -> None:
|
||||
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)) == path.normpath(app.outdir)):
|
||||
logger.warning(__('html_static_path entry %r is placed inside outdir'), entry)
|
||||
elif (
|
||||
path.splitdrive(app.outdir)[0] == path.splitdrive(static_path)[0]
|
||||
and path.commonpath((app.outdir, static_path)) == path.normpath(app.outdir)
|
||||
): # fmt: skip
|
||||
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)) and
|
||||
not isurl(config.html_logo)):
|
||||
if (
|
||||
config.html_logo
|
||||
and not path.isfile(path.join(app.confdir, config.html_logo))
|
||||
and not isurl(config.html_logo)
|
||||
):
|
||||
logger.warning(__('logo file %r does not exist'), config.html_logo)
|
||||
config.html_logo = None
|
||||
|
||||
|
||||
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)) and
|
||||
not isurl(config.html_favicon)):
|
||||
if (
|
||||
config.html_favicon
|
||||
and not path.isfile(path.join(app.confdir, config.html_favicon))
|
||||
and not isurl(config.html_favicon)
|
||||
):
|
||||
logger.warning(__('favicon file %r does not exist'), config.html_favicon)
|
||||
config.html_favicon = None
|
||||
|
||||
@ -1290,9 +1380,11 @@ def error_on_html_sidebars_string_values(app: Sphinx, config: Config) -> None:
|
||||
errors[pattern] = [pat_sidebars]
|
||||
if not errors:
|
||||
return
|
||||
msg = __("Values in 'html_sidebars' must be a list of strings. "
|
||||
"At least one pattern has a string value: %s. "
|
||||
"Change to `html_sidebars = %r`.")
|
||||
msg = __(
|
||||
"Values in 'html_sidebars' must be a list of strings. "
|
||||
'At least one pattern has a string value: %s. '
|
||||
'Change to `html_sidebars = %r`.'
|
||||
)
|
||||
bad_patterns = ', '.join(map(repr, errors))
|
||||
fixed = config.html_sidebars | errors
|
||||
raise ConfigError(msg % (bad_patterns, fixed))
|
||||
@ -1301,10 +1393,11 @@ def error_on_html_sidebars_string_values(app: Sphinx, config: Config) -> None:
|
||||
def error_on_html_4(_app: Sphinx, config: Config) -> None:
|
||||
"""Error on HTML 4."""
|
||||
if config.html4_writer:
|
||||
raise ConfigError(_(
|
||||
msg = __(
|
||||
'HTML 4 is no longer supported by Sphinx. '
|
||||
'("html4_writer=True" detected in configuration options)',
|
||||
))
|
||||
)
|
||||
raise ConfigError(msg)
|
||||
|
||||
|
||||
def setup(app: Sphinx) -> ExtensionMetadata:
|
||||
@ -1316,7 +1409,11 @@ def setup(app: Sphinx) -> ExtensionMetadata:
|
||||
app.add_config_value('html_theme_path', [], 'html')
|
||||
app.add_config_value('html_theme_options', {}, 'html')
|
||||
app.add_config_value(
|
||||
'html_title', lambda c: _('%s %s documentation') % (c.project, c.release), 'html', str)
|
||||
'html_title',
|
||||
lambda c: _('%s %s documentation') % (c.project, c.release),
|
||||
'html',
|
||||
str,
|
||||
)
|
||||
app.add_config_value('html_short_title', lambda self: self.html_title, 'html')
|
||||
app.add_config_value('html_style', None, 'html', {list, str})
|
||||
app.add_config_value('html_logo', None, 'html', str)
|
||||
@ -1326,8 +1423,9 @@ def setup(app: Sphinx) -> ExtensionMetadata:
|
||||
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_last_updated_time_zone', 'local', 'html',
|
||||
ENUM('GMT', 'local'))
|
||||
app.add_config_value(
|
||||
'html_last_updated_time_zone', 'local', 'html', ENUM('GMT', 'local')
|
||||
)
|
||||
app.add_config_value('html_sidebars', {}, 'html')
|
||||
app.add_config_value('html_additional_pages', {}, 'html')
|
||||
app.add_config_value('html_domain_indices', True, 'html', types={set, list})
|
||||
@ -1354,8 +1452,9 @@ def setup(app: Sphinx) -> ExtensionMetadata:
|
||||
app.add_config_value('html_scaled_image_link', True, 'html')
|
||||
app.add_config_value('html_baseurl', '', 'html')
|
||||
# removal is indefinitely on hold (ref: https://github.com/sphinx-doc/sphinx/issues/10265)
|
||||
app.add_config_value('html_codeblock_linenos_style', 'inline', 'html',
|
||||
ENUM('table', 'inline'))
|
||||
app.add_config_value(
|
||||
'html_codeblock_linenos_style', 'inline', 'html', ENUM('table', 'inline')
|
||||
)
|
||||
app.add_config_value('html_math_renderer', None, 'env')
|
||||
app.add_config_value('html4_writer', False, 'html')
|
||||
|
||||
@ -1390,7 +1489,11 @@ def setup(app: Sphinx) -> ExtensionMetadata:
|
||||
|
||||
# deprecated name -> (object to return, canonical path or empty string, removal version)
|
||||
_DEPRECATED_OBJECTS: dict[str, tuple[Any, str, tuple[int, int]]] = {
|
||||
'Stylesheet': (_CascadingStyleSheet, 'sphinx.builders.html._assets._CascadingStyleSheet', (9, 0)), # NoQA: E501
|
||||
'Stylesheet': (
|
||||
_CascadingStyleSheet,
|
||||
'sphinx.builders.html._assets._CascadingStyleSheet',
|
||||
(9, 0),
|
||||
),
|
||||
'JavaScript': (_JavaScript, 'sphinx.builders.html._assets._JavaScript', (9, 0)),
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,9 @@ class _CascadingStyleSheet:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
filename: str | os.PathLike[str], /, *,
|
||||
filename: str | os.PathLike[str],
|
||||
/,
|
||||
*,
|
||||
priority: int = 500,
|
||||
rel: str = 'stylesheet',
|
||||
type: str = 'text/css',
|
||||
@ -31,20 +33,28 @@ class _CascadingStyleSheet:
|
||||
|
||||
def __str__(self) -> str:
|
||||
attr = ', '.join(f'{k}={v!r}' for k, v in self.attributes.items())
|
||||
return (f'{self.__class__.__name__}({self.filename!r}, '
|
||||
f'priority={self.priority}, '
|
||||
f'{attr})')
|
||||
return (
|
||||
f'{self.__class__.__name__}({self.filename!r}, '
|
||||
f'priority={self.priority}, '
|
||||
f'{attr})'
|
||||
)
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if isinstance(other, str):
|
||||
warnings.warn('The str interface for _CascadingStyleSheet objects is deprecated. '
|
||||
'Use css.filename instead.', RemovedInSphinx90Warning, stacklevel=2)
|
||||
warnings.warn(
|
||||
'The str interface for _CascadingStyleSheet objects is deprecated. '
|
||||
'Use css.filename instead.',
|
||||
RemovedInSphinx90Warning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.filename == other
|
||||
if not isinstance(other, _CascadingStyleSheet):
|
||||
return NotImplemented
|
||||
return (self.filename == other.filename
|
||||
and self.priority == other.priority
|
||||
and self.attributes == other.attributes)
|
||||
return (
|
||||
self.filename == other.filename
|
||||
and self.priority == other.priority
|
||||
and self.attributes == other.attributes
|
||||
)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.filename, self.priority, *sorted(self.attributes.items())))
|
||||
@ -58,13 +68,21 @@ class _CascadingStyleSheet:
|
||||
raise AttributeError(msg)
|
||||
|
||||
def __getattr__(self, key: str) -> str:
|
||||
warnings.warn('The str interface for _CascadingStyleSheet objects is deprecated. '
|
||||
'Use css.filename instead.', RemovedInSphinx90Warning, stacklevel=2)
|
||||
warnings.warn(
|
||||
'The str interface for _CascadingStyleSheet objects is deprecated. '
|
||||
'Use css.filename instead.',
|
||||
RemovedInSphinx90Warning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return getattr(os.fspath(self.filename), key)
|
||||
|
||||
def __getitem__(self, key: int | slice) -> str:
|
||||
warnings.warn('The str interface for _CascadingStyleSheet objects is deprecated. '
|
||||
'Use css.filename instead.', RemovedInSphinx90Warning, stacklevel=2)
|
||||
warnings.warn(
|
||||
'The str interface for _CascadingStyleSheet objects is deprecated. '
|
||||
'Use css.filename instead.',
|
||||
RemovedInSphinx90Warning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return os.fspath(self.filename)[key]
|
||||
|
||||
|
||||
@ -75,7 +93,9 @@ class _JavaScript:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
filename: str | os.PathLike[str], /, *,
|
||||
filename: str | os.PathLike[str],
|
||||
/,
|
||||
*,
|
||||
priority: int = 500,
|
||||
**attributes: str,
|
||||
) -> None:
|
||||
@ -87,20 +107,28 @@ class _JavaScript:
|
||||
attr = ''
|
||||
if self.attributes:
|
||||
attr = ', ' + ', '.join(f'{k}={v!r}' for k, v in self.attributes.items())
|
||||
return (f'{self.__class__.__name__}({self.filename!r}, '
|
||||
f'priority={self.priority}'
|
||||
f'{attr})')
|
||||
return (
|
||||
f'{self.__class__.__name__}({self.filename!r}, '
|
||||
f'priority={self.priority}'
|
||||
f'{attr})'
|
||||
)
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if isinstance(other, str):
|
||||
warnings.warn('The str interface for _JavaScript objects is deprecated. '
|
||||
'Use js.filename instead.', RemovedInSphinx90Warning, stacklevel=2)
|
||||
warnings.warn(
|
||||
'The str interface for _JavaScript objects is deprecated. '
|
||||
'Use js.filename instead.',
|
||||
RemovedInSphinx90Warning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.filename == other
|
||||
if not isinstance(other, _JavaScript):
|
||||
return NotImplemented
|
||||
return (self.filename == other.filename
|
||||
and self.priority == other.priority
|
||||
and self.attributes == other.attributes)
|
||||
return (
|
||||
self.filename == other.filename
|
||||
and self.priority == other.priority
|
||||
and self.attributes == other.attributes
|
||||
)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.filename, self.priority, *sorted(self.attributes.items())))
|
||||
@ -114,13 +142,21 @@ class _JavaScript:
|
||||
raise AttributeError(msg)
|
||||
|
||||
def __getattr__(self, key: str) -> str:
|
||||
warnings.warn('The str interface for _JavaScript objects is deprecated. '
|
||||
'Use js.filename instead.', RemovedInSphinx90Warning, stacklevel=2)
|
||||
warnings.warn(
|
||||
'The str interface for _JavaScript objects is deprecated. '
|
||||
'Use js.filename instead.',
|
||||
RemovedInSphinx90Warning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return getattr(os.fspath(self.filename), key)
|
||||
|
||||
def __getitem__(self, key: int | slice) -> str:
|
||||
warnings.warn('The str interface for _JavaScript objects is deprecated. '
|
||||
'Use js.filename instead.', RemovedInSphinx90Warning, stacklevel=2)
|
||||
warnings.warn(
|
||||
'The str interface for _JavaScript objects is deprecated. '
|
||||
'Use js.filename instead.',
|
||||
RemovedInSphinx90Warning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return os.fspath(self.filename)[key]
|
||||
|
||||
|
||||
|
@ -24,7 +24,7 @@ class BuildInfo:
|
||||
|
||||
@classmethod
|
||||
def load(cls: type[BuildInfo], filename: Path, /) -> BuildInfo:
|
||||
content = filename.read_text(encoding="utf-8")
|
||||
content = filename.read_text(encoding='utf-8')
|
||||
lines = content.splitlines()
|
||||
|
||||
version = lines[0].rstrip()
|
||||
@ -61,8 +61,9 @@ class BuildInfo:
|
||||
self.tags_hash = stable_hash(sorted(tags))
|
||||
|
||||
def __eq__(self, other: BuildInfo) -> bool: # type: ignore[override]
|
||||
return (self.config_hash == other.config_hash and
|
||||
self.tags_hash == other.tags_hash)
|
||||
return (
|
||||
self.config_hash == other.config_hash and self.tags_hash == other.tags_hash
|
||||
)
|
||||
|
||||
def dump(self, filename: Path, /) -> None:
|
||||
build_info = (
|
||||
@ -72,4 +73,4 @@ class BuildInfo:
|
||||
f'config: {self.config_hash}\n'
|
||||
f'tags: {self.tags_hash}\n'
|
||||
)
|
||||
filename.write_text(build_info, encoding="utf-8")
|
||||
filename.write_text(build_info, encoding='utf-8')
|
||||
|
@ -36,16 +36,18 @@ class KeyboardTransform(SphinxPostTransform):
|
||||
default_priority = 400
|
||||
formats = ('html',)
|
||||
pattern = re.compile(r'(?<=.)(-|\+|\^|\s+)(?=.)')
|
||||
multiwords_keys = (('caps', 'lock'),
|
||||
('page', 'down'),
|
||||
('page', 'up'),
|
||||
('scroll', 'lock'),
|
||||
('num', 'lock'),
|
||||
('sys', 'rq'),
|
||||
('back', 'space'))
|
||||
multiwords_keys = (
|
||||
('caps', 'lock'),
|
||||
('page', 'down'),
|
||||
('page', 'up'),
|
||||
('scroll', 'lock'),
|
||||
('num', 'lock'),
|
||||
('sys', 'rq'),
|
||||
('back', 'space'),
|
||||
)
|
||||
|
||||
def run(self, **kwargs: Any) -> None:
|
||||
matcher = NodeMatcher(nodes.literal, classes=["kbd"])
|
||||
matcher = NodeMatcher(nodes.literal, classes=['kbd'])
|
||||
# this list must be pre-created as during iteration new nodes
|
||||
# are added which match the condition in the NodeMatcher.
|
||||
for node in list(matcher.findall(self.document)):
|
||||
@ -61,7 +63,7 @@ class KeyboardTransform(SphinxPostTransform):
|
||||
parts[:3] = []
|
||||
else:
|
||||
key = parts.pop(0)
|
||||
node += nodes.literal('', key, classes=["kbd"])
|
||||
node += nodes.literal('', key, classes=['kbd'])
|
||||
|
||||
try:
|
||||
# key separator (ex. -, +, ^)
|
||||
|
Loading…
Reference in New Issue
Block a user