SF.net SVN: docutils:[9876 ] trunk/docutils/docutils/u tils/__init__.py

aa-turner--- via Docutils-checkins <[email protected]>
Newsgroups gmane.text.docutils.cvs
Message-ID <[email protected]>
Revision: 9876
          http://sourceforge.net/p/docutils/code/9876
Author:   aa-turner
Date:     2024-08-09 07:45:39 +0000 (Fri, 09 Aug 2024)
Log Message:
-----------
Add type hints to ``docutils.utils``

Modified Paths:
--------------
    trunk/docutils/docutils/utils/__init__.py

Modified: trunk/docutils/docutils/utils/__init__.py
===================================================================
--- trunk/docutils/docutils/utils/__init__.py	2024-08-09 06:38:17 UTC (rev 9875)
+++ trunk/docutils/docutils/utils/__init__.py	2024-08-09 07:45:39 UTC (rev 9876)
@@ -6,16 +6,19 @@
 Miscellaneous utilities for the documentation utilities.
 """
 
+from __future__ import annotations
+
 __docformat__ = 'reStructuredText'
 
-import sys
+import itertools
 import os
 import os.path
-from pathlib import PurePath, Path
 import re
-import itertools
+import sys
+import unicodedata
 import warnings
-import unicodedata
+from pathlib import PurePath, Path
+from typing import TYPE_CHECKING
 
 from docutils import ApplicationError, DataError, __version_info__
 from docutils import io, nodes
@@ -22,10 +25,25 @@
 # for backwards compatibility
 from docutils.nodes import unescape  # noqa: F401
 
+if TYPE_CHECKING:
+    from collections.abc import Callable, Sequence, Iterable
+    from typing import Any, Final, Literal, TextIO
 
+    from typing_extensions import TypeAlias
+
+    from docutils import VersionInfo
+    from docutils.nodes import Element, Text
+    from docutils.frontend import Values
+
+    _StrPath: TypeAlias = str | os.PathLike[str]
+    _ObserverFunc: TypeAlias = Callable[[nodes.system_message], None]
+
+
 class SystemMessage(ApplicationError):
 
-    def __init__(self, system_message, level) -> None:
+    def __init__(
+        self, system_message: nodes.system_message, level: int,
+    ) -> None:
         Exception.__init__(self, system_message.astext())
         self.level = level
 
@@ -65,25 +83,36 @@
        1995.
     """
 
-    levels = 'DEBUG INFO WARNING ERROR SEVERE'.split()
+    # Reporter.get_source_and_line is patched in by ``RSTState.runtime_init``
+    get_source_and_line: Callable[
+        [int | None], tuple[_StrPath | None, int | None]
+    ]
+
+    levels: Final[Sequence[str]] = (
+        'DEBUG',
+        'INFO',
+        'WARNING',
+        'ERROR',
+        'SEVERE',
+    )
     """List of names for system message levels, indexed by level."""
 
     # system message level constants:
-    (DEBUG_LEVEL,
-     INFO_LEVEL,
-     WARNING_LEVEL,
-     ERROR_LEVEL,
-     SEVERE_LEVEL) = range(5)
+    DEBUG_LEVEL: Final = 0
+    INFO_LEVEL: Final = 1
+    WARNING_LEVEL: Final = 2
+    ERROR_LEVEL: Final = 3
+    SEVERE_LEVEL: Final = 4
 
     def __init__(
         self,
-        source,
-        report_level,
-        halt_level,
-        stream=None,
-        debug=False,
-        encoding=None,
-        error_handler='backslashreplace',
+        source: _StrPath,
+        report_level: int,
+        halt_level: int,
+        stream: io.ErrorOutput | TextIO | str | Literal[False] | None = None,
+        debug: bool = False,
+        encoding: str | None = None,
+        error_handler: str = 'backslashreplace',
     ) -> None:
         """
         :Parameters:
@@ -121,20 +150,20 @@
         if not isinstance(stream, io.ErrorOutput):
             stream = io.ErrorOutput(stream, encoding, error_handler)
 
-        self.stream = stream
+        self.stream: io.ErrorOutput = stream
         """Where warning output is sent."""
 
-        self.encoding = encoding or getattr(stream, 'encoding', 'ascii')
+        self.encoding: str = encoding or getattr(stream, 'encoding', 'ascii')
         """The output character encoding."""
 
-        self.observers = []
+        self.observers: list[_ObserverFunc] = []
         """List of bound methods or functions to call with each system_message
         created."""
 
-        self.max_level = -1
+        self.max_level: int = -1
         """The highest level system message generated so far."""
 
-    def attach_observer(self, observer) -> None:
+    def attach_observer(self, observer: _ObserverFunc) -> None:
         """
         The `observer` parameter is a function or bound method which takes one
         argument, a `nodes.system_message` instance.
