summaryrefslogtreecommitdiffstats
path: root/sphinx/writers
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/writers')
-rw-r--r--sphinx/writers/html.py2
-rw-r--r--sphinx/writers/html5.py30
-rw-r--r--sphinx/writers/latex.py24
-rw-r--r--sphinx/writers/manpage.py20
-rw-r--r--sphinx/writers/texinfo.py23
-rw-r--r--sphinx/writers/text.py36
-rw-r--r--sphinx/writers/xml.py2
7 files changed, 70 insertions, 67 deletions
diff --git a/sphinx/writers/html.py b/sphinx/writers/html.py
index 99d47c9..06551d6 100644
--- a/sphinx/writers/html.py
+++ b/sphinx/writers/html.py
@@ -17,7 +17,7 @@ logger = logging.getLogger(__name__)
HTMLTranslator = HTML5Translator
# A good overview of the purpose behind these classes can be found here:
-# http://www.arnebrodowski.de/blog/write-your-own-restructuredtext-writer.html
+# https://www.arnebrodowski.de/blog/write-your-own-restructuredtext-writer.html
class HTMLWriter(Writer):
diff --git a/sphinx/writers/html5.py b/sphinx/writers/html5.py
index eb0a1ee..90eedd8 100644
--- a/sphinx/writers/html5.py
+++ b/sphinx/writers/html5.py
@@ -28,7 +28,7 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
# A good overview of the purpose behind these classes can be found here:
-# http://www.arnebrodowski.de/blog/write-your-own-restructuredtext-writer.html
+# https://www.arnebrodowski.de/blog/write-your-own-restructuredtext-writer.html
def multiply_length(length: str, scale: int) -> str:
@@ -247,8 +247,8 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
self.depart_desc_parameter(node)
def visit_desc_optional(self, node: Element) -> None:
- self.params_left_at_level = sum([isinstance(c, addnodes.desc_parameter)
- for c in node.children])
+ self.params_left_at_level = sum(isinstance(c, addnodes.desc_parameter)
+ for c in node.children)
self.optional_param_level += 1
self.max_optional_param_level = self.optional_param_level
if self.multi_line_parameter_list:
@@ -338,7 +338,7 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
self.depart_reference(node)
# overwritten -- we don't want source comments to show up in the HTML
- def visit_comment(self, node: Element) -> None: # type: ignore[override]
+ def visit_comment(self, node: Element) -> None:
raise nodes.SkipNode
# overwritten
@@ -475,6 +475,17 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
self.add_fignumber(node.parent)
if isinstance(node.parent, nodes.table):
self.body.append('<span class="caption-text">')
+ # Partially revert https://sourceforge.net/p/docutils/code/9562/
+ if (
+ isinstance(node.parent, nodes.topic)
+ and self.settings.toc_backlinks
+ and 'contents' in node.parent['classes']
+ and self.body[-1].startswith('<a ')
+ # TODO: only remove for EPUB
+ ):
+ # remove <a class="reference internal" href="#top">
+ self.body.pop()
+ self.context[-1] = '</p>\n'
def depart_title(self, node: Element) -> None:
close_tag = self.context[-1]
@@ -589,10 +600,8 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
def visit_productionlist(self, node: Element) -> None:
self.body.append(self.starttag(node, 'pre'))
- names = []
productionlist = cast(Iterable[addnodes.production], node)
- for production in productionlist:
- names.append(production['tokenname'])
+ names = (production['tokenname'] for production in productionlist)
maxlen = max(len(name) for name in names)
lastname = None
for production in productionlist:
@@ -846,13 +855,8 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
def visit_manpage(self, node: Element) -> None:
self.visit_literal_emphasis(node)
- if self.manpages_url:
- node['refuri'] = self.manpages_url.format(**node.attributes)
- self.visit_reference(node)
def depart_manpage(self, node: Element) -> None:
- if self.manpages_url:
- self.depart_reference(node)
self.depart_literal_emphasis(node)
# overwritten to add even/odd classes
@@ -928,7 +932,7 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
# See Docutils r9413
# Re-instate the footnote-reference class
- def visit_footnote_reference(self, node):
+ def visit_footnote_reference(self, node: Element) -> None:
href = '#' + node['refid']
classes = ['footnote-reference', self.settings.footnote_references]
self.body.append(self.starttag(node, 'a', suffix='', classes=classes,
diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py
index 89b349a..606225a 100644
--- a/sphinx/writers/latex.py
+++ b/sphinx/writers/latex.py
@@ -29,7 +29,7 @@ try:
from docutils.utils.roman import toRoman
except ImportError:
# In Debian/Ubuntu, roman package is provided as roman, not as docutils.utils.roman
- from roman import toRoman # type: ignore[no-redef]
+ from roman import toRoman # type: ignore[no-redef, import-not-found]
if TYPE_CHECKING:
from docutils.nodes import Element, Node, Text
@@ -158,7 +158,7 @@ class Table:
return 'tabulary'
def get_colspec(self) -> str:
- """Returns a column spec of table.
+ r"""Returns a column spec of table.
This is what LaTeX calls the 'preamble argument' of the used table environment.
@@ -321,7 +321,7 @@ class LaTeXTranslator(SphinxTranslator):
self.elements = self.builder.context.copy()
# initial section names
- self.sectionnames = LATEXSECTIONNAMES[:]
+ self.sectionnames = LATEXSECTIONNAMES.copy()
if self.theme.toplevel_sectioning == 'section':
self.sectionnames.remove('chapter')
@@ -911,8 +911,8 @@ class LaTeXTranslator(SphinxTranslator):
self._depart_sig_parameter(node)
def visit_desc_optional(self, node: Element) -> None:
- self.params_left_at_level = sum([isinstance(c, addnodes.desc_parameter)
- for c in node.children])
+ self.params_left_at_level = sum(isinstance(c, addnodes.desc_parameter)
+ for c in node.children)
self.optional_param_level += 1
self.max_optional_param_level = self.optional_param_level
if self.multi_line_parameter_list:
@@ -2121,14 +2121,14 @@ class LaTeXTranslator(SphinxTranslator):
self.body.append('}}$')
def visit_inline(self, node: Element) -> None:
- classes = node.get('classes', [])
- if classes in [['menuselection']]:
+ classes = node.get('classes', []) # type: ignore[var-annotated]
+ if classes == ['menuselection']:
self.body.append(r'\sphinxmenuselection{')
self.context.append('}')
- elif classes in [['guilabel']]:
+ elif classes == ['guilabel']:
self.body.append(r'\sphinxguilabel{')
self.context.append('}')
- elif classes in [['accelerator']]:
+ elif classes == ['accelerator']:
self.body.append(r'\sphinxaccelerator{')
self.context.append('}')
elif classes and not self.in_title:
@@ -2153,12 +2153,12 @@ class LaTeXTranslator(SphinxTranslator):
pass
def visit_container(self, node: Element) -> None:
- classes = node.get('classes', [])
+ classes = node.get('classes', []) # type: ignore[var-annotated]
for c in classes:
self.body.append('\n\\begin{sphinxuseclass}{%s}' % c)
def depart_container(self, node: Element) -> None:
- classes = node.get('classes', [])
+ classes = node.get('classes', []) # type: ignore[var-annotated]
for _c in classes:
self.body.append('\n\\end{sphinxuseclass}')
@@ -2261,6 +2261,6 @@ class LaTeXTranslator(SphinxTranslator):
# FIXME: Workaround to avoid circular import
# refs: https://github.com/sphinx-doc/sphinx/issues/5433
-from sphinx.builders.latex.nodes import ( # noqa: E402 # isort:skip
+from sphinx.builders.latex.nodes import ( # NoQA: E402 # isort:skip
HYPERLINK_SUPPORT_NODES, captioned_literal_block, footnotetext,
)
diff --git a/sphinx/writers/manpage.py b/sphinx/writers/manpage.py
index 108eb5e..d2d6dc5 100644
--- a/sphinx/writers/manpage.py
+++ b/sphinx/writers/manpage.py
@@ -49,12 +49,13 @@ class NestedInlineTransform:
<strong>foo=</strong><emphasis>var</emphasis>
<strong>&bar=</strong><emphasis>2</emphasis>
"""
+
def __init__(self, document: nodes.document) -> None:
self.document = document
def apply(self, **kwargs: Any) -> None:
matcher = NodeMatcher(nodes.literal, nodes.emphasis, nodes.strong)
- for node in list(self.document.findall(matcher)): # type: nodes.TextElement
+ for node in list(matcher.findall(self.document)):
if any(matcher(subnode) for subnode in node):
pos = node.parent.index(node)
for subnode in reversed(list(node)):
@@ -244,7 +245,7 @@ class ManualPageTranslator(SphinxTranslator, BaseTranslator):
super().visit_term(node)
# overwritten -- we don't want source comments to show up
- def visit_comment(self, node: Element) -> None: # type: ignore[override]
+ def visit_comment(self, node: Element) -> None:
raise nodes.SkipNode
# overwritten -- added ensure_eol()
@@ -271,12 +272,10 @@ class ManualPageTranslator(SphinxTranslator, BaseTranslator):
def visit_productionlist(self, node: Element) -> None:
self.ensure_eol()
- names = []
self.in_productionlist += 1
self.body.append('.sp\n.nf\n')
productionlist = cast(Iterable[addnodes.production], node)
- for production in productionlist:
- names.append(production['tokenname'])
+ names = (production['tokenname'] for production in productionlist)
maxlen = max(len(name) for name in names)
lastname = None
for production in productionlist:
@@ -309,13 +308,17 @@ class ManualPageTranslator(SphinxTranslator, BaseTranslator):
# overwritten -- don't visit inner marked up nodes
def visit_reference(self, node: Element) -> None:
+ uri = node.get('refuri', '')
+ if uri:
+ # OSC 8 link start (using groff's device control directive).
+ self.body.append(fr"\X'tty: link {uri}'")
+
self.body.append(self.defs['reference'][0])
# avoid repeating escaping code... fine since
# visit_Text calls astext() and only works on that afterwards
- self.visit_Text(node) # type: ignore[arg-type]
+ self.visit_Text(node)
self.body.append(self.defs['reference'][1])
- uri = node.get('refuri', '')
if uri.startswith(('mailto:', 'http:', 'https:', 'ftp:')):
# if configured, put the URL after the link
if self.config.man_show_urls and node.astext() != uri:
@@ -325,6 +328,9 @@ class ManualPageTranslator(SphinxTranslator, BaseTranslator):
' <',
self.defs['strong'][0], uri, self.defs['strong'][1],
'>'])
+ if uri:
+ # OSC 8 link end.
+ self.body.append(r"\X'tty: link'")
raise nodes.SkipNode
def visit_number_reference(self, node: Element) -> None:
diff --git a/sphinx/writers/texinfo.py b/sphinx/writers/texinfo.py
index 7032c65..6e0a7de 100644
--- a/sphinx/writers/texinfo.py
+++ b/sphinx/writers/texinfo.py
@@ -96,7 +96,8 @@ def find_subsections(section: Element) -> list[nodes.section]:
def smart_capwords(s: str, sep: str | None = None) -> str:
"""Like string.capwords() but does not capitalize words that already
- contain a capital letter."""
+ contain a capital letter.
+ """
words = s.split(sep)
for i, word in enumerate(words):
if all(x.islower() for x in word):
@@ -106,6 +107,7 @@ def smart_capwords(s: str, sep: str | None = None) -> str:
class TexinfoWriter(writers.Writer):
"""Texinfo writer for generating Texinfo documents."""
+
supported = ('texinfo', 'texi')
settings_spec: tuple[str, Any, tuple[tuple[str, list[str], dict[str, str]], ...]] = (
@@ -255,7 +257,8 @@ class TexinfoTranslator(SphinxTranslator):
def collect_node_names(self) -> None:
"""Generates a unique id for each section.
- Assigns the attribute ``node_name`` to each section."""
+ Assigns the attribute ``node_name`` to each section.
+ """
def add_node_name(name: str) -> str:
node_id = self.escape_id(name)
@@ -278,7 +281,7 @@ class TexinfoTranslator(SphinxTranslator):
for name, content in self.indices]
# each section is also a node
for section in self.document.findall(nodes.section):
- title = cast(nodes.TextElement, section.next_node(nodes.Titular))
+ title = cast(nodes.TextElement, section.next_node(nodes.Titular)) # type: ignore[type-var]
name = title.astext() if title else '<untitled>'
section['node_name'] = add_node_name(name)
@@ -288,7 +291,7 @@ class TexinfoTranslator(SphinxTranslator):
targets: list[Element] = [self.document]
targets.extend(self.document.findall(nodes.section))
for node in targets:
- assert 'node_name' in node and node['node_name'] # NoQA: PT018
+ assert node.get('node_name', False)
entries = [s['node_name'] for s in find_subsections(node)]
node_menus[node['node_name']] = entries
# try to find a suitable "Top" node
@@ -352,7 +355,8 @@ class TexinfoTranslator(SphinxTranslator):
def escape_arg(self, s: str) -> str:
"""Return an escaped string suitable for use as an argument
- to a Texinfo command."""
+ to a Texinfo command.
+ """
s = self.escape(s)
# commas are the argument delimiters
s = s.replace(',', '@comma{}')
@@ -430,7 +434,7 @@ class TexinfoTranslator(SphinxTranslator):
entries = self.node_menus[name]
if not entries:
return
- self.body.append(f'\n{self.escape(self.node_names[name], )}\n\n')
+ self.body.append(f'\n{self.escape(self.node_names[name])}\n\n')
self.add_menu_entries(entries)
for subentry in entries:
_add_detailed_menu(subentry)
@@ -524,7 +528,7 @@ class TexinfoTranslator(SphinxTranslator):
try:
sid = self.short_ids[id]
except KeyError:
- sid = hex(len(self.short_ids))[2:]
+ sid = f'{len(self.short_ids):x}'
self.short_ids[id] = sid
return sid
@@ -1287,11 +1291,10 @@ class TexinfoTranslator(SphinxTranslator):
def visit_productionlist(self, node: Element) -> None:
self.visit_literal_block(None)
- names = []
productionlist = cast(Iterable[addnodes.production], node)
- for production in productionlist:
- names.append(production['tokenname'])
+ names = (production['tokenname'] for production in productionlist)
maxlen = max(len(name) for name in names)
+
for production in productionlist:
if production['tokenname']:
for id in production.get('ids'):
diff --git a/sphinx/writers/text.py b/sphinx/writers/text.py
index 63df2d7..d2dd208 100644
--- a/sphinx/writers/text.py
+++ b/sphinx/writers/text.py
@@ -5,7 +5,7 @@ import math
import os
import re
import textwrap
-from collections.abc import Generator, Iterable, Sequence
+from collections.abc import Iterable, Iterator, Sequence
from itertools import chain, groupby
from typing import TYPE_CHECKING, Any, cast
@@ -26,6 +26,7 @@ class Cell:
"""Represents a cell in a table.
It can span multiple columns or multiple lines.
"""
+
def __init__(self, text: str = "", rowspan: int = 1, colspan: int = 1) -> None:
self.text = text
self.wrapped: list[str] = []
@@ -93,6 +94,7 @@ class Table:
+--------+--------+
"""
+
def __init__(self, colwidth: list[int] | None = None) -> None:
self.lines: list[list[Cell]] = []
self.separator = 0
@@ -148,7 +150,7 @@ class Table:
line.append(Cell())
def __repr__(self) -> str:
- return "\n".join(repr(line) for line in self.lines)
+ return "\n".join(map(repr, self.lines))
def cell_width(self, cell: Cell, source: list[int]) -> int:
"""Give the cell width, according to the given source (either
@@ -164,7 +166,7 @@ class Table:
return width + (cell.colspan - 1) * 3
@property
- def cells(self) -> Generator[Cell, None, None]:
+ def cells(self) -> Iterator[Cell]:
seen: set[Cell] = set()
for line in self.lines:
for cell in line:
@@ -262,9 +264,8 @@ class TextWrapper(textwrap.TextWrapper):
r'(?<=[\w\!\"\'\&\.\,\?])-{2,}(?=\w))') # em-dash
def _wrap_chunks(self, chunks: list[str]) -> list[str]:
- """_wrap_chunks(chunks : [string]) -> [string]
+ """The original _wrap_chunks uses len() to calculate width.
- The original _wrap_chunks uses len() to calculate width.
This method respects wide/fullwidth characters for width adjustment.
"""
lines: list[str] = []
@@ -309,10 +310,7 @@ class TextWrapper(textwrap.TextWrapper):
return lines
def _break_word(self, word: str, space_left: int) -> tuple[str, str]:
- """_break_word(word : string, space_left : int) -> (string, string)
-
- Break line by unicode width instead of len(word).
- """
+ """Break line by unicode width instead of len(word)."""
total = 0
for i, c in enumerate(word):
total += column_width(c)
@@ -321,9 +319,8 @@ class TextWrapper(textwrap.TextWrapper):
return word, ''
def _split(self, text: str) -> list[str]:
- """_split(text : string) -> [string]
+ """Override original method that only split by 'wordsep_re'.
- Override original method that only split by 'wordsep_re'.
This '_split' splits wide-characters into chunks by one character.
"""
def split(t: str) -> list[str]:
@@ -339,12 +336,7 @@ class TextWrapper(textwrap.TextWrapper):
def _handle_long_word(self, reversed_chunks: list[str], cur_line: list[str],
cur_len: int, width: int) -> None:
- """_handle_long_word(chunks : [string],
- cur_line : [string],
- cur_len : int, width : int)
-
- Override original method for using self._break_word() instead of slice.
- """
+ """Override original method for using self._break_word() instead of slice."""
space_left = max(width - cur_len, 1)
if self.break_long_words:
l, r = self._break_word(reversed_chunks[-1], space_left)
@@ -693,8 +685,8 @@ class TextTranslator(SphinxTranslator):
self.visit_desc_parameter(node)
def visit_desc_optional(self, node: Element) -> None:
- self.params_left_at_level = sum([isinstance(c, addnodes.desc_parameter)
- for c in node.children])
+ self.params_left_at_level = sum(isinstance(c, addnodes.desc_parameter)
+ for c in node.children)
self.optional_param_level += 1
self.max_optional_param_level = self.optional_param_level
if self.multi_line_parameter_list:
@@ -757,10 +749,8 @@ class TextTranslator(SphinxTranslator):
def visit_productionlist(self, node: Element) -> None:
self.new_state()
- names = []
productionlist = cast(Iterable[addnodes.production], node)
- for production in productionlist:
- names.append(production['tokenname'])
+ names = (production['tokenname'] for production in productionlist)
maxlen = max(len(name) for name in names)
lastname = None
for production in productionlist:
@@ -1289,7 +1279,7 @@ class TextTranslator(SphinxTranslator):
if 'text' in node.get('format', '').split():
self.new_state(0)
self.add_text(node.astext())
- self.end_state(wrap = False)
+ self.end_state(wrap=False)
raise nodes.SkipNode
def visit_math(self, node: Element) -> None:
diff --git a/sphinx/writers/xml.py b/sphinx/writers/xml.py
index 38aa763..a4d1312 100644
--- a/sphinx/writers/xml.py
+++ b/sphinx/writers/xml.py
@@ -48,5 +48,5 @@ class PseudoXMLWriter(BaseXMLWriter):
self.output = self.document.pformat()
def supports(self, format: str) -> bool:
- """This writer supports all format-specific elements."""
+ """All format-specific elements are supported."""
return True