From cf7da1843c45a4c2df7a749f7886a2d2ba0ee92a Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 15 Apr 2024 19:25:40 +0200 Subject: Adding upstream version 7.2.6. Signed-off-by: Daniel Baumann --- sphinx/util/rst.py | 110 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 sphinx/util/rst.py (limited to 'sphinx/util/rst.py') diff --git a/sphinx/util/rst.py b/sphinx/util/rst.py new file mode 100644 index 0000000..1e8fd66 --- /dev/null +++ b/sphinx/util/rst.py @@ -0,0 +1,110 @@ +"""reST helper functions.""" + +from __future__ import annotations + +import re +from collections import defaultdict +from contextlib import contextmanager +from typing import TYPE_CHECKING +from unicodedata import east_asian_width + +from docutils.parsers.rst import roles +from docutils.parsers.rst.languages import en as english +from docutils.parsers.rst.states import Body +from docutils.utils import Reporter +from jinja2 import Environment, pass_environment + +from sphinx.locale import __ +from sphinx.util import docutils, logging + +if TYPE_CHECKING: + from collections.abc import Generator + + from docutils.statemachine import StringList + +logger = logging.getLogger(__name__) + +FIELD_NAME_RE = re.compile(Body.patterns['field_marker']) +symbols_re = re.compile(r'([!-\-/:-@\[-`{-~])') # symbols without dot(0x2e) +SECTIONING_CHARS = ['=', '-', '~'] + +# width of characters +WIDECHARS: dict[str, str] = defaultdict(lambda: "WF") # WF: Wide + Full-width +WIDECHARS["ja"] = "WFA" # In Japanese, Ambiguous characters also have double width + + +def escape(text: str) -> str: + text = symbols_re.sub(r'\\\1', text) + text = re.sub(r'^\.', r'\.', text) # escape a dot at top + return text + + +def textwidth(text: str, widechars: str = 'WF') -> int: + """Get width of text.""" + def charwidth(char: str, widechars: str) -> int: + if east_asian_width(char) in widechars: + return 2 + else: + return 1 + + return sum(charwidth(c, widechars) for c in text) + + +@pass_environment +def heading(env: Environment, text: str, level: int = 1) -> str: + """Create a heading for *level*.""" + assert level <= 3 + width = textwidth(text, WIDECHARS[env.language]) + sectioning_char = SECTIONING_CHARS[level - 1] + return f'{text}\n{sectioning_char * width}' + + +@contextmanager +def default_role(docname: str, name: str) -> Generator[None, None, None]: + if name: + dummy_reporter = Reporter('', 4, 4) + role_fn, _ = roles.role(name, english, 0, dummy_reporter) + if role_fn: # type: ignore[truthy-function] + docutils.register_role('', role_fn) # type: ignore[arg-type] + else: + logger.warning(__('default role %s not found'), name, location=docname) + + yield + + docutils.unregister_role('') + + +def prepend_prolog(content: StringList, prolog: str) -> None: + """Prepend a string to content body as prolog.""" + if prolog: + pos = 0 + for line in content: + if FIELD_NAME_RE.match(line): + pos += 1 + else: + break + + if pos > 0: + # insert a blank line after docinfo + content.insert(pos, '', '', 0) + pos += 1 + + # insert prolog (after docinfo if exists) + lineno = 0 + for lineno, line in enumerate(prolog.splitlines()): + content.insert(pos + lineno, line, '', lineno) + + content.insert(pos + lineno + 1, '', '', 0) + + +def append_epilog(content: StringList, epilog: str) -> None: + """Append a string to content body as epilog.""" + if epilog: + if len(content) > 0: + source, lineno = content.info(-1) + else: + source = '' + lineno = 0 + content.append('', source, lineno + 1) + for lineno, line in enumerate(epilog.splitlines()): + content.append(line, '', lineno) -- cgit v1.2.3