SF.net SVN: docutils:[9901 ] trunk/docutils/docutils

milde--- via Docutils-checkins <[email protected]>
Newsgroups gmane.text.docutils.cvs
Message-ID <[email protected]>
Revision: 9901
          http://sourceforge.net/p/docutils/code/9901
Author:   milde
Date:     2024-08-15 07:30:22 +0000 (Thu, 15 Aug 2024)
Log Message:
-----------
Use base class "Node" instead of `Element | Node` in type hints.

`nodes.Element` and `nodes.Node` are the only child classes of the abstract
base class `nodes.Node`.

Ensure `nodes.Node` has all attributes and methods that are defined in
both subclasses.

Modified Paths:
--------------
    trunk/docutils/docutils/nodes.py
    trunk/docutils/docutils/parsers/rst/directives/misc.py
    trunk/docutils/docutils/utils/__init__.py

Modified: trunk/docutils/docutils/nodes.py
===================================================================
--- trunk/docutils/docutils/nodes.py	2024-08-14 19:10:12 UTC (rev 9900)
+++ trunk/docutils/docutils/nodes.py	2024-08-15 07:30:22 UTC (rev 9901)
@@ -57,7 +57,6 @@
     _ContentModelTuple: TypeAlias = tuple[_ContentModelItem, ...]
 
     _UpdateFun: TypeAlias = Callable[[str, Any, bool], None]
-    _ElementT: TypeAlias = Element | Text  # TODO: use Node instead?
 
 
 # ==============================
@@ -67,12 +66,15 @@
 class Node:
     """Abstract base class of nodes in a document tree."""
 
-    parent: Element = None
+    parent: Node = None
     """Back-reference to the Node immediately containing this Node."""
 
-    children: Sequence[_ElementT]  # defined in subclasses
-    """List of child nodes (Elements or Text)."""
+    children: Sequence[Node] = ()
+    """Sequence of child nodes.
 
+    Override in subclass instances that are not terminal nodes.
+    """
+
     source: str | os.PathLike[str] | None = None
     """Path or description of the input source which generated this Node."""
 
@@ -118,6 +120,14 @@
         domroot = dom.Document()
         return self._dom_node(domroot)
 
+    def _dom_node(self, domroot: minidom.Document) -> minidom.Text:
+        # Stub. Override in subclasses.
+        return domroot.createElement(self.__class__.__name__)
+
+    def shortrepr(self) -> str:
+        # concise string representation for test and debugging purposes
+        return repr(self)
+
     def pformat(self, indent: str = '    ', level: int = 0) -> str:
         """
         Return an indented pseudo-XML representation, for test purposes.
@@ -138,7 +148,7 @@
         """Return a string representation of this Node."""
         raise NotImplementedError
 
-    def setup_child(self, child: _ElementT) -> None:
+    def setup_child(self, child: Node) -> None:
         child.parent = self
         if self.document:
             child.document = self.document
@@ -377,7 +387,13 @@
         except StopIteration:
             return None
 
+    def validate(self, recursive: bool = True) -> None:
+        """Raise ValidationError if this node is not valid.
 
+        Override in subclasses that define validity constraints.
+        """
+
+
 class Text(Node, str):  # NoQA: SLOT000 (Node doesn't define __slots__)
     """
     Instances are terminal nodes (leaves) containing text only; no child
@@ -450,15 +466,7 @@
     def lstrip(self, chars: str | None = None) -> Self:
         return self.__class__(str.lstrip(self, chars))
 
-    def validate(self, recursive: bool = True) -> None:
-        """Validate Docutils Document Tree element ("doctree")."""
-        # Text nodes have no attributes and no children.
 