@@ -141,14 +170,19 @@
         """
         self.observers.append(observer)
 
-    def detach_observer(self, observer) -> None:
+    def detach_observer(self, observer: _ObserverFunc) -> None:
         self.observers.remove(observer)
 
-    def notify_observers(self, message) -> None:
+    def notify_observers(self, message: nodes.system_message) -> None:
         for observer in self.observers:
             observer(message)
 
-    def system_message(self, level, message, *children, **kwargs):
+    def system_message(self,
+                       level: int,
+                       message: str,
+                       *children: Element | Text,
+                       **kwargs: Any
+                       ) -> nodes.system_message:
         """
         Return a system_message object.
 
@@ -195,7 +229,9 @@
         self.max_level = max(level, self.max_level)
         return msg
 
-    def debug(self, *args, **kwargs):
+    def debug(
+        self, *args: Element | Text, **kwargs: Any
+    ) -> nodes.system_message:
         """
         Level-0, "DEBUG": an internal reporting issue. Typically, there is no
         effect on the processing. Level-0 system messages are handled
@@ -204,7 +240,9 @@
         if self.debug_flag:
             return self.system_message(self.DEBUG_LEVEL, *args, **kwargs)
 
-    def info(self, *args, **kwargs):
+    def info(
+        self, *args: Element | Text, **kwargs: Any
+    ) -> nodes.system_message:
         """
         Level-1, "INFO": a minor issue that can be ignored. Typically there is
         no effect on processing, and level-1 system messages are not reported.
@@ -211,7 +249,9 @@
         """
         return self.system_message(self.INFO_LEVEL, *args, **kwargs)
 
-    def warning(self, *args, **kwargs):
+    def warning(
+        self, *args: Element | Text, **kwargs: Any
+    ) -> nodes.system_message:
         """
         Level-2, "WARNING": an issue that should be addressed. If ignored,
         there may be unpredictable problems with the output.
@@ -218,7 +258,9 @@
         """
         return self.system_message(self.WARNING_LEVEL, *args, **kwargs)
 
-    def error(self, *args, **kwargs):
+    def error(
+        self, *args: Element | Text, **kwargs: Any
+    ) -> nodes.system_message:
         """
         Level-3, "ERROR": an error that should be addressed. If ignored, the
         output will contain errors.
@@ -225,7 +267,9 @@
         """
         return self.system_message(self.ERROR_LEVEL, *args, **kwargs)
 
-    def severe(self, *args, **kwargs):
+    def severe(
+        self, *args: Element | Text, **kwargs: Any
+    ) -> nodes.system_message:
         """
         Level-4, "SEVERE": a severe error that must be addressed. If ignored,
         the output will contain severe errors. Typically level-4 system
@@ -234,13 +278,15 @@
         return self.system_message(self.SEVERE_LEVEL, *args, **kwargs)
 
 
-class ExtensionOptionError(DataError): pass
-class BadOptionError(ExtensionOptionError): pass
-class BadOptionDataError(ExtensionOptionError): pass
-class DuplicateOptionError(ExtensionOptionError): pass
+class ExtensionOptionError(DataError): pass  # NoQA: E701
+class BadOptionError(ExtensionOptionError): pass  # NoQA: E701
+class BadOptionDataError(ExtensionOptionError): pass  # NoQA: E701
+class DuplicateOptionError(ExtensionOptionError): pass  # NoQA: E701
 
 
-def extract_extension_options(field_list, options_spec):
+def extract_extension_options(field_list: nodes.field_list,
+                              options_spec: dict[str, Callable[object], Any],
+                              ) -> dict[str, Any]:
     """
     Return a dictionary mapping extension option names to converted values.
 
@@ -265,7 +311,9 @@
     return assemble_option_dict(option_list, options_spec)
 
 
-def extract_options(field_list):
+def extract_options(
+    field_list: nodes.field_list
+) -> list[tuple[str, str | None]]:
     """
     Return a list of option (name, value) pairs from field names & bodies.
 
