diff options
Diffstat (limited to 'sphinx/writers')
-rw-r--r-- | sphinx/writers/html.py | 2 | ||||
-rw-r--r-- | sphinx/writers/html5.py | 30 | ||||
-rw-r--r-- | sphinx/writers/latex.py | 24 | ||||
-rw-r--r-- | sphinx/writers/manpage.py | 20 | ||||
-rw-r--r-- | sphinx/writers/texinfo.py | 23 | ||||
-rw-r--r-- | sphinx/writers/text.py | 36 | ||||
-rw-r--r-- | sphinx/writers/xml.py | 2 |
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 |