diff --git a/doc/development/tutorials/examples/todo.py b/doc/development/tutorials/examples/todo.py index 59e394ee8..15368f440 100644 --- a/doc/development/tutorials/examples/todo.py +++ b/doc/development/tutorials/examples/todo.py @@ -70,7 +70,7 @@ def merge_todos(app, env, docnames, other): def process_todo_nodes(app, doctree, fromdocname): if not app.config.todo_include_todos: - for node in doctree.traverse(todo): + for node in doctree.findall(todo): node.parent.remove(node) # Replace all todolist nodes with a list of the collected todos. @@ -80,7 +80,7 @@ def process_todo_nodes(app, doctree, fromdocname): if not hasattr(env, 'todo_all_todos'): env.todo_all_todos = [] - for node in doctree.traverse(todolist): + for node in doctree.findall(todolist): if not app.config.todo_include_todos: node.replace_self([]) continue @@ -93,7 +93,7 @@ def process_todo_nodes(app, doctree, fromdocname): description = ( _('(The original entry is located in %s, line %d and can be found ') % (filename, todo_info['lineno'])) - para += nodes.Text(description, description) + para += nodes.Text(description) # Create a reference newnode = nodes.reference('', '') @@ -104,7 +104,7 @@ def process_todo_nodes(app, doctree, fromdocname): newnode['refuri'] += '#' + todo_info['target']['refid'] newnode.append(innernode) para += newnode - para += nodes.Text('.)', '.)') + para += nodes.Text('.)') # Insert into the todolist content.append(todo_info['todo']) diff --git a/setup.py b/setup.py index c935f65ce..e11a894c7 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages, setup import sphinx -with open('README.rst') as f: +with open('README.rst', encoding='utf-8') as f: long_desc = f.read() if sys.version_info < (3, 6): diff --git a/sphinx/__init__.py b/sphinx/__init__.py index 2adf7a69f..088b8f568 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -18,6 +18,8 @@ if 'PYTHONWARNINGS' not in os.environ: # docutils.io using mode='rU' for open warnings.filterwarnings('ignore', "'U' mode is deprecated", DeprecationWarning, module='docutils.io') +warnings.filterwarnings('ignore', 'The frontend.Option class .*', + DeprecationWarning, module='docutils.frontend') __version__ = '5.0.0+' __released__ = '5.0.0' # used when Sphinx builds its own docs diff --git a/sphinx/addnodes.py b/sphinx/addnodes.py index bf74bd1aa..0a4a079da 100644 --- a/sphinx/addnodes.py +++ b/sphinx/addnodes.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Sequence +import docutils from docutils import nodes from docutils.nodes import Element @@ -28,7 +29,6 @@ class document(nodes.document): def set_id(self, node: Element, msgnode: Element = None, suggested_prefix: str = '') -> str: - from sphinx.util import docutils if docutils.__version_info__ >= (0, 16): ret = super().set_id(node, msgnode, suggested_prefix) # type: ignore else: @@ -527,8 +527,6 @@ class manpage(nodes.Inline, nodes.FixedTextElement): def setup(app: "Sphinx") -> Dict[str, Any]: - from sphinx.util import docutils # lazy import - app.add_node(toctree) app.add_node(desc) diff --git a/sphinx/builders/_epub_base.py b/sphinx/builders/_epub_base.py index 26efdc1fb..4bb602fe0 100644 --- a/sphinx/builders/_epub_base.py +++ b/sphinx/builders/_epub_base.py @@ -483,7 +483,7 @@ class EpubBuilder(StandaloneHTMLBuilder): metadata['copyright'] = html.escape(self.config.epub_copyright) metadata['scheme'] = html.escape(self.config.epub_scheme) metadata['id'] = html.escape(self.config.epub_identifier) - metadata['date'] = html.escape(format_date("%Y-%m-%d")) + metadata['date'] = html.escape(format_date("%Y-%m-%d", language='en')) metadata['manifest_items'] = [] metadata['spines'] = [] metadata['guides'] = [] diff --git a/sphinx/builders/epub3.py b/sphinx/builders/epub3.py index 013955ed1..46c636c7d 100644 --- a/sphinx/builders/epub3.py +++ b/sphinx/builders/epub3.py @@ -88,7 +88,7 @@ class Epub3Builder(_epub_base.EpubBuilder): metadata['contributor'] = html.escape(self.config.epub_contributor) metadata['page_progression_direction'] = PAGE_PROGRESSION_DIRECTIONS.get(writing_mode) metadata['ibook_scroll_axis'] = IBOOK_SCROLL_AXIS.get(writing_mode) - metadata['date'] = html.escape(format_date("%Y-%m-%dT%H:%M:%SZ")) + metadata['date'] = html.escape(format_date("%Y-%m-%dT%H:%M:%SZ", language='en')) metadata['version'] = html.escape(self.config.version) metadata['epub_version'] = self.config.epub_version return metadata diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py index f713fb2b5..3f4ea569f 100644 --- a/sphinx/builders/html/__init__.py +++ b/sphinx/builders/html/__init__.py @@ -5,6 +5,7 @@ import os import posixpath import re import sys +import warnings from datetime import datetime from os import path from typing import IO, Any, Dict, Iterable, Iterator, List, Optional, Set, Tuple, Type @@ -370,7 +371,7 @@ class StandaloneHTMLBuilder(Builder): def get_outdated_docs(self) -> Iterator[str]: try: - with open(path.join(self.outdir, '.buildinfo')) as fp: + with open(path.join(self.outdir, '.buildinfo'), encoding="utf-8") as fp: buildinfo = BuildInfo.load(fp) if self.build_info != buildinfo: @@ -443,10 +444,14 @@ class StandaloneHTMLBuilder(Builder): self.load_indexer(docnames) self.docwriter = HTMLWriter(self) - self.docsettings: Any = OptionParser( - defaults=self.env.settings, - components=(self.docwriter,), - read_config_files=True).get_default_values() + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', category=DeprecationWarning) + # DeprecationWarning: The frontend.OptionParser class will be replaced + # by a subclass of argparse.ArgumentParser in Docutils 0.21 or later. + self.docsettings: Any = OptionParser( + defaults=self.env.settings, + components=(self.docwriter,), + read_config_files=True).get_default_values() self.docsettings.compact_lists = bool(self.config.html_compact_lists) # determine the additional indices to include @@ -763,11 +768,13 @@ class StandaloneHTMLBuilder(Builder): def create_pygments_style_file(self) -> None: """create a style file for pygments.""" - with open(path.join(self.outdir, '_static', 'pygments.css'), 'w') as f: + with open(path.join(self.outdir, '_static', 'pygments.css'), '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') as f: + with open(path.join(self.outdir, '_static', 'pygments_dark.css'), 'w', + encoding="utf-8") as f: f.write(self.dark_highlighter.get_stylesheet()) def copy_translation_js(self) -> None: @@ -853,7 +860,7 @@ class StandaloneHTMLBuilder(Builder): def write_buildinfo(self) -> None: try: - with open(path.join(self.outdir, '.buildinfo'), 'w') as fp: + with open(path.join(self.outdir, '.buildinfo'), 'w', encoding="utf-8") as fp: self.build_info.dump(fp) except OSError as exc: logger.warning(__('Failed to write build info file: %r'), exc) diff --git a/sphinx/builders/latex/__init__.py b/sphinx/builders/latex/__init__.py index f28a727b3..47aa59344 100644 --- a/sphinx/builders/latex/__init__.py +++ b/sphinx/builders/latex/__init__.py @@ -1,6 +1,7 @@ """LaTeX builder.""" import os +import warnings from os import path from typing import Any, Dict, Iterable, List, Tuple, Union @@ -241,7 +242,7 @@ class LaTeXBuilder(Builder): def write_stylesheet(self) -> None: highlighter = highlighting.PygmentsBridge('latex', self.config.pygments_style) stylesheet = path.join(self.outdir, 'sphinxhighlight.sty') - with open(stylesheet, 'w') as f: + with open(stylesheet, 'w', encoding="utf-8") as f: f.write('\\NeedsTeXFormat{LaTeX2e}[1995/12/01]\n') f.write('\\ProvidesPackage{sphinxhighlight}' '[2016/05/29 stylesheet for highlighting with pygments]\n') @@ -250,10 +251,14 @@ class LaTeXBuilder(Builder): def write(self, *ignored: Any) -> None: docwriter = LaTeXWriter(self) - docsettings: Any = OptionParser( - defaults=self.env.settings, - components=(docwriter,), - read_config_files=True).get_default_values() + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', category=DeprecationWarning) + # DeprecationWarning: The frontend.OptionParser class will be replaced + # by a subclass of argparse.ArgumentParser in Docutils 0.21 or later. + docsettings: Any = OptionParser( + defaults=self.env.settings, + components=(docwriter,), + read_config_files=True).get_default_values() self.init_document_data() self.write_stylesheet() @@ -347,9 +352,9 @@ class LaTeXBuilder(Builder): newnodes: List[Node] = [nodes.emphasis(sectname, sectname)] for subdir, title in self.titles: if docname.startswith(subdir): - newnodes.append(nodes.Text(_(' (in '), _(' (in '))) + newnodes.append(nodes.Text(_(' (in '))) newnodes.append(nodes.emphasis(title, title)) - newnodes.append(nodes.Text(')', ')')) + newnodes.append(nodes.Text(')')) break else: pass diff --git a/sphinx/builders/latex/theming.py b/sphinx/builders/latex/theming.py index 69f849381..6bcdc7f9d 100644 --- a/sphinx/builders/latex/theming.py +++ b/sphinx/builders/latex/theming.py @@ -73,7 +73,7 @@ class UserTheme(Theme): def __init__(self, name: str, filename: str) -> None: super().__init__(name) self.config = configparser.RawConfigParser() - self.config.read(path.join(filename)) + self.config.read(path.join(filename), encoding='utf-8') for key in self.REQUIRED_CONFIG_KEYS: try: diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index 816cf314e..7755c5f8d 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -184,8 +184,10 @@ class CheckExternalLinksBuilder(DummyBuilder): checker = HyperlinkAvailabilityChecker(self.env, self.config) logger.info('') - with open(path.join(self.outdir, 'output.txt'), 'w') as self.txt_outfile,\ - open(path.join(self.outdir, 'output.json'), 'w') as self.json_outfile: + output_text = path.join(self.outdir, 'output.txt') + output_json = path.join(self.outdir, 'output.json') + with open(output_text, 'w', encoding="utf-8") as self.txt_outfile,\ + open(output_json, 'w', encoding="utf-8") as self.json_outfile: for result in checker.check(self.hyperlinks): self.process_result(result) diff --git a/sphinx/builders/manpage.py b/sphinx/builders/manpage.py index cc6fc1fbe..ad1138c2c 100644 --- a/sphinx/builders/manpage.py +++ b/sphinx/builders/manpage.py @@ -1,5 +1,6 @@ """Manual pages builder.""" +import warnings from os import path from typing import Any, Dict, List, Set, Tuple, Union @@ -45,10 +46,14 @@ class ManualPageBuilder(Builder): @progress_message(__('writing')) def write(self, *ignored: Any) -> None: docwriter = ManualPageWriter(self) - docsettings: Any = OptionParser( - defaults=self.env.settings, - components=(docwriter,), - read_config_files=True).get_default_values() + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', category=DeprecationWarning) + # DeprecationWarning: The frontend.OptionParser class will be replaced + # by a subclass of argparse.ArgumentParser in Docutils 0.21 or later. + docsettings: Any = OptionParser( + defaults=self.env.settings, + components=(docwriter,), + read_config_files=True).get_default_values() for info in self.config.man_pages: docname, name, description, authors, section = info diff --git a/sphinx/builders/texinfo.py b/sphinx/builders/texinfo.py index 24a19b0df..33b866e56 100644 --- a/sphinx/builders/texinfo.py +++ b/sphinx/builders/texinfo.py @@ -1,6 +1,7 @@ """Texinfo builder.""" import os +import warnings from os import path from typing import Any, Dict, Iterable, List, Tuple, Union @@ -101,10 +102,14 @@ class TexinfoBuilder(Builder): with progress_message(__("writing")): self.post_process_images(doctree) docwriter = TexinfoWriter(self) - settings: Any = OptionParser( - defaults=self.env.settings, - components=(docwriter,), - read_config_files=True).get_default_values() + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', category=DeprecationWarning) + # DeprecationWarning: The frontend.OptionParser class will be replaced + # by a subclass of argparse.ArgumentParser in Docutils 0.21 or later. + settings: Any = OptionParser( + defaults=self.env.settings, + components=(docwriter,), + read_config_files=True).get_default_values() settings.author = author settings.title = title settings.texinfo_filename = targetname[:-5] + '.info' @@ -150,9 +155,9 @@ class TexinfoBuilder(Builder): newnodes: List[Node] = [nodes.emphasis(sectname, sectname)] for subdir, title in self.titles: if docname.startswith(subdir): - newnodes.append(nodes.Text(_(' (in '), _(' (in '))) + newnodes.append(nodes.Text(_(' (in '))) newnodes.append(nodes.emphasis(title, title)) - newnodes.append(nodes.Text(')', ')')) + newnodes.append(nodes.Text(')')) break else: pass diff --git a/sphinx/cmd/build.py b/sphinx/cmd/build.py index 00a06762a..ce0b14c75 100644 --- a/sphinx/cmd/build.py +++ b/sphinx/cmd/build.py @@ -236,7 +236,7 @@ def build_main(argv: List[str] = sys.argv[1:]) -> int: try: warnfile = abspath(args.warnfile) ensuredir(path.dirname(warnfile)) - warnfp = open(args.warnfile, 'w') + warnfp = open(args.warnfile, 'w', encoding="utf-8") except Exception as exc: parser.error(__('cannot open warning file %r: %s') % ( args.warnfile, exc)) diff --git a/sphinx/cmd/quickstart.py b/sphinx/cmd/quickstart.py index e8b446d40..5e9c2b470 100644 --- a/sphinx/cmd/quickstart.py +++ b/sphinx/cmd/quickstart.py @@ -367,7 +367,7 @@ def generate(d: Dict, overwrite: bool = True, silent: bool = False, templatedir: conf_path = os.path.join(templatedir, 'conf.py_t') if templatedir else None if not conf_path or not path.isfile(conf_path): conf_path = os.path.join(package_dir, 'templates', 'quickstart', 'conf.py_t') - with open(conf_path) as f: + with open(conf_path, encoding="utf-8") as f: conf_text = f.read() write_file(path.join(srcdir, 'conf.py'), template.render_string(conf_text, d)) diff --git a/sphinx/config.py b/sphinx/config.py index dc9a2a9ae..5f92479d3 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -405,7 +405,7 @@ def correct_copyright_year(app: "Sphinx", config: Config) -> None: if getenv('SOURCE_DATE_EPOCH') is not None: for k in ('copyright', 'epub_copyright'): if k in config: - replace = r'\g<1>%s' % format_date('%Y') + replace = r'\g<1>%s' % format_date('%Y', language='en') config[k] = copyright_year_re.sub(replace, config[k]) diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py index 83cbb6707..083fa088a 100644 --- a/sphinx/directives/other.py +++ b/sphinx/directives/other.py @@ -172,7 +172,7 @@ class Author(SphinxDirective): text = _('Code author: ') else: text = _('Author: ') - emph += nodes.Text(text, text) + emph += nodes.Text(text) inodes, messages = self.state.inline_text(self.arguments[0], self.lineno) emph.extend(inodes) diff --git a/sphinx/domains/index.py b/sphinx/domains/index.py index 8c630c8f5..42ad3c760 100644 --- a/sphinx/domains/index.py +++ b/sphinx/domains/index.py @@ -102,7 +102,7 @@ class IndexRole(ReferenceRole): index = addnodes.index(entries=entries) target = nodes.target('', '', ids=[target_id]) - text = nodes.Text(title, title) + text = nodes.Text(title) self.set_source_info(index) return [index, target, text], [] diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index ce9aae3a8..d5c962dc8 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -416,7 +416,7 @@ def token_xrefs(text: str, productionGroup: str = '') -> List[Node]: for m in token_re.finditer(text): if m.start() > pos: txt = text[pos:m.start()] - retnodes.append(nodes.Text(txt, txt)) + retnodes.append(nodes.Text(txt)) token = m.group(1) if ':' in token: if token[0] == '~': @@ -437,7 +437,7 @@ def token_xrefs(text: str, productionGroup: str = '') -> List[Node]: retnodes.append(refnode) pos = m.end() if pos < len(text): - retnodes.append(nodes.Text(text[pos:], text[pos:])) + retnodes.append(nodes.Text(text[pos:])) return retnodes diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index b9667230d..036aa3666 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -10,6 +10,7 @@ from os import path from typing import (TYPE_CHECKING, Any, Callable, Dict, Generator, Iterator, List, Optional, Set, Tuple, Union) +import docutils from docutils import nodes from docutils.nodes import Node @@ -38,7 +39,7 @@ logger = logging.getLogger(__name__) default_settings: Dict[str, Any] = { 'auto_id_prefix': 'id', - 'embed_images': False, + 'image_loading': 'link', 'embed_stylesheet': False, 'cloak_email_addresses': True, 'pep_base_url': 'https://peps.python.org/', @@ -53,6 +54,8 @@ default_settings: Dict[str, Any] = { 'file_insertion_enabled': True, 'smartquotes_locales': [], } +if docutils.__version_info__[:2] <= (0, 17): + default_settings['embed_images'] = False # This is increased every time an environment attribute is added # or changed to properly invalidate pickle files. diff --git a/sphinx/ext/coverage.py b/sphinx/ext/coverage.py index 3ebd095c2..1be815861 100644 --- a/sphinx/ext/coverage.py +++ b/sphinx/ext/coverage.py @@ -90,7 +90,7 @@ class CoverageBuilder(Builder): c_objects = self.env.domaindata['c']['objects'] for filename in self.c_sourcefiles: undoc: Set[Tuple[str, str]] = set() - with open(filename) as f: + with open(filename, encoding="utf-8") as f: for line in f: for key, regex in self.c_regexes: match = regex.match(line) @@ -108,7 +108,7 @@ class CoverageBuilder(Builder): def write_c_coverage(self) -> None: output_file = path.join(self.outdir, 'c.txt') - with open(output_file, 'w') as op: + with open(output_file, 'w', encoding="utf-8") as op: if self.config.coverage_write_headline: write_header(op, 'Undocumented C API elements', '=') op.write('\n') @@ -227,7 +227,7 @@ class CoverageBuilder(Builder): def write_py_coverage(self) -> None: output_file = path.join(self.outdir, 'python.txt') failed = [] - with open(output_file, 'w') as op: + with open(output_file, 'w', encoding="utf-8") as op: if self.config.coverage_write_headline: write_header(op, 'Undocumented Python objects', '=') keys = sorted(self.py_undoc.keys()) diff --git a/sphinx/ext/githubpages.py b/sphinx/ext/githubpages.py index e250fb2f2..53e063a11 100644 --- a/sphinx/ext/githubpages.py +++ b/sphinx/ext/githubpages.py @@ -11,13 +11,14 @@ from sphinx.environment import BuildEnvironment def create_nojekyll_and_cname(app: Sphinx, env: BuildEnvironment) -> None: if app.builder.format == 'html': - open(os.path.join(app.builder.outdir, '.nojekyll'), 'wt').close() + open(os.path.join(app.builder.outdir, '.nojekyll'), 'wb').close() html_baseurl = app.config.html_baseurl if html_baseurl: domain = urllib.parse.urlparse(html_baseurl).hostname if domain and not domain.endswith(".github.io"): - with open(os.path.join(app.builder.outdir, 'CNAME'), 'wt') as f: + with open(os.path.join(app.builder.outdir, 'CNAME'), 'w', + encoding="utf-8") as f: # NOTE: don't write a trailing newline. The `CNAME` file that's # auto-generated by the Github UI doesn't have one. f.write(domain) diff --git a/sphinx/ext/imgmath.py b/sphinx/ext/imgmath.py index 2c908daab..e32a18851 100644 --- a/sphinx/ext/imgmath.py +++ b/sphinx/ext/imgmath.py @@ -56,7 +56,7 @@ depthsvgcomment_re = re.compile(r'') def read_svg_depth(filename: str) -> int: """Read the depth from comment at last line of SVG file """ - with open(filename) as f: + with open(filename, encoding="utf-8") as f: for line in f: # noqa: B007 pass # Only last line is checked @@ -69,7 +69,7 @@ def read_svg_depth(filename: str) -> int: def write_svg_depth(filename: str, depth: int) -> None: """Write the depth to SVG file as a comment at end of file """ - with open(filename, 'a') as f: + with open(filename, 'a', encoding="utf-8") as f: f.write('\n' % depth) diff --git a/sphinx/ext/intersphinx.py b/sphinx/ext/intersphinx.py index a347b02ff..a048331a0 100644 --- a/sphinx/ext/intersphinx.py +++ b/sphinx/ext/intersphinx.py @@ -572,7 +572,7 @@ class IntersphinxRoleResolver(ReferencesResolver): default_priority = ReferencesResolver.default_priority - 1 def run(self, **kwargs: Any) -> None: - for node in self.document.traverse(pending_xref): + for node in self.document.findall(pending_xref): if 'intersphinx' not in node: continue contnode = cast(nodes.TextElement, node[0].deepcopy()) diff --git a/sphinx/ext/todo.py b/sphinx/ext/todo.py index c0d034087..a5eab6069 100644 --- a/sphinx/ext/todo.py +++ b/sphinx/ext/todo.py @@ -159,7 +159,7 @@ class TodoListProcessor: suffix = description[description.find('>>') + 2:] para = nodes.paragraph(classes=['todo-source']) - para += nodes.Text(prefix, prefix) + para += nodes.Text(prefix) # Create a reference linktext = nodes.emphasis(_('original entry'), _('original entry')) @@ -172,7 +172,7 @@ class TodoListProcessor: pass para += reference - para += nodes.Text(suffix, suffix) + para += nodes.Text(suffix) return para diff --git a/sphinx/roles.py b/sphinx/roles.py index e2564269e..1c5216196 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -293,7 +293,7 @@ class EmphasizedLiteral(SphinxRole): if len(stack) == 3 and stack[1] == "{" and len(stack[2]) > 0: # emphasized word found if stack[0]: - result.append(nodes.Text(stack[0], stack[0])) + result.append(nodes.Text(stack[0])) result.append(nodes.emphasis(stack[2], stack[2])) stack = [''] else: @@ -310,7 +310,7 @@ class EmphasizedLiteral(SphinxRole): if ''.join(stack): # remaining is treated as Text text = ''.join(stack) - result.append(nodes.Text(text, text)) + result.append(nodes.Text(text)) return result diff --git a/sphinx/search/__init__.py b/sphinx/search/__init__.py index f8fc294d6..e4fdeec91 100644 --- a/sphinx/search/__init__.py +++ b/sphinx/search/__init__.py @@ -449,9 +449,9 @@ class IndexBuilder: """Returns JS code that will be inserted into language_data.js.""" if self.lang.js_stemmer_rawcode: js_dir = path.join(package_dir, 'search', 'minified-js') - with open(path.join(js_dir, 'base-stemmer.js')) as js_file: + with open(path.join(js_dir, 'base-stemmer.js'), encoding='utf-8') as js_file: base_js = js_file.read() - with open(path.join(js_dir, self.lang.js_stemmer_rawcode)) as js_file: + with open(path.join(js_dir, self.lang.js_stemmer_rawcode), encoding='utf-8') as js_file: language_js = js_file.read() return ('%s\n%s\nStemmer = %sStemmer;' % (base_js, language_js, self.lang.language_name)) 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 027417dd6..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,7 @@ 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): diff --git a/sphinx/util/docutils.py b/sphinx/util/docutils.py index f954c81d5..bb62723b9 100644 --- a/sphinx/util/docutils.py +++ b/sphinx/util/docutils.py @@ -17,8 +17,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 RemovedInSphinx60Warning, deprecated_alias from sphinx.errors import SphinxError from sphinx.locale import _, __ from sphinx.util import logging @@ -32,8 +32,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__, + }, + RemovedInSphinx60Warning, + { + '__version_info__': 'docutils.__version_info__', + }) additional_nodes: Set[Type[Element]] = set() @@ -312,7 +318,7 @@ class NullReporter(Reporter): def is_html5_writer_available() -> bool: - return __version_info__ > (0, 13, 0) + return docutils.__version_info__ > (0, 13, 0) @contextmanager @@ -338,6 +344,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 +549,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/smartypants.py b/sphinx/util/smartypants.py index bc4171631..f05d6da58 100644 --- a/sphinx/util/smartypants.py +++ b/sphinx/util/smartypants.py @@ -27,10 +27,10 @@ import re import warnings from typing import Generator, Iterable, Tuple +from docutils import __version_info__ as docutils_version 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) 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/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..571b6202c 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 ('https://www.python.org/static/img/python-logo.png' 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 ('