@@ -300,7 +348,9 @@
     return option_list
 
 
-def assemble_option_dict(option_list, options_spec):
+def assemble_option_dict(option_list: list[tuple[str, str | None]],
+                         options_spec: dict[str, Callable[object], Any],
+                         ) -> dict[str, Any]:
     """
     Return a mapping of option names to values.
 
@@ -336,7 +386,7 @@
 class NameValueError(DataError): pass
 
 
-def decode_path(path):
+def decode_path(path: str | None | bytes) -> str:
     """
     Ensure `path` is Unicode. Return `str` instance.
 
@@ -345,11 +395,11 @@
     # TODO: is this still required with Python 3?
     if isinstance(path, str):
         return path
+    if path is None:
+        return ''
     try:
         path = path.decode(sys.getfilesystemencoding(), 'strict')
     except AttributeError:  # default value None has no decode method
-        if not path:
-            return ''
         raise ValueError('`path` value must be a String or ``None``, '
                          f'not {path!r}')
     except UnicodeDecodeError:
@@ -405,7 +455,7 @@
     return attlist
 
 
-def new_reporter(source_path, settings):
+def new_reporter(source_path: _StrPath, settings: Values) -> Reporter:
     """
     Return a new Reporter object.
 
@@ -423,7 +473,9 @@
     return reporter
 
 
-def new_document(source_path, settings=None):
+def new_document(source_path: _StrPath,
+                 settings: Values | None = None
+                 ) -> nodes.document:
     """
     Return a new empty document object.
 
@@ -460,7 +512,10 @@
     return document
 
 
-def clean_rcs_keywords(paragraph, keyword_substitutions) -> None:
+def clean_rcs_keywords(
+    paragraph: nodes.paragraph,
+    keyword_substitutions: Sequence[tuple[re.Pattern[[str], str]]],
+) -> None:
     if len(paragraph) == 1 and isinstance(paragraph[0], nodes.Text):
         textnode = paragraph[0]
         for pattern, substitution in keyword_substitutions:
@@ -470,7 +525,7 @@
                 return
 
 
-def relative_path(source, target):
+def relative_path(source: _StrPath | None, target: _StrPath) -> str:
     """
     Build and return a path to `target`, relative to `source` (both files).
 
@@ -525,7 +580,9 @@
     return '/'.join(parts)
 
 
-def get_stylesheet_reference(settings, relative_to=None):
+def get_stylesheet_reference(settings: Values,
+                             relative_to: _StrPath | None = None
+                             ) -> str:
     """
     Retrieve a stylesheet reference from the settings object.
 
@@ -557,7 +614,7 @@
 # * no re-writing of the path (and therefore no optional argument)
 #   (if required, use ``utils.relative_path(source, target)``
 #   in the calling script)
-def get_stylesheet_list(settings):
+def get_stylesheet_list(settings: Values) -> list[str]:
     """
     Retrieve list of stylesheet references from the settings object.
     """
@@ -574,7 +631,7 @@
     return stylesheets
 
 
-def find_file_in_dirs(path, dirs):
+def find_file_in_dirs(path: _StrPath, dirs: Iterable[_StrPath]) -> str:
     """
     Search for `path` in the list of directories `dirs`.
 
@@ -590,7 +647,7 @@
     return path.as_posix()
 
 
-def get_trim_footnote_ref_space(settings):
+def get_trim_footnote_ref_space(settings: Values) -> bool:
     """
     Return whether or not to trim footnote space.
 
@@ -605,7 +662,7 @@
         return settings.trim_footnote_reference_space
 
 