-    def check_position(self) -> None:
-        """Hook for additional checks of the parent's content model."""
-        # no special placement requirements for Text nodes
-
-
 class Element(Node):
     """
     `Element` is the superclass to all specific elements.
@@ -565,7 +573,7 @@
 
     def __init__(self,
                  rawsource: str = '',
-                 *children: _ElementT,
+                 *children: Node,
                  **attributes: Any,
                  ) -> None:
         self.rawsource = rawsource
@@ -578,7 +586,7 @@
         if isinstance(rawsource, Element):
             raise TypeError('First argument "rawsource" must be a string.')
 
-        self.children: list[_ElementT] = []
+        self.children: list[Node] = []
         """List of child nodes (elements and/or `Text`)."""
 
         self.extend(children)           # maintain parent info
@@ -619,15 +627,14 @@
                 data = data[:56] + ' ...'
                 break
         if self['names']:
-            return '<%s "%s": %s>' % (self.__class__.__name__,
+            return '<%s "%s": %s>' % (self.tagname,
                                       '; '.join(self['names']), data)
         else:
-            return '<%s: %s>' % (self.__class__.__name__, data)
+            return '<%s: %s>' % (self.tagname, data)
 
     def shortrepr(self) -> str:
         if self['names']:
-            return '<%s "%s"...>' % (self.__class__.__name__,
-                                     '; '.join(self['names']))
+            return '<%s "%s"...>' % (self.tagname, '; '.join(self['names']))
         else:
             return '<%s...>' % self.tagname
 
@@ -669,7 +676,7 @@
     def __len__(self) -> int:
         return len(self.children)
 
-    def __contains__(self, key: str | _ElementT) -> bool:
+    def __contains__(self, key: str | Node) -> bool:
         # Test for both, children and attributes with operator ``in``.
         if isinstance(key, str):
             return key in self.attributes
@@ -680,16 +687,16 @@
         ...
 
     @overload
-    def __getitem__(self, key: int) -> _ElementT:
+    def __getitem__(self, key: int) -> Node:
         ...
 
     @overload
-    def __getitem__(self, key: slice) -> list[_ElementT]:
+    def __getitem__(self, key: slice) -> list[Node]:
         ...
 
     def __getitem__(self,
                     key: str | int | slice,
-                    ) -> _ElementT | list[_ElementT] | Any:
+                    ) -> Node | list[Node] | Any:
         if isinstance(key, str):
             return self.attributes[key]
         elif isinstance(key, int):
@@ -706,11 +713,11 @@
         ...
 
     @overload
-    def __setitem__(self, key: int, item: _ElementT) -> None:
+    def __setitem__(self, key: int, item: Node) -> None:
         ...
 
     @overload
-    def __setitem__(self, key: slice, item: Iterable[_ElementT]) -> None:
+    def __setitem__(self, key: slice, item: Iterable[Node]) -> None:
         ...
 
     def __setitem__(self, key, item) -> None:
@@ -740,13 +747,13 @@
             raise TypeError('element index must be an integer, a simple '
                             'slice, or an attribute name string')
 
-    def __add__(self, other: list[_ElementT]) -> list[_ElementT]:
+    def __add__(self, other: list[Node]) -> list[Node]:
         return self.children + other
 
-    def __radd__(self, other: list[_ElementT]) -> list[_ElementT]:
+    def __radd__(self, other: list[Node]) -> list[Node]:
         return other + self.children
 
-    def __iadd__(self, other: _ElementT | Iterable[_ElementT]) -> Self:
+    def __iadd__(self, other: Node | Iterable[Node]) -> Self:
         """Append a node or a list of nodes to `self.children`."""
         if isinstance(other, Node):
             self.append(other)
@@ -812,17 +819,17 @@
         except AttributeError:
             return fallback
 
-    def append(self, item: _ElementT) -> None:
+    def append(self, item: Node) -> None:
         self.setup_child(item)
         self.children.append(item)
 
-    def extend(self, item: Iterable[_ElementT]) -> None:
+    def extend(self, item: Iterable[Node]) -> None:
         for node in item:
             self.append(node)
 
     def insert(self,
                index: SupportsIndex,
-               item: _ElementT | Iterable[_ElementT],
+               item: Node | Iterable[Node],
                ) -> None:
         if isinstance(item, Node):
             self.setup_child(item)
@@ -830,20 +837,20 @@
         elif item is not None:
             self[index:index] = item
 
-    def pop(self, i: int = -1) -> _ElementT:
+    def pop(self, i: int = -1) -> Node:
         return self.children.pop(i)
 
-    def remove(self, item: _ElementT) -> None:
+    def remove(self, item: Node) -> None:
         self.children.remove(item)
 
     def index(self,
-              item: _ElementT,
+              item: Node,
               start: int = 0,
               stop: int = sys.maxsize,
               ) -> int:
         return self.children.index(item, start, stop)
 
-    def previous_sibling(self) -> _ElementT | None:
+    def previous_sibling(self) -> Node | None:
         """Return preceding sibling node or ``None``."""
         try:
             i = self.parent.index(self)
@@ -1109,8 +1116,8 @@
         self.children = []
 
     def replace(self,
-                old: _ElementT,
-                new: _ElementT | Iterable[_ElementT],
+                old: Node,
+                new: Node | Iterable[Node],
                 ) -> None:
         """Replace one child `Node` with another child or children."""
         index = self.index(old)
@@ -1120,7 +1127,7 @@
         elif new is not None:
             self[index:index+1] = new
 
-    def replace_self(self, new: _ElementT | Sequence[_ElementT]) -> None:
+    def replace_self(self, new: Node | Sequence[Node]) -> None:
         """
         Replace `self` node with `new`, where `new` is a node or a
         list of nodes.
