is updated
-% at each novel such footnote to know what is the target from then on
-% for \sphinxfootnotemark and already encountered [1], or [2],...
-%
-% LaTeX package varioref is not supported by hyperref (from its doc: "There
-% are too many problems with varioref. Nobody has time to sort them out.
-% Therefore this package is now unsupported.") So we will simply use our own
-% macros to access the page number of footnote text and decide whether to print
-% it. \pagename is internationalized by latex-babel.
-\def\spx@thefnmark#1#2{%
- % #1=label for reference, #2=page where footnote was printed
- \ifx\spx@tempa\spx@tempb
- % same page
- #1%
- \else
- \sphinxthefootnotemark{#1}{#2}%
- \fi
-}%
-\def\sphinxfootref@get #1#2#3#4#5\relax{%
- \def\sphinxfootref@label{#1}%
- \def\sphinxfootref@page {#2}%
- \def\sphinxfootref@Href {#4}%
-}%
-\protected\def\sphinxfootref#1{% #1 always explicit number in Sphinx usage
- \spx@opt@BeforeFootnote
- \ltx@ifundefined{r@\thesphinxscope.#1}%
- {\gdef\@thefnmark{?}\H@@footnotemark}%
- {\expandafter\expandafter\expandafter\sphinxfootref@get
- \csname r@\thesphinxscope.#1\endcsname\relax
- \edef\spx@tempa{\thepage}\edef\spx@tempb{\sphinxfootref@page}%
- \protected@xdef\@thefnmark{\spx@thefnmark{\sphinxfootref@label}{\sphinxfootref@page}}%
- \let\spx@@makefnmark\@makefnmark
- \def\@makefnmark{%
- \hyper@linkstart{link}{\sphinxfootref@Href}%
- \spx@@makefnmark
- \hyper@linkend
+\newcounter{sphinxfootnotemark}
+\renewcommand\thesphinxfootnotemark{\number\value{sphinxfootnotemark}}
+% - compares page number of footnote mark versus the one of footnote text
+\def\sphinx@xdef@thefnmark#1{%
+ \expandafter\expandafter\expandafter\sphinx@footref@get
+ \csname r@\thesphinxscope.footnote.#1\endcsname\relax
+ \expandafter\expandafter\expandafter\sphinx@footmark@getpage
+ \csname r@footnotemark.\thesphinxfootnotemark\endcsname\thepage\relax
+ \protected@xdef\@thefnmark{%
+ \ifx\spx@footmarkpage\spx@footrefpage
+ \spx@footreflabel
+ \else
+ % the macro \sphinxthefootnotemark is in sphinx.sty
+ \sphinxthefootnotemark{\spx@footreflabel}{\spx@footrefpage}%
+ \fi
}%
- \H@@footnotemark
- \let\@makefnmark\spx@@makefnmark
- }%
}%
+\def\sphinx@footref@get #1#2#3#4#5\relax{%
+ \def\spx@footreflabel{#1}%
+ \def\spx@footrefpage {#2}%
+ \def\spx@footrefHref {#4}%
+}%
+\def\sphinx@footmark@getpage #1#2#3\relax{%
+ \edef\spx@footmarkpage{#2}%
+}%
+\protected\def\sphinxfootref#1{% #1 always is explicit number in Sphinx
+ \spx@opt@BeforeFootnote
+ % each of \refstepcounter and \label interferes with memoir class detection
+ % of successive footnote marks, so we move them to inside \@makefnmark
+ \let\spx@saved@makefnmark\@makefnmark
+ \ltx@ifundefined{r@\thesphinxscope.footnote.#1}%
+ {\gdef\@thefnmark{?}% on first LaTeX run
+ \refstepcounter{sphinxfootnotemark}\label{footnotemark.\thesphinxfootnotemark}%
+ }%
+ {\sphinx@xdef@thefnmark{#1}% also defines \spx@footrefHref
+ \def\@makefnmark{% will be used by \H@@footnotemark
+ \refstepcounter{sphinxfootnotemark}\label{footnotemark.\thesphinxfootnotemark}%
+ \hyper@linkstart{link}{\spx@footrefHref}%
+ \spx@saved@makefnmark
+ \hyper@linkend
+ }%
+ }%
+ \H@@footnotemark
+ \let\@makefnmark\spx@saved@makefnmark
+}%
\AtBeginDocument{%
% let hyperref less complain
\pdfstringdefDisableCommands{\def\sphinxfootnotemark [#1]{}}%
diff --git a/sphinx/themes/basic/static/doctools.js b/sphinx/themes/basic/static/doctools.js
index 9b5a831b6..c3db08d1c 100644
--- a/sphinx/themes/basic/static/doctools.js
+++ b/sphinx/themes/basic/static/doctools.js
@@ -130,18 +130,16 @@ const Documentation = {
* highlight the search words provided in the url in the text
*/
highlightSearchWords: () => {
- const highlight = new URLSearchParams(window.location.search).get(
- "highlight"
- );
- const terms = highlight ? highlight.split(/\s+/) : [];
+ const highlight =
+ new URLSearchParams(window.location.search).get("highlight") || "";
+ const terms = highlight.toLowerCase().split(/\s+/).filter(x => x);
if (terms.length === 0) return; // nothing to do
- let body = document.querySelectorAll("div.body");
- if (!body.length) body = document.querySelector("body");
+ // There should never be more than one element matching "div.body"
+ const divBody = document.querySelectorAll("div.body");
+ const body = divBody.length ? divBody[0] : document.querySelector("body");
window.setTimeout(() => {
- terms.forEach((term) =>
- _highlightText(body, term.toLowerCase(), "highlighted")
- );
+ terms.forEach((term) => _highlightText(body, term, "highlighted"));
}, 10);
const searchBox = document.getElementById("searchbox");
@@ -169,14 +167,14 @@ const Documentation = {
.querySelectorAll("span.highlighted")
.forEach((el) => el.classList.remove("highlighted"));
const url = new URL(window.location);
- url.searchParams.delete('highlight');
- window.history.replaceState({}, '', url);
+ url.searchParams.delete("highlight");
+ window.history.replaceState({}, "", url);
},
/**
* helper function to focus on search bar
*/
- focusSearchBar : () => {
+ focusSearchBar: () => {
document.querySelectorAll("input[name=q]")[0]?.focus();
},
@@ -206,9 +204,11 @@ const Documentation = {
initOnKeyListeners: () => {
// only install a listener if it is really needed
- if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS &&
- !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS)
- return;
+ if (
+ !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS &&
+ !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS
+ )
+ return;
const blacklistedElements = new Set([
"TEXTAREA",
@@ -218,14 +218,12 @@ const Documentation = {
]);
document.addEventListener("keydown", (event) => {
if (blacklistedElements.has(document.activeElement.tagName)) return; // bail for input elements
- if (event.altKey || event.ctrlKey || event.metaKey)
- return; // bail with special keys
+ if (event.altKey || event.ctrlKey || event.metaKey) return; // bail with special keys
if (!event.shiftKey) {
switch (event.key) {
case "ArrowLeft":
- if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS)
- break;
+ if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break;
const prevLink = document.querySelector('link[rel="prev"]');
if (prevLink && prevLink.href) {
@@ -234,8 +232,7 @@ const Documentation = {
}
break;
case "ArrowRight":
- if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS)
- break;
+ if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break;
const nextLink = document.querySelector('link[rel="next"]');
if (nextLink && nextLink.href) {
@@ -244,8 +241,7 @@ const Documentation = {
}
break;
case "Escape":
- if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS)
- break;
+ if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break;
Documentation.hideSearchWords();
event.preventDefault();
}
@@ -253,9 +249,8 @@ const Documentation = {
// some keyboard layouts may need Shift to get /
switch (event.key) {
- case '/':
- if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS)
- break;
+ case "/":
+ if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break;
Documentation.focusSearchBar();
event.preventDefault();
}
diff --git a/sphinx/themes/basic/static/documentation_options.js_t b/sphinx/themes/basic/static/documentation_options.js_t
index 0e3b92769..693e91799 100644
--- a/sphinx/themes/basic/static/documentation_options.js_t
+++ b/sphinx/themes/basic/static/documentation_options.js_t
@@ -10,5 +10,5 @@ var DOCUMENTATION_OPTIONS = {
SOURCELINK_SUFFIX: '{{ sourcelink_suffix }}',
NAVIGATION_WITH_KEYS: {{ 'true' if theme_navigation_with_keys|tobool else 'false'}},
SHOW_SEARCH_SUMMARY: {{ 'true' if show_search_summary else 'false' }},
- ENABLE_SEARCH_SHORTCUTS: {{ 'true' if enable_search_shortcuts|tobool else 'true'}},
+ ENABLE_SEARCH_SHORTCUTS: {{ 'true' if enable_search_shortcuts|tobool else 'false'}},
};
diff --git a/sphinx/theming.py b/sphinx/theming.py
index 6b8f79c3d..598ed33e3 100644
--- a/sphinx/theming.py
+++ b/sphinx/theming.py
@@ -64,7 +64,7 @@ class Theme:
extract_zip(theme_path, self.themedir)
self.config = configparser.RawConfigParser()
- self.config.read(path.join(self.themedir, THEMECONF))
+ self.config.read(path.join(self.themedir, THEMECONF), encoding='utf-8')
try:
inherit = self.config.get('theme', 'inherit')
diff --git a/sphinx/transforms/__init__.py b/sphinx/transforms/__init__.py
index a2a592221..b661870bf 100644
--- a/sphinx/transforms/__init__.py
+++ b/sphinx/transforms/__init__.py
@@ -5,6 +5,7 @@ import unicodedata
import warnings
from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Tuple, cast
+import docutils
from docutils import nodes
from docutils.nodes import Element, Node, Text
from docutils.transforms import Transform, Transformer
@@ -17,7 +18,7 @@ from sphinx import addnodes
from sphinx.config import Config
from sphinx.deprecation import RemovedInSphinx60Warning
from sphinx.locale import _, __
-from sphinx.util import docutils, logging
+from sphinx.util import logging
from sphinx.util.docutils import new_document
from sphinx.util.i18n import format_date
from sphinx.util.nodes import NodeMatcher, apply_source_workaround, is_smartquotable
@@ -108,7 +109,7 @@ class DefaultSubstitutions(SphinxTransform):
# special handling: can also specify a strftime format
text = format_date(self.config.today_fmt or _('%b %d, %Y'),
language=self.config.language)
- ref.replace_self(nodes.Text(text, text))
+ ref.replace_self(nodes.Text(text))
class MoveModuleTargets(SphinxTransform):
diff --git a/sphinx/util/cfamily.py b/sphinx/util/cfamily.py
index e751ae9bb..2dbb02004 100644
--- a/sphinx/util/cfamily.py
+++ b/sphinx/util/cfamily.py
@@ -128,7 +128,7 @@ class ASTCPPAttribute(ASTAttribute):
def describe_signature(self, signode: TextElement) -> None:
signode.append(addnodes.desc_sig_punctuation('[[', '[['))
- signode.append(nodes.Text(self.arg, self.arg))
+ signode.append(nodes.Text(self.arg))
signode.append(addnodes.desc_sig_punctuation(']]', ']]'))
@@ -161,7 +161,7 @@ class ASTGnuAttributeList(ASTAttribute):
def describe_signature(self, signode: TextElement) -> None:
txt = str(self)
- signode.append(nodes.Text(txt, txt))
+ signode.append(nodes.Text(txt))
class ASTIdAttribute(ASTAttribute):
@@ -174,7 +174,7 @@ class ASTIdAttribute(ASTAttribute):
return self.id
def describe_signature(self, signode: TextElement) -> None:
- signode.append(nodes.Text(self.id, self.id))
+ signode.append(nodes.Text(self.id))
class ASTParenAttribute(ASTAttribute):
@@ -189,7 +189,31 @@ class ASTParenAttribute(ASTAttribute):
def describe_signature(self, signode: TextElement) -> None:
txt = str(self)
- signode.append(nodes.Text(txt, txt))
+ signode.append(nodes.Text(txt))
+
+
+class ASTAttributeList(ASTBaseBase):
+ def __init__(self, attrs: List[ASTAttribute]) -> None:
+ self.attrs = attrs
+
+ def __len__(self) -> int:
+ return len(self.attrs)
+
+ def __add__(self, other: "ASTAttributeList") -> "ASTAttributeList":
+ return ASTAttributeList(self.attrs + other.attrs)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return ' '.join(transform(attr) for attr in self.attrs)
+
+ def describe_signature(self, signode: TextElement) -> None:
+ if len(self.attrs) == 0:
+ return
+ self.attrs[0].describe_signature(signode)
+ if len(self.attrs) == 1:
+ return
+ for attr in self.attrs[1:]:
+ signode.append(addnodes.desc_sig_space())
+ attr.describe_signature(signode)
################################################################################
@@ -423,5 +447,14 @@ class BaseParser:
return None
+ def _parse_attribute_list(self) -> ASTAttributeList:
+ res = []
+ while True:
+ attr = self._parse_attribute()
+ if attr is None:
+ break
+ res.append(attr)
+ return ASTAttributeList(res)
+
def _parse_paren_expression_list(self) -> ASTBaseParenExprList:
raise NotImplementedError
diff --git a/sphinx/util/docstrings.py b/sphinx/util/docstrings.py
index 11c823b33..6f341d80e 100644
--- a/sphinx/util/docstrings.py
+++ b/sphinx/util/docstrings.py
@@ -7,7 +7,7 @@ from typing import Dict, List, Tuple
from docutils.parsers.rst.states import Body
-from sphinx.deprecation import RemovedInSphinx50Warning, RemovedInSphinx60Warning
+from sphinx.deprecation import RemovedInSphinx60Warning
field_list_item_re = re.compile(Body.patterns['field_marker'])
@@ -49,35 +49,27 @@ def extract_metadata(s: str) -> Dict[str, str]:
return metadata
-def prepare_docstring(s: str, ignore: int = None, tabsize: int = 8) -> List[str]:
+def prepare_docstring(s: str, tabsize: int = 8) -> List[str]:
"""Convert a docstring into lines of parseable reST. Remove common leading
- indentation, where the indentation of a given number of lines (usually just
- one) is ignored.
+ indentation, where the indentation of the first line is ignored.
Return the docstring as a list of lines usable for inserting into a docutils
ViewList (used as argument of nested_parse().) An empty line is added to
act as a separator between this docstring and following content.
"""
- if ignore is None:
- ignore = 1
- else:
- warnings.warn("The 'ignore' argument to prepare_docstring() is deprecated.",
- RemovedInSphinx50Warning, stacklevel=2)
-
lines = s.expandtabs(tabsize).splitlines()
# Find minimum indentation of any non-blank lines after ignored lines.
margin = sys.maxsize
- for line in lines[ignore:]:
+ for line in lines[1:]:
content = len(line.lstrip())
if content:
indent = len(line) - content
margin = min(margin, indent)
- # Remove indentation from ignored lines.
- for i in range(ignore):
- if i < len(lines):
- lines[i] = lines[i].lstrip()
+ # Remove indentation from the first line.
+ if len(lines):
+ lines[0] = lines[0].lstrip()
if margin < sys.maxsize:
- for i in range(ignore, len(lines)):
+ for i in range(1, len(lines)):
lines[i] = lines[i][margin:]
# Remove any leading blank lines.
while lines and not lines[0]:
diff --git a/sphinx/util/docutils.py b/sphinx/util/docutils.py
index f954c81d5..e1fd78096 100644
--- a/sphinx/util/docutils.py
+++ b/sphinx/util/docutils.py
@@ -2,6 +2,7 @@
import os
import re
+import warnings
from contextlib import contextmanager
from copy import copy
from os import path
@@ -17,8 +18,8 @@ from docutils.parsers.rst import Directive, directives, roles
from docutils.parsers.rst.states import Inliner
from docutils.statemachine import State, StateMachine, StringList
from docutils.utils import Reporter, unescape
-from packaging import version
+from sphinx.deprecation import RemovedInSphinx70Warning, deprecated_alias
from sphinx.errors import SphinxError
from sphinx.locale import _, __
from sphinx.util import logging
@@ -32,8 +33,14 @@ if TYPE_CHECKING:
from sphinx.config import Config
from sphinx.environment import BuildEnvironment
-
-__version_info__ = version.parse(docutils.__version__).release
+deprecated_alias('sphinx.util.docutils',
+ {
+ '__version_info__': docutils.__version_info__,
+ },
+ RemovedInSphinx70Warning,
+ {
+ '__version_info__': 'docutils.__version_info__',
+ })
additional_nodes: Set[Type[Element]] = set()
@@ -312,7 +319,9 @@ class NullReporter(Reporter):
def is_html5_writer_available() -> bool:
- return __version_info__ > (0, 13, 0)
+ warnings.warn('is_html5_writer_available() is deprecated.',
+ RemovedInSphinx70Warning)
+ return True
@contextmanager
@@ -338,6 +347,7 @@ class SphinxFileOutput(FileOutput):
def __init__(self, **kwargs: Any) -> None:
self.overwrite_if_changed = kwargs.pop('overwrite_if_changed', False)
+ kwargs.setdefault('encoding', 'utf-8')
super().__init__(**kwargs)
def write(self, data: str) -> str:
@@ -542,7 +552,7 @@ class SphinxTranslator(nodes.NodeVisitor):
# Node.findall() is a new interface to traverse a doctree since docutils-0.18.
# This applies a patch docutils-0.17 or older to be available Node.findall()
# method to use it from our codebase.
-if __version_info__ < (0, 18):
+if docutils.__version_info__ < (0, 18):
def findall(self, *args, **kwargs):
return iter(self.traverse(*args, **kwargs))
diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py
index cdcf76965..b25a75fb5 100644
--- a/sphinx/util/inspect.py
+++ b/sphinx/util/inspect.py
@@ -487,6 +487,12 @@ class TypeAliasForwardRef:
def __eq__(self, other: Any) -> bool:
return self.name == other
+ def __hash__(self) -> int:
+ return hash(self.name)
+
+ def __repr__(self) -> str:
+ return self.name
+
class TypeAliasModule:
"""Pseudo module class for autodoc_type_aliases."""
diff --git a/sphinx/util/jsdump.py b/sphinx/util/jsdump.py
index ed5aea4ba..484f35b7e 100644
--- a/sphinx/util/jsdump.py
+++ b/sphinx/util/jsdump.py
@@ -4,8 +4,14 @@ Uses the basestring encode function from simplejson by Bob Ippolito.
"""
import re
+import warnings
from typing import IO, Any, Dict, List, Match, Union
+from sphinx.deprecation import RemovedInSphinx70Warning
+
+warnings.warn('"sphinx.util.jsdump" has been deprecated. Please use "json" instead.',
+ RemovedInSphinx70Warning)
+
_str_re = re.compile(r'"(\\\\|\\"|[^"])*"')
_int_re = re.compile(r'\d+')
_name_re = re.compile(r'[a-zA-Z_]\w*')
diff --git a/sphinx/util/smartypants.py b/sphinx/util/smartypants.py
index bc4171631..ab300e094 100644
--- a/sphinx/util/smartypants.py
+++ b/sphinx/util/smartypants.py
@@ -30,7 +30,6 @@ from typing import Generator, Iterable, Tuple
from docutils.utils import smartquotes
from sphinx.deprecation import RemovedInSphinx60Warning
-from sphinx.util.docutils import __version_info__ as docutils_version
warnings.warn('sphinx.util.smartypants is deprecated.',
RemovedInSphinx60Warning)
@@ -373,17 +372,3 @@ def educate_tokens(text_tokens: Iterable[Tuple[str, str]],
text = smartquotes.processEscapes(text, restore=True)
yield text
-
-
-if docutils_version < (0, 13, 2):
- # Monkey patch the old docutils versions to fix the issues mentioned
- # at https://sourceforge.net/p/docutils/bugs/313/
- # at https://sourceforge.net/p/docutils/bugs/317/
- # and more
- smartquotes.educateQuotes = educateQuotes
- smartquotes.educate_tokens = educate_tokens
-
- # Fix the issue with French quotes mentioned at
- # https://sourceforge.net/p/docutils/mailman/message/35760696/
- # Add/fix other languages as well
- smartquotes.smartchars.quotes = langquotes
diff --git a/sphinx/util/typing.py b/sphinx/util/typing.py
index 1534b2c8e..8e48b184b 100644
--- a/sphinx/util/typing.py
+++ b/sphinx/util/typing.py
@@ -215,6 +215,9 @@ def _restify_py37(cls: Optional[Type], mode: str = 'fully-qualified-except-typin
return text
elif isinstance(cls, typing._SpecialForm):
return ':py:obj:`~%s.%s`' % (cls.__module__, cls._name)
+ elif sys.version_info >= (3, 11) and cls is typing.Any:
+ # handle bpo-46998
+ return f':py:obj:`~{cls.__module__}.{cls.__name__}`'
elif hasattr(cls, '__qualname__'):
if cls.__module__ == 'typing':
return ':py:class:`~%s.%s`' % (cls.__module__, cls.__qualname__)
diff --git a/sphinx/writers/html.py b/sphinx/writers/html.py
index 99c9aeeb3..b0d442aef 100644
--- a/sphinx/writers/html.py
+++ b/sphinx/writers/html.py
@@ -503,10 +503,25 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator):
if 'kbd' in node['classes']:
self.body.append(self.starttag(node, 'kbd', '',
CLASS='docutils literal notranslate'))
- else:
+ return
+ lang = node.get("language", None)
+ if 'code' not in node['classes'] or not lang:
self.body.append(self.starttag(node, 'code', '',
CLASS='docutils literal notranslate'))
self.protect_literal_text += 1
+ return
+
+ opts = self.config.highlight_options.get(lang, {})
+ highlighted = self.highlighter.highlight_block(
+ node.astext(), lang, opts=opts, location=node, nowrap=True)
+ starttag = self.starttag(
+ node,
+ "code",
+ suffix="",
+ CLASS="docutils literal highlight highlight-%s" % lang,
+ )
+ self.body.append(starttag + highlighted.strip() + "")
+ raise nodes.SkipNode
def depart_literal(self, node: Element) -> None:
if 'kbd' in node['classes']:
diff --git a/sphinx/writers/html5.py b/sphinx/writers/html5.py
index 38ac7ae3b..9f33f5458 100644
--- a/sphinx/writers/html5.py
+++ b/sphinx/writers/html5.py
@@ -462,10 +462,25 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
if 'kbd' in node['classes']:
self.body.append(self.starttag(node, 'kbd', '',
CLASS='docutils literal notranslate'))
- else:
+ return
+ lang = node.get("language", None)
+ if 'code' not in node['classes'] or not lang:
self.body.append(self.starttag(node, 'code', '',
CLASS='docutils literal notranslate'))
self.protect_literal_text += 1
+ return
+
+ opts = self.config.highlight_options.get(lang, {})
+ highlighted = self.highlighter.highlight_block(
+ node.astext(), lang, opts=opts, location=node, nowrap=True)
+ starttag = self.starttag(
+ node,
+ "code",
+ suffix="",
+ CLASS="docutils literal highlight highlight-%s" % lang,
+ )
+ self.body.append(starttag + highlighted.strip() + "")
+ raise nodes.SkipNode
def depart_literal(self, node: Element) -> None:
if 'kbd' in node['classes']:
diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py
index 680f06bf5..e0c7d56f8 100644
--- a/sphinx/writers/latex.py
+++ b/sphinx/writers/latex.py
@@ -814,16 +814,14 @@ class LaTeXTranslator(SphinxTranslator):
def visit_footnote(self, node: Element) -> None:
self.in_footnote += 1
label = cast(nodes.label, node[0])
- if 'referred' in node:
- self.body.append(r'\sphinxstepexplicit ')
if self.in_parsed_literal:
self.body.append(r'\begin{footnote}[%s]' % label.astext())
else:
self.body.append('%' + CR)
self.body.append(r'\begin{footnote}[%s]' % label.astext())
if 'referred' in node:
- self.body.append(r'\phantomsection'
- r'\label{\thesphinxscope.%s}%%' % label.astext() + CR)
+ # TODO: in future maybe output a latex macro with backrefs here
+ pass
self.body.append(r'\sphinxAtStartFootnote' + CR)
def depart_footnote(self, node: Element) -> None:
@@ -1697,10 +1695,27 @@ class LaTeXTranslator(SphinxTranslator):
def visit_literal(self, node: Element) -> None:
if self.in_title:
self.body.append(r'\sphinxstyleliteralintitle{\sphinxupquote{')
+ return
elif 'kbd' in node['classes']:
self.body.append(r'\sphinxkeyboard{\sphinxupquote{')
- else:
+ return
+ lang = node.get("language", None)
+ if 'code' not in node['classes'] or not lang:
self.body.append(r'\sphinxcode{\sphinxupquote{')
+ return
+
+ opts = self.config.highlight_options.get(lang, {})
+ hlcode = self.highlighter.highlight_block(
+ node.astext(), lang, opts=opts, location=node)
+ # TODO: Use nowrap option once LaTeX formatter supports it
+ # https://github.com/pygments/pygments/pull/1343
+ hlcode = hlcode.replace(r'\begin{Verbatim}[commandchars=\\\{\}]',
+ r'\sphinxcode{\sphinxupquote{')
+ # get consistent trailer
+ hlcode = hlcode.rstrip()[:-14] # strip \end{Verbatim}
+ self.body.append(hlcode)
+ self.body.append('}}')
+ raise nodes.SkipNode
def depart_literal(self, node: Element) -> None:
self.body.append('}}')
@@ -1717,9 +1732,7 @@ class LaTeXTranslator(SphinxTranslator):
def visit_footnotetext(self, node: Element) -> None:
label = cast(nodes.label, node[0])
self.body.append('%' + CR)
- self.body.append(r'\begin{footnotetext}[%s]'
- r'\phantomsection\label{\thesphinxscope.%s}%%'
- % (label.astext(), label.astext()) + CR)
+ self.body.append(r'\begin{footnotetext}[%s]' % label.astext())
self.body.append(r'\sphinxAtStartFootnote' + CR)
def depart_footnotetext(self, node: Element) -> None:
diff --git a/tests/roots/test-apidoc-duplicates/fish_licence/halibut.cpython-38-x86_64-linux-gnu.so b/tests/roots/test-apidoc-duplicates/fish_licence/halibut.cpython-38-x86_64-linux-gnu.so
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/roots/test-apidoc-duplicates/fish_licence/halibut.pyx b/tests/roots/test-apidoc-duplicates/fish_licence/halibut.pyx
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/roots/test-apidoc-toc/mypackage/main.py b/tests/roots/test-apidoc-toc/mypackage/main.py
index c43573a38..d448cc847 100755
--- a/tests/roots/test-apidoc-toc/mypackage/main.py
+++ b/tests/roots/test-apidoc-toc/mypackage/main.py
@@ -10,6 +10,6 @@ if __name__ == "__main__":
res_path = \
os.path.join(os.path.dirname(mod_resource.__file__), 'resource.txt')
- with open(res_path) as f:
+ with open(res_path, encoding='utf-8') as f:
text = f.read()
print("From mod_resource:resource.txt -> {}".format(text))
diff --git a/tests/roots/test-ext-autodoc/target/autodoc_type_aliases.py b/tests/roots/test-ext-autodoc/target/autodoc_type_aliases.py
index d8a2fecef..f2c07a0c7 100644
--- a/tests/roots/test-ext-autodoc/target/autodoc_type_aliases.py
+++ b/tests/roots/test-ext-autodoc/target/autodoc_type_aliases.py
@@ -1,7 +1,7 @@
from __future__ import annotations
import io
-from typing import overload
+from typing import Optional, overload
myint = int
@@ -11,6 +11,9 @@ variable: myint
#: docstring
variable2 = None # type: myint
+#: docstring
+variable3: Optional[myint]
+
def read(r: io.BytesIO) -> io.StringIO:
"""docstring"""
diff --git a/tests/roots/test-ext-autosummary/autosummary_class_module.py b/tests/roots/test-ext-autosummary/autosummary_class_module.py
new file mode 100644
index 000000000..f13de1703
--- /dev/null
+++ b/tests/roots/test-ext-autosummary/autosummary_class_module.py
@@ -0,0 +1,2 @@
+class Class():
+ pass
diff --git a/tests/roots/test-ext-autosummary/autosummary_dummy_module.py b/tests/roots/test-ext-autosummary/autosummary_dummy_module.py
index 38e961990..9c4a0445d 100644
--- a/tests/roots/test-ext-autosummary/autosummary_dummy_module.py
+++ b/tests/roots/test-ext-autosummary/autosummary_dummy_module.py
@@ -1,6 +1,8 @@
from os import path # NOQA
from typing import Union
+from autosummary_class_module import Class
+
__all__ = [
"CONSTANT1",
"Exc",
@@ -60,3 +62,7 @@ class _Exc(Exception):
qux = 2
#: a module-level attribute that has been excluded from __all__
quuz = 2
+
+considered_as_imported = Class()
+non_imported_member = Class()
+""" This attribute has a docstring, so it is recognized as a not-imported member """
diff --git a/tests/roots/test-reST-code-role/conf.py b/tests/roots/test-reST-code-role/conf.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/roots/test-reST-code-role/index.rst b/tests/roots/test-reST-code-role/index.rst
new file mode 100644
index 000000000..5be6bfc57
--- /dev/null
+++ b/tests/roots/test-reST-code-role/index.rst
@@ -0,0 +1,9 @@
+.. role:: python(code)
+ :language: python
+ :class: highlight
+
+Inline :python:`def foo(1 + 2 + None + "abc"): pass` code block
+
+.. code-block:: python
+
+ def foo(1 + 2 + None + "abc"): pass
diff --git a/tests/test_api_translator.py b/tests/test_api_translator.py
index aad5a2ccc..25aee0c61 100644
--- a/tests/test_api_translator.py
+++ b/tests/test_api_translator.py
@@ -2,10 +2,9 @@
import sys
+import docutils
import pytest
-from sphinx.util import docutils
-
@pytest.fixture(scope='module', autouse=True)
def setup_module(rootdir):
diff --git a/tests/test_build.py b/tests/test_build.py
index f2af19565..fd123e294 100644
--- a/tests/test_build.py
+++ b/tests/test_build.py
@@ -37,14 +37,14 @@ def nonascii_srcdir(request, rootdir, sphinx_test_tempdir):
(srcdir / (test_name + '.txt')).write_text(dedent("""
nonascii file name page
=======================
- """))
+ """), encoding='utf8')
root_doc = srcdir / 'index.txt'
- root_doc.write_text(root_doc.read_text() + dedent("""
+ root_doc.write_text(root_doc.read_text(encoding='utf8') + dedent("""
.. toctree::
%(test_name)s/%(test_name)s
- """ % {'test_name': test_name}))
+ """ % {'test_name': test_name}), encoding='utf8')
return srcdir
@@ -63,7 +63,7 @@ def test_build_all(requests_head, make_app, nonascii_srcdir, buildername):
def test_root_doc_not_found(tempdir, make_app):
- (tempdir / 'conf.py').write_text('')
+ (tempdir / 'conf.py').write_text('', encoding='utf8')
assert tempdir.listdir() == ['conf.py']
app = make_app('dummy', srcdir=tempdir)
diff --git a/tests/test_build_changes.py b/tests/test_build_changes.py
index a4a07619b..b340c8d54 100644
--- a/tests/test_build_changes.py
+++ b/tests/test_build_changes.py
@@ -8,7 +8,7 @@ def test_build(app):
app.build()
# TODO: Use better checking of html content
- htmltext = (app.outdir / 'changes.html').read_text()
+ htmltext = (app.outdir / 'changes.html').read_text(encoding='utf8')
assert 'New in version 0.6: Some funny stuff.' in htmltext
assert 'Changed in version 0.6: Even more funny stuff.' in htmltext
assert 'Deprecated since version 0.6: Boring stuff.' in htmltext
diff --git a/tests/test_build_dirhtml.py b/tests/test_build_dirhtml.py
index 25598f795..a84f68dd3 100644
--- a/tests/test_build_dirhtml.py
+++ b/tests/test_build_dirhtml.py
@@ -17,7 +17,7 @@ def test_dirhtml(app, status, warning):
assert (app.outdir / 'foo/foo_2/index.html').exists()
assert (app.outdir / 'bar/index.html').exists()
- content = (app.outdir / 'index.html').read_text()
+ content = (app.outdir / 'index.html').read_text(encoding='utf8')
assert 'href="foo/"' in content
assert 'href="foo/foo_1/"' in content
assert 'href="foo/foo_2/"' in content
diff --git a/tests/test_build_epub.py b/tests/test_build_epub.py
index 4ba29df85..90dbb0ce1 100644
--- a/tests/test_build_epub.py
+++ b/tests/test_build_epub.py
@@ -57,11 +57,11 @@ class EPUBElementTree:
@pytest.mark.sphinx('epub', testroot='basic')
def test_build_epub(app):
app.build()
- assert (app.outdir / 'mimetype').read_text() == 'application/epub+zip'
+ assert (app.outdir / 'mimetype').read_text(encoding='utf8') == 'application/epub+zip'
assert (app.outdir / 'META-INF' / 'container.xml').exists()
# toc.ncx
- toc = EPUBElementTree.fromstring((app.outdir / 'toc.ncx').read_text())
+ toc = EPUBElementTree.fromstring((app.outdir / 'toc.ncx').read_text(encoding='utf8'))
assert toc.find("./ncx:docTitle/ncx:text").text == 'Python'
# toc.ncx / head
@@ -81,7 +81,7 @@ def test_build_epub(app):
assert navlabel.text == 'The basic Sphinx documentation for testing'
# content.opf
- opf = EPUBElementTree.fromstring((app.outdir / 'content.opf').read_text())
+ opf = EPUBElementTree.fromstring((app.outdir / 'content.opf').read_text(encoding='utf8'))
# content.opf / metadata
metadata = opf.find("./idpf:metadata")
@@ -133,7 +133,7 @@ def test_build_epub(app):
assert reference.get('href') == 'index.xhtml'
# nav.xhtml
- nav = EPUBElementTree.fromstring((app.outdir / 'nav.xhtml').read_text())
+ nav = EPUBElementTree.fromstring((app.outdir / 'nav.xhtml').read_text(encoding='utf8'))
assert nav.attrib == {'lang': 'en',
'{http://www.w3.org/XML/1998/namespace}lang': 'en'}
assert nav.find("./xhtml:head/xhtml:title").text == 'Table of Contents'
@@ -153,7 +153,7 @@ def test_epub_cover(app):
app.build()
# content.opf / metadata
- opf = EPUBElementTree.fromstring((app.outdir / 'content.opf').read_text())
+ opf = EPUBElementTree.fromstring((app.outdir / 'content.opf').read_text(encoding='utf8'))
cover_image = opf.find("./idpf:manifest/idpf:item[@href='%s']" % app.config.epub_cover[0])
cover = opf.find("./idpf:metadata/idpf:meta[@name='cover']")
assert cover
@@ -276,7 +276,7 @@ def test_epub_writing_mode(app):
app.build()
# horizontal / page-progression-direction
- opf = EPUBElementTree.fromstring((app.outdir / 'content.opf').read_text())
+ opf = EPUBElementTree.fromstring((app.outdir / 'content.opf').read_text(encoding='utf8'))
assert opf.find("./idpf:spine").get('page-progression-direction') == 'ltr'
# horizontal / ibooks:scroll-axis
@@ -284,7 +284,7 @@ def test_epub_writing_mode(app):
assert metadata.find("./idpf:meta[@property='ibooks:scroll-axis']").text == 'vertical'
# horizontal / writing-mode (CSS)
- css = (app.outdir / '_static' / 'epub.css').read_text()
+ css = (app.outdir / '_static' / 'epub.css').read_text(encoding='utf8')
assert 'writing-mode: horizontal-tb;' in css
# vertical
@@ -293,7 +293,7 @@ def test_epub_writing_mode(app):
app.build()
# vertical / page-progression-direction
- opf = EPUBElementTree.fromstring((app.outdir / 'content.opf').read_text())
+ opf = EPUBElementTree.fromstring((app.outdir / 'content.opf').read_text(encoding='utf8'))
assert opf.find("./idpf:spine").get('page-progression-direction') == 'rtl'
# vertical / ibooks:scroll-axis
@@ -301,7 +301,7 @@ def test_epub_writing_mode(app):
assert metadata.find("./idpf:meta[@property='ibooks:scroll-axis']").text == 'horizontal'
# vertical / writing-mode (CSS)
- css = (app.outdir / '_static' / 'epub.css').read_text()
+ css = (app.outdir / '_static' / 'epub.css').read_text(encoding='utf8')
assert 'writing-mode: vertical-rl;' in css
@@ -309,7 +309,7 @@ def test_epub_writing_mode(app):
def test_epub_anchor_id(app):
app.build()
- html = (app.outdir / 'index.xhtml').read_text()
+ html = (app.outdir / 'index.xhtml').read_text(encoding='utf8')
assert (''
'blah blah blah
' in html)
assert (''
@@ -322,7 +322,7 @@ def test_epub_assets(app):
app.builder.build_all()
# epub_sytlesheets (same as html_css_files)
- content = (app.outdir / 'index.xhtml').read_text()
+ content = (app.outdir / 'index.xhtml').read_text(encoding='utf8')
assert (''
in content)
assert ('' in content
# files in html_css_files are not outputted
@@ -350,7 +350,7 @@ def test_html_download_role(app, status, warning):
app.build()
assert not (app.outdir / '_downloads' / 'dummy.dat').exists()
- content = (app.outdir / 'index.xhtml').read_text()
+ content = (app.outdir / 'index.xhtml').read_text(encoding='utf8')
assert (''
'dummy.dat
' in content)
assert (''
diff --git a/tests/test_build_gettext.py b/tests/test_build_gettext.py
index 6d80324bd..3c4617e84 100644
--- a/tests/test_build_gettext.py
+++ b/tests/test_build_gettext.py
@@ -23,7 +23,7 @@ def test_build_gettext(app):
assert (app.outdir / 'subdir.pot').isfile()
# regression test for issue #960
- catalog = (app.outdir / 'markup.pot').read_text()
+ catalog = (app.outdir / 'markup.pot').read_text(encoding='utf8')
assert 'msgid "something, something else, something more"' in catalog
@@ -76,7 +76,7 @@ def test_gettext_index_entries(app):
return m.groups()[0]
return None
- pot = (app.outdir / 'index_entries.pot').read_text()
+ pot = (app.outdir / 'index_entries.pot').read_text(encoding='utf8')
msgids = [_f for _f in map(msgid_getter, pot.splitlines()) if _f]
expected_msgids = [
@@ -125,7 +125,7 @@ def test_gettext_disable_index_entries(app):
return m.groups()[0]
return None
- pot = (app.outdir / 'index_entries.pot').read_text()
+ pot = (app.outdir / 'index_entries.pot').read_text(encoding='utf8')
msgids = [_f for _f in map(msgid_getter, pot.splitlines()) if _f]
expected_msgids = [
@@ -148,7 +148,7 @@ def test_gettext_template(app):
app.builder.build_all()
assert (app.outdir / 'sphinx.pot').isfile()
- result = (app.outdir / 'sphinx.pot').read_text()
+ result = (app.outdir / 'sphinx.pot').read_text(encoding='utf8')
assert "Welcome" in result
assert "Sphinx %(version)s" in result
@@ -158,7 +158,7 @@ def test_gettext_template_msgid_order_in_sphinxpot(app):
app.builder.build_all()
assert (app.outdir / 'sphinx.pot').isfile()
- result = (app.outdir / 'sphinx.pot').read_text()
+ result = (app.outdir / 'sphinx.pot').read_text(encoding='utf8')
assert re.search(
('msgid "Template 1".*'
'msgid "This is Template 1\\.".*'
@@ -176,7 +176,7 @@ def test_build_single_pot(app):
assert (app.outdir / 'documentation.pot').isfile()
- result = (app.outdir / 'documentation.pot').read_text()
+ result = (app.outdir / 'documentation.pot').read_text(encoding='utf8')
assert re.search(
('msgid "Todo".*'
'msgid "Like footnotes.".*'
diff --git a/tests/test_build_html.py b/tests/test_build_html.py
index 370f25d08..75f89a67a 100644
--- a/tests/test_build_html.py
+++ b/tests/test_build_html.py
@@ -5,6 +5,7 @@ import re
from itertools import chain, cycle
from unittest.mock import ANY, call, patch
+import docutils
import pygments
import pytest
from html5lib import HTMLParser
@@ -13,7 +14,7 @@ from packaging import version
from sphinx.builders.html import validate_html_extra_path, validate_html_static_path
from sphinx.errors import ConfigError
from sphinx.testing.util import strip_escseq
-from sphinx.util import docutils, md5
+from sphinx.util import md5
from sphinx.util.inventory import InventoryFile
if docutils.__version_info__ < (0, 17):
@@ -479,7 +480,7 @@ def test_html_download(app):
app.build()
# subdir/includes.html
- result = (app.outdir / 'subdir' / 'includes.html').read_text()
+ result = (app.outdir / 'subdir' / 'includes.html').read_text(encoding='utf8')
pattern = ('')
matched = re.search(pattern, result)
@@ -488,7 +489,7 @@ def test_html_download(app):
filename = matched.group(1)
# includes.html
- result = (app.outdir / 'includes.html').read_text()
+ result = (app.outdir / 'includes.html').read_text(encoding='utf8')
pattern = ('')
matched = re.search(pattern, result)
@@ -511,7 +512,7 @@ def test_html_download_role(app, status, warning):
digest_another = md5(b'another/dummy.dat').hexdigest()
assert (app.outdir / '_downloads' / digest_another / 'dummy.dat').exists()
- content = (app.outdir / 'index.html').read_text()
+ content = (app.outdir / 'index.html').read_text(encoding='utf8')
assert ((''
''
@@ -693,9 +694,9 @@ def test_numfig_disabled(app, cached_etree_parse, fname, expect):
def test_numfig_without_numbered_toctree_warn(app, warning):
app.build()
# remove :numbered: option
- index = (app.srcdir / 'index.rst').read_text()
+ index = (app.srcdir / 'index.rst').read_text(encoding='utf8')
index = re.sub(':numbered:.*', '', index)
- (app.srcdir / 'index.rst').write_text(index)
+ (app.srcdir / 'index.rst').write_text(index, encoding='utf8')
app.builder.build_all()
warnings = warning.getvalue()
@@ -781,9 +782,9 @@ def test_numfig_without_numbered_toctree_warn(app, warning):
confoverrides={'numfig': True})
def test_numfig_without_numbered_toctree(app, cached_etree_parse, fname, expect):
# remove :numbered: option
- index = (app.srcdir / 'index.rst').read_text()
+ index = (app.srcdir / 'index.rst').read_text(encoding='utf8')
index = re.sub(':numbered:.*', '', index)
- (app.srcdir / 'index.rst').write_text(index)
+ (app.srcdir / 'index.rst').write_text(index, encoding='utf8')
if not app.outdir.listdir():
app.build()
@@ -1171,7 +1172,7 @@ def test_html_assets(app):
assert not (app.outdir / '_static' / '.htaccess').exists()
assert not (app.outdir / '_static' / '.htpasswd').exists()
assert (app.outdir / '_static' / 'API.html').exists()
- assert (app.outdir / '_static' / 'API.html').read_text() == 'Sphinx-1.4.4'
+ assert (app.outdir / '_static' / 'API.html').read_text(encoding='utf8') == 'Sphinx-1.4.4'
assert (app.outdir / '_static' / 'css' / 'style.css').exists()
assert (app.outdir / '_static' / 'js' / 'custom.js').exists()
assert (app.outdir / '_static' / 'rimg.png').exists()
@@ -1192,7 +1193,7 @@ def test_html_assets(app):
assert not (app.outdir / 'subdir' / '.htpasswd').exists()
# html_css_files
- content = (app.outdir / 'index.html').read_text()
+ content = (app.outdir / 'index.html').read_text(encoding='utf8')
assert '' in content
assert ('' in content)
@@ -1215,7 +1216,7 @@ def test_assets_order(app):
app.add_js_file('lazy.js', priority=900)
app.builder.build_all()
- content = (app.outdir / 'index.html').read_text()
+ content = (app.outdir / 'index.html').read_text(encoding='utf8')
# css_files
expected = ['_static/early.css', '_static/pygments.css', '_static/alabaster.css',
@@ -1239,7 +1240,7 @@ def test_javscript_loading_method(app):
app.add_js_file('late.js', loading_method='defer')
app.builder.build_all()
- content = (app.outdir / 'index.html').read_text()
+ content = (app.outdir / 'index.html').read_text(encoding='utf8')
assert '' in content
assert '' in content
@@ -1274,7 +1275,7 @@ def test_html_sourcelink_suffix_empty(app):
def test_html_entity(app):
app.builder.build_all()
valid_entities = {'amp', 'lt', 'gt', 'quot', 'apos'}
- content = (app.outdir / 'index.html').read_text()
+ content = (app.outdir / 'index.html').read_text(encoding='utf8')
for entity in re.findall(r'&([a-z]+);', content, re.M):
assert entity not in valid_entities
@@ -1316,7 +1317,7 @@ def test_html_inventory(app):
@pytest.mark.sphinx('html', testroot='images', confoverrides={'html_sourcelink_suffix': ''})
def test_html_anchor_for_figure(app):
app.builder.build_all()
- content = (app.outdir / 'index.html').read_text()
+ content = (app.outdir / 'index.html').read_text(encoding='utf8')
if docutils.__version_info__ < (0, 17):
assert ('The caption of pic'
'
'
@@ -1330,7 +1331,7 @@ def test_html_anchor_for_figure(app):
@pytest.mark.sphinx('html', testroot='directives-raw')
def test_html_raw_directive(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'index.html').read_text()
+ result = (app.outdir / 'index.html').read_text(encoding='utf8')
# standard case
assert 'standalone raw directive (HTML)' in result
@@ -1374,7 +1375,7 @@ def test_alternate_stylesheets(app, cached_etree_parse, fname, expect):
@pytest.mark.sphinx('html', testroot='html_style')
def test_html_style(app, status, warning):
app.build()
- result = (app.outdir / 'index.html').read_text()
+ result = (app.outdir / 'index.html').read_text(encoding='utf8')
assert '' in result
assert (''
not in result)
@@ -1384,7 +1385,7 @@ def test_html_style(app, status, warning):
def test_html_remote_images(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'index.html').read_text()
+ result = (app.outdir / 'index.html').read_text(encoding='utf8')
assert ('
' in result)
assert not (app.outdir / 'python-logo.png').exists()
@@ -1394,7 +1395,7 @@ def test_html_remote_images(app, status, warning):
def test_html_remote_logo(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'index.html').read_text()
+ result = (app.outdir / 'index.html').read_text(encoding='utf8')
assert ('
' in result)
assert ('' in result)
assert not (app.outdir / 'python-logo.png').exists()
@@ -1404,7 +1405,7 @@ def test_html_remote_logo(app, status, warning):
def test_html_local_logo(app, status, warning):
app.builder.build_all()
- result = (app.outdir / 'index.html').read_text()
+ result = (app.outdir / 'index.html').read_text(encoding='utf8')
assert ('
' in result)
assert (app.outdir / '_static/img.png').exists()
@@ -1415,7 +1416,7 @@ def test_html_sidebar(app, status, warning):
# default for alabaster
app.builder.build_all()
- result = (app.outdir / 'index.html').read_text()
+ result = (app.outdir / 'index.html').read_text(encoding='utf8')
assert ('