-def get_source_line(node):
+def get_source_line(node: Element) -> tuple[_StrPath | None, int | None]:
     """
     Return the "source" and "line" attributes from the `node` given or from
     its closest ancestor.
@@ -617,7 +674,7 @@
     return None, None
 
 
-def escape2null(text):
+def escape2null(text: str) -> str:
     """Return a string with escape-backslashes converted to nulls."""
     parts = []
     start = 0
@@ -633,7 +690,7 @@
         start = found + 2               # skip character after escape
 
 
-def split_escaped_whitespace(text):
+def split_escaped_whitespace(text: str) -> list[str]:
     """
     Split `text` on escaped whitespace (null+space or null+newline).
     Return a list of strings.
@@ -644,11 +701,11 @@
     return list(itertools.chain(*strings))
 
 
-def strip_combining_chars(text):
+def strip_combining_chars(text: str) -> str:
     return ''.join(c for c in text if not unicodedata.combining(c))
 
 
-def find_combining_chars(text):
+def find_combining_chars(text: str) -> list[int]:
     """Return indices of all combining chars in  Unicode string `text`.
 
     >>> from docutils.utils import find_combining_chars
@@ -659,7 +716,7 @@
     return [i for i, c in enumerate(text) if unicodedata.combining(c)]
 
 
-def column_indices(text):
+def column_indices(text: str) -> list[int]:
     """Indices of Unicode string `text` when skipping combining characters.
 
     >>> from docutils.utils import column_indices
@@ -686,7 +743,7 @@
 column widths."""
 
 
-def column_width(text):
+def column_width(text: str) -> int:
     """Return the column width of text.
 
     Correct ``len(text)`` for wide East Asian and combining Unicode chars.
@@ -698,7 +755,7 @@
     return width
 
 
-def uniq(L):
+def uniq(L: list) -> list:
     r = []
     for item in L:
         if item not in r:
@@ -706,7 +763,7 @@
     return r
 
 
-def normalize_language_tag(tag):
+def normalize_language_tag(tag: str) -> list[str]:
     """Return a list of normalized combinations for a `BCP 47` language tag.
 
     Example:
@@ -734,7 +791,7 @@
     return taglist
 
 
-def xml_declaration(encoding=None) -> str:
+def xml_declaration(encoding: str | Literal['unicode'] | None = None) -> str:
     """Return an XML text declaration.
 
     Include an encoding declaration, if `encoding`
@@ -756,7 +813,10 @@
     to explicitly call the close() method.
     """
 
-    def __init__(self, output_file=None, dependencies=()) -> None:
+    def __init__(self,
+                 output_file: Literal['-'] | _StrPath | None = None,
+                 dependencies: Iterable[_StrPath] = ()
+                 ) -> None:
         """
         Initialize the dependency list, automatically setting the
         output file to `output_file` (see `set_output()`) and adding
@@ -765,12 +825,12 @@
         If output_file is None, no file output is done when calling add().
         """
         self.list = []
-        self.file = None
+        self.file: TextIO | None = None
         if output_file:
             self.set_output(output_file)
         self.add(*dependencies)
 
-    def set_output(self, output_file) -> None:
+    def set_output(self, output_file: Literal['-'] | _StrPath) -> None:
         """
         Set the output file and clear the list of already added
         dependencies.
@@ -780,13 +840,12 @@
 
         If output_file is '-', the output will be written to stdout.
         """
-        if output_file:
-            if output_file == '-':
-                self.file = sys.stdout
-            else:
-                self.file = open(output_file, 'w', encoding='utf-8')
+        if output_file == '-':
+            self.file = sys.stdout
+        elif output_file:
+            self.file = open(output_file, 'w', encoding='utf-8')
 
-    def add(self, *paths) -> None:
+    def add(self, *paths: _StrPath) -> None:
         """
         Append `path` to `self.list` unless it is already there.
 
@@ -817,14 +876,15 @@
         return '%s(%r, %s)' % (self.__class__.__name__, output_file, self.list)
 
 
-release_level_abbreviations = {
+release_level_abbreviations: dict[str, str] = {
     'alpha': 'a',
     'beta': 'b',
     'candidate': 'rc',
-    'final': ''}
+    'final': '',
+}
 
 
-def version_identifier(version_info=None):
+def version_identifier(version_info: VersionInfo | None = None) -> str:
     """
     Return a version identifier string built from `version_info`, a
     `docutils.VersionInfo` namedtuple instance or compatible tuple. If

This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.



_______________________________________________
Docutils-checkins mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/docutils-checkins
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.