@@ -1279,8 +1286,8 @@
 
     def validate_content(self,
                          model: _ContentModelTuple | None = None,
-                         elements: Sequence[_ElementT] | None = None,
-                         ) -> list[_ElementT]:
+                         elements: Sequence[Node] | None = None,
+                         ) -> list[Node]:
         """Test compliance of `elements` with `model`.
 
         :model: content model description, default `self.content_model`,
@@ -1306,7 +1313,10 @@
                     continue  # try same child with next part of content model
             else:
                 # Check additional placement constraints (if applicable):
-                child.check_position()
+                try:
+                    child.check_position()
+                except AttributeError:
+                    pass
             # advance:
             if quantifier in ('.', '?'):  # go to next element
                 child = next(ichildren, None)
@@ -1314,13 +1324,16 @@
                 for child in ichildren:
                     if not isinstance(child, category):
                         break
-                    child.check_position()
+                    try:
+                        child.check_position()
+                    except AttributeError:
+                        pass
                 else:
                     child = None
         return [] if child is None else [child, *ichildren]
 
     def _report_child(self,
-                      child: _ElementT | None,
+                      child: Node | None,
                       category: Element | Iterable[Element],
                       ) -> str:
         # Return a str reporting a missing child or child of wrong category.
@@ -1527,7 +1540,7 @@
     def __init__(self,
                  rawsource: str = '',
                  text: str = '',
-                 *children: _ElementT,
+                 *children: Node,
                  **attributes: Any,
                  ) -> None:
         if text:
@@ -1546,7 +1559,7 @@
     def __init__(self,
                  rawsource: str = '',
                  text: str = '',
-                 *children: _ElementT,
+                 *children: Node,
                  **attributes: Any,
                  ) -> None:
         super().__init__(rawsource, text, *children, **attributes)
@@ -1730,7 +1743,7 @@
     def __init__(self,
                  settings: Values,
                  reporter: Reporter,
-                 *args: _ElementT,
+                 *args: Node,
                  **kwargs: Any,
                  ) -> None:
         Element.__init__(self, *args, **kwargs)
@@ -2134,8 +2147,8 @@
 
     def validate_content(self,
                          model: _ContentModelTuple | None = None,
-                         elements: Sequence[_ElementT] | None = None,
-                         ) -> list[_ElementT]:
+                         elements: Sequence[Node] | None = None,
+                         ) -> list[Node]:
         """Repeatedly test for children matching the content model.
 
         Provisional.
@@ -2474,7 +2487,7 @@
 
     def __init__(self,
                  message: str | None = None,
-                 *children: _ElementT,
+                 *children: Node,
                  **attributes: Any,
                  ) -> None:
         rawsource = attributes.pop('rawsource', '')
@@ -2526,7 +2539,7 @@
                  transform: Transform,
                  details: Mapping[str, Any] | None = None,
                  rawsource: str = '',
-                 *children: _ElementT,
+                 *children: Node,
                  **attributes: Any,
                  ) -> None:
         Element.__init__(self, rawsource, *children, **attributes)
@@ -2699,7 +2712,7 @@
     def __init__(self, document: document, /) -> None:
         self.document: document = document
 
-    def dispatch_visit(self, node: _ElementT) -> None:
+    def dispatch_visit(self, node: Node) -> None:
         """
         Call self."``visit_`` + node class name" with `node` as
         parameter.  If the ``visit_...`` method does not exist, call
@@ -2712,7 +2725,7 @@
             % (method.__name__, node_name))
         return method(node)
 
-    def dispatch_departure(self, node: _ElementT) -> None:
+    def dispatch_departure(self, node: Node) -> None:
         """
         Call self."``depart_`` + node class name" with `node` as
         parameter.  If the ``depart_...`` method does not exist, call
@@ -2725,7 +2738,7 @@
             % (method.__name__, node_name))
         return method(node)
 
-    def unknown_visit(self, node: _ElementT) -> None:
+    def unknown_visit(self, node: Node) -> None:
         """
         Called when entering unknown `Node` types.
 
@@ -2737,7 +2750,7 @@
                 '%s visiting unknown node type: %s'
                 % (self.__class__, node.__class__.__name__))
 
-    def unknown_departure(self, node: _ElementT) -> None:
+    def unknown_departure(self, node: Node) -> None:
         """
         Called before exiting unknown `Node` types.
 
@@ -2776,24 +2789,24 @@
     be overridden for default behavior.
     """
 
-    def default_visit(self, node: _ElementT):
+    def default_visit(self, node: Node):
         """Override for generic, uniform traversals."""
         raise NotImplementedError
 
-    def default_departure(self, node: _ElementT):
+    def default_departure(self, node: Node):
         """Override for generic, uniform traversals."""
         raise NotImplementedError
 
 
-def _call_default_visit(self: GenericNodeVisitor, node: _ElementT) -> None:
+def _call_default_visit(self: GenericNodeVisitor, node: Node) -> None:
     self.default_visit(node)
 
 
-def _call_default_departure(self: GenericNodeVisitor, node: _ElementT) -> None:
+def _call_default_departure(self: GenericNodeVisitor, node: Node) -> None:
     self.default_departure(node)
 
 
-def _nop(self: SparseNodeVisitor, node: _ElementT) -> None:
+def _nop(self: SparseNodeVisitor, node: Node) -> None:
     pass
 
 
@@ -2816,13 +2829,13 @@
 
     def __init__(self, document: document) -> None:
         super().__init__(document)
-        self.parent_stack: list[list[_ElementT]] = []
-        self.parent: list[_ElementT] = []
+        self.parent_stack: list[list[Node]] = []
+        self.parent: list[Node] = []
 
-    def get_tree_copy(self) -> _ElementT:
+    def get_tree_copy(self) -> Node:
         return self.parent[0]
 
-    def default_visit(self, node: _ElementT) -> None:
+    def default_visit(self, node: Node) -> None:
         """Copy the current node, and make it the new acting parent."""
         newnode = node.copy()
         self.parent.append(newnode)
@@ -2829,7 +2842,7 @@
         self.parent_stack.append(self.parent)
         self.parent = newnode
 
-    def default_departure(self, node: _ElementT) -> None:
+    def default_departure(self, node: Node) -> None:
         """Restore the previous acting parent."""
         self.parent = self.parent_stack.pop()
 

Modified: trunk/docutils/docutils/parsers/rst/directives/misc.py
===================================================================
--- trunk/docutils/docutils/parsers/rst/directives/misc.py	2024-08-14 19:10:12 UTC (rev 9900)
+++ trunk/docutils/docutils/parsers/rst/directives/misc.py	2024-08-15 07:30:22 UTC (rev 9901)
@@ -24,7 +24,7 @@
 if TYPE_CHECKING:
     import os
 
-    from docutils.nodes import Element, Text
+    from docutils.nodes import Node
 
 
 def adapt_path(path, source='', root_prefix='/'):
@@ -74,7 +74,7 @@
 
     standard_include_path = Path(states.__file__).parent / 'include'
 
-    def run(self) -> list[Element | Text]:
+    def run(self) -> list[Node]:
         """Include a file as part of the content of this reST file.
 
         Depending on the options, the file (or a clipping) is
@@ -275,7 +275,7 @@
         source: str | os.PathLike[str],
         include_lines: list[str],
         clip_options: tuple[int | str, int | str, str, str],
-    ) -> list[Element | Text]:
+    ) -> list[Node]:
         """Return content from *source* after parsing with the given parser."""
         settings = self.state.document.settings
         include_log = self.state.document.include_log

Modified: trunk/docutils/docutils/utils/__init__.py
===================================================================
--- trunk/docutils/docutils/utils/__init__.py	2024-08-14 19:10:12 UTC (rev 9900)
+++ trunk/docutils/docutils/utils/__init__.py	2024-08-15 07:30:22 UTC (rev 9901)
@@ -31,7 +31,7 @@
 
     from typing_extensions import TypeAlias
 
-    from docutils.nodes import Element, Text
+    from docutils.nodes import Node
     from docutils.frontend import Values
 
     _StrPath: TypeAlias = str | os.PathLike[str]
@@ -179,7 +179,7 @@
     def system_message(self,
                        level: int,
                        message: str,
-                       *children: Element | Text,
+                       *children: Node,
                        **kwargs: Any
                        ) -> nodes.system_message:
         """
@@ -229,7 +229,7 @@
         return msg
 
     def debug(
-        self, *args: Element | Text, **kwargs: Any
+        self, *args: Node, **kwargs: Any
     ) -> nodes.system_message:
         """
         Level-0, "DEBUG": an internal reporting issue. Typically, there is no
@@ -240,7 +240,7 @@
             return self.system_message(self.DEBUG_LEVEL, *args, **kwargs)
 
     def info(
-        self, *args: Element | Text, **kwargs: Any
+        self, *args: Node, **kwargs: Any
     ) -> nodes.system_message:
         """
         Level-1, "INFO": a minor issue that can be ignored. Typically there is
@@ -249,7 +249,7 @@
         return self.system_message(self.INFO_LEVEL, *args, **kwargs)
 
     def warning(
-        self, *args: Element | Text, **kwargs: Any
+        self, *args: Node, **kwargs: Any
     ) -> nodes.system_message:
         """
         Level-2, "WARNING": an issue that should be addressed. If ignored,
@@ -258,7 +258,7 @@
         return self.system_message(self.WARNING_LEVEL, *args, **kwargs)
 
     def error(
-        self, *args: Element | Text, **kwargs: Any
+        self, *args: Node, **kwargs: Any
     ) -> nodes.system_message:
         """
         Level-3, "ERROR": an error that should be addressed. If ignored, the
@@ -267,7 +267,7 @@
         return self.system_message(self.ERROR_LEVEL, *args, **kwargs)
 
     def severe(
-        self, *args: Element | Text, **kwargs: Any
+        self, *args: Node, **kwargs: Any
     ) -> nodes.system_message:
         """
         Level-4, "SEVERE": a severe error that must be addressed. If ignored,
@@ -661,7 +661,7 @@
         return settings.trim_footnote_reference_space
 
 
-def get_source_line(node: Element) -> tuple[_StrPath | None, int | None]:
+def get_source_line(node: Node) -> tuple[_StrPath | None, int | None]:
     """
     Return the "source" and "line" attributes from the `node` given or from
     its closest ancestor.

This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
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.