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

milde--- via Docutils-checkins <[email protected]> Mon, 08 Sep 2025 13:44:23 +0000
Newsgroups gmane.text.docutils.cvs
Message-ID <[email protected]>
Revision: 10228
          http://sourceforge.net/p/docutils/code/10228
Author:   milde
Date:     2025-09-08 13:44:23 +0000 (Mon, 08 Sep 2025)
Log Message:
-----------
rST parser: simplifications, fixes, and improvements

Define the attribute `parent_state_machine` only in
`parsers.rst.state.NestedStateMachine`.
It does not make sense in other state machines.

More detailled error message for inacessible section parents.

Test with a copy of Sphinx's `_fresh_title_style_context`.

Modified Paths:
--------------
    trunk/docutils/HISTORY.rst
    trunk/docutils/docutils/parsers/rst/states.py
    trunk/docutils/docutils/statemachine.py
    trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py

Modified: trunk/docutils/HISTORY.rst
===================================================================
--- trunk/docutils/HISTORY.rst	2025-09-05 14:07:30 UTC (rev 10227)
+++ trunk/docutils/HISTORY.rst	2025-09-08 13:44:23 UTC (rev 10228)
@@ -14,7 +14,7 @@
 .. contents::
 
 
-Release 0.23b0 (unpublished)
+Release 0.22.1 (unpublished)
 ============================
 
 * docutils/frontend.py, docutils/writers/
@@ -29,19 +29,14 @@
 * docutils/parsers/rst/states.py
 
   - Relax "section title" system messages from SEVERE to ERROR.
-  - Revert to using `document.memo.section_level` to fix behaviour with
-    nested parsing into a detached node (cf. bugs #508 and #509).
-  - Set `parent_state_machine` attribute when creating nested state machines.
-    Use it to update the "current node" of the parent state machines after
-    nested parsing.
+  - Fix behaviour with nested parsing into a detached node
+    (cf. bugs #508 and #509).
+  - New attribute `NestedStateMachine.parent_state_machine`.
+    Use case: update the "current node" of parent state machine(s)
+    after nested parsing.
   - Better error messages for grid table markup errors (bug #504),
     based on patch #214 by Jynn Nelson.
 
-* docutils/statemachine.py
-
-  - New attribute `StateMachine.parent_state_machine` to store the
-    parent state machine of nested state machines.
-
 * docutils/transforms/references.py
 
   - Better error reports for hyperlinks with embedded URI or alias.
@@ -52,7 +47,7 @@
     for elements with IDs (fixes bug #503).
   - Fix cross-reference anchor placement in figures, images,
     literal-blocks, tables, and (sub)titles.
-  - Simplify code for nested image.
+  - Simplify code for images nested in reference or figure elements.
 
 
 Release 0.22 (2026-07-29)

Modified: trunk/docutils/docutils/parsers/rst/states.py
===================================================================
--- trunk/docutils/docutils/parsers/rst/states.py	2025-09-05 14:07:30 UTC (rev 10227)
+++ trunk/docutils/docutils/parsers/rst/states.py	2025-09-08 13:44:23 UTC (rev 10228)
@@ -182,6 +182,14 @@
     document structures.
     """
 
+    def __init__(self, state_classes, initial_state,
+                 debug=False, parent_state_machine=None) -> None:
+
+        self.parent_state_machine = parent_state_machine
+        """The instance of the parent state machine."""
+
+        super().__init__(state_classes, initial_state, debug)
+
     def run(self, input_lines, input_offset, memo, node, match_titles=True):
         """
         Parse `input_lines` and populate `node`.
@@ -328,7 +336,6 @@
 
         # run the state machine and populate `node`:
         block_length = len(block)
-        old_section_level = self.memo.section_level
         my_state_machine.run(block, input_offset, memo=self.memo,
                              node=node, match_titles=match_titles)
 
@@ -342,8 +349,10 @@
                         sm = sm.parent_state_machine
                 except AttributeError:
                     pass
-            else:
-                self.memo.section_level = old_section_level
+            # set section level
+            # (fails with Sphinx's `_fresh_title_style_context`)
+            self.memo.section_level = len(
+                self.state_machine.node.section_hierarchy())
         # clean up
         new_offset = my_state_machine.abs_line_offset()
         if use_default == 2:
@@ -438,11 +447,12 @@
             try:
                 new_parent = parent_sections[newlevel-oldlevel-1].parent
             except IndexError:
-                new_parent = None
-            if new_parent is None:
                 styles = ' '.join('/'.join(style) for style in title_styles)
                 details = (f'The parent of level {newlevel} sections cannot'
-                           ' be reached.\nOne reason may be a high level'
+                           ' be reached. The parser is at section level'
+                           f' {oldlevel} but the current node has only'
+                           f' {len(parent_sections)} parent section(s).'
+                           '\nOne reason may be a high level'
                            ' section used in a directive that parses its'
                            ' content into a base node not attached to'
                            ' the document\n(up to Docutils 0.21,'

Modified: trunk/docutils/docutils/statemachine.py
===================================================================
--- trunk/docutils/docutils/statemachine.py	2025-09-05 14:07:30 UTC (rev 10227)
+++ trunk/docutils/docutils/statemachine.py	2025-09-08 13:44:23 UTC (rev 10228)
@@ -130,8 +130,7 @@
     results of processing in a list.
     """
 
-    def __init__(self, state_classes, initial_state,
-                 debug=False, parent_state_machine=None) -> None:
+    def __init__(self, state_classes, initial_state, debug=False) -> None:
         """
         Initialize a `StateMachine` object; add state objects.
 
@@ -140,7 +139,6 @@
         - `state_classes`: a list of `State` (sub)classes.
         - `initial_state`: a string, the class name of the initial state.
         - `debug`: a boolean; produce verbose output if true (nonzero).
-        - `parent_state_machine`: the parent of a nested state machine.
         """
         self.input_lines = None
         """`StringList` of input lines (without newlines).
@@ -158,9 +156,6 @@
         self.debug = debug
         """Debugging mode on/off."""
 
-        self.parent_state_machine = parent_state_machine
-        """The instance of the parent state machine or None."""
-
         self.initial_state = initial_state
         """The name of the initial state (key to `self.states`)."""
 

Modified: trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py
===================================================================
--- trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py	2025-09-05 14:07:30 UTC (rev 10227)
+++ trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py	2025-09-08 13:44:23 UTC (rev 10228)
@@ -24,6 +24,7 @@
 """
 
 from pathlib import Path
+import contextlib
 import sys
 import unittest
 
@@ -87,6 +88,7 @@
 
 class ParseIntoSectionNode(ParseIntoNode):
     # Some 3rd party extensions use a <section> as dummy base node.
+    # cf. https://github.com/sphinx-contrib/autoprogram/blob/master/sphinxcontrib/autoprogram.py
     #
     # Attention: this directive is flawed:
     # * no check for section validity,
@@ -98,6 +100,53 @@
         return node.children
 
 
+class FreshParseIntoNode(ParseIntoNode):
+    """Nested parsing with support for sections (separate title styles).
+
+    * no check for section validity,
+    * "current" node not updated! -> element order may get lost.
+
+    cf. `sphinx.util.nodes.nested_parse_with_titles()`
+    and `sphinx.util.parsing.nested_parse_to_nodes()`
+    """
+    def run(self):
+        node = nodes.Element()
+        with _fresh_title_style_context(self.state):
+            self.state.nested_parse(self.content, self.content_offset,
+                                    node, match_titles=True)
+        return node.children
+
+
+class FreshParseIntoCurrentNode(ParseIntoNode):
+    # Nested parsing with support for sections (separate title styles)
+    #
+    # Parsing into the current node, `nested_parse()` ensures validity
+    # and updates the "current node".
+    def run(self):
+        with _fresh_title_style_context(self.state):
+            self.state.nested_parse(self.content, self.content_offset,
+                                    match_titles=True)
+        # update section level
+        self.state_machine.memo.section_level = len(
+            self.state_machine.node.section_hierarchy())
+        return []  # node already attached to document
+
+
[email protected]
+def _fresh_title_style_context(state):
+    # copied from sphinx/sphinx/util/parsing.py
+    memo = state.memo
+    surrounding_title_styles = memo.title_styles
+    surrounding_section_level = memo.section_level
+    memo.title_styles = []
+    memo.section_level = 0
+    try:
+        yield
+    finally:
+        memo.title_styles = surrounding_title_styles
+        memo.section_level = surrounding_section_level
+
+
 class ParserTestCase(unittest.TestCase):
     maxDiff = None
 
@@ -105,6 +154,8 @@
         register_directive('nested', ParseIntoNode)
         register_directive('nested-current', ParseIntoCurrentNode)
         register_directive('nested-section', ParseIntoSectionNode)
+        register_directive('fresh', FreshParseIntoNode)
+        register_directive('fresh-current', FreshParseIntoCurrentNode)
         parser = rst.Parser()
         settings = get_default_settings(rst.Parser)
         settings.warning_stream = ''
@@ -147,6 +198,8 @@
   ***********
   nested2.1
   ---------
+  nested2.2
+  ---------
   inaccessible2
   =============
 
@@ -183,7 +236,10 @@
         <section ids="nested2-1" names="nested2.1">
             <title>
                 nested2.1
-            <system_message level="3" line="20" source="test data" type="ERROR">
+        <section ids="nested2-2" names="nested2.2">
+            <title>
+                nested2.2
+            <system_message level="3" line="22" source="test data" type="ERROR">
                 <paragraph>
                     A level 1 section cannot be used here.
                 <literal_block xml:space="preserve">
@@ -192,13 +248,13 @@
                 <paragraph>
                     Established title styles: = - * ~
                 <paragraph>
-                    The parent of level 1 sections cannot be reached.
+                    The parent of level 1 sections cannot be reached. The parser is at section level 2 but the current node has only 1 parent section(s).
                     One reason may be a high level section used in a directive that parses its content into a base node not attached to the document
                     (up to Docutils 0.21, these sections were silently dropped).
         <section ids="sec2-2" names="sec2.2">
             <title>
                 sec2.2
-            <system_message level="3" line="25" source="test data" type="ERROR">
+            <system_message level="3" line="27" source="test data" type="ERROR">
                 <paragraph>
                     Inconsistent title style: skip from level 2 to 4.
                 <literal_block xml:space="preserve">
@@ -385,7 +441,7 @@
             <paragraph>
                 Established title styles: =
             <paragraph>
-                The parent of level 1 sections cannot be reached.
+                The parent of level 1 sections cannot be reached. The parser is at section level 1 but the current node has only 0 parent section(s).
                 One reason may be a high level section used in a directive that parses its content into a base node not attached to the document
                 (up to Docutils 0.21, these sections were silently dropped).
         <paragraph>
@@ -439,6 +495,132 @@
             Element <block_quote> invalid:
               Child element <section ids="invalid-section-sic" names="invalid\\ section\\ (sic!)"> not allowed at this position.
 """],
+# Nested parsing with new title style hierarchy
+["""\
+sec1
+====
+sec1.1
+------
+.. fresh::
+
+  fresh1.1.1
+  ==========
+  fresh1.1.1.1
+  ~~~~~~~~~~~~~
+
+sec2
+====
+.. fresh::
+
+  fresh2.1
+  ***********
+  New title styles with every directive.
+
+  fresh2.1.1
+  -----------
+  fresh2.1.2
+  -----------
+  fresh2.1.2.1
+  =============
+
+This text belongs into the last nested section (sic!).
+
+sec2.2
+------
+Document-wide title styles unchanged
+
+sec2.2.1
+********
+""",
+"""\
+<document source="test data">
+    <section ids="sec1" names="sec1">
+        <title>
+            sec1
+        <section ids="sec1-1" names="sec1.1">
+            <title>
+                sec1.1
+            <section ids="fresh1-1-1" names="fresh1.1.1">
+                <title>
+                    fresh1.1.1
+                <section ids="fresh1-1-1-1" names="fresh1.1.1.1">
+                    <title>
+                        fresh1.1.1.1
+    <section ids="sec2" names="sec2">
+        <title>
+            sec2
+        <section ids="fresh2-1" names="fresh2.1">
+            <title>
+                fresh2.1
+            <paragraph>
+                New title styles with every directive.
+            <section ids="fresh2-1-1" names="fresh2.1.1">
+                <title>
+                    fresh2.1.1
+            <section ids="fresh2-1-2" names="fresh2.1.2">
+                <title>
+                    fresh2.1.2
+                <section ids="fresh2-1-2-1" names="fresh2.1.2.1">
+                    <title>
+                        fresh2.1.2.1
+        <paragraph>
+            This text belongs into the last nested section (sic!).
+        <section ids="sec2-2" names="sec2.2">
+            <title>
+                sec2.2
+            <paragraph>
+                Document-wide title styles unchanged
+            <section ids="sec2-2-1" names="sec2.2.1">
+                <title>
+                    sec2.2.1
+    <system_message level="2" line="27" source="test data" type="WARNING">
+        <paragraph>
+            Element <section ids="sec2" names="sec2"> invalid:
+              Child element <paragraph> not allowed at this position.
+"""],
+# Nested parsing into current node with new title style hierarchy
+["""\
+sec1
+====
+sec1.1
+------
+.. fresh-current::
+
+  fc1.1.1
+  -------
+  fc1.1.2
+  -------
+  fc1.1.2.1
+  =========
+
+This text belongs into the last nested section.
+
+sec1.2
+------
+""",
+"""\
+<document source="test data">
+    <section ids="sec1" names="sec1">
+        <title>
+            sec1
+        <section ids="sec1-1" names="sec1.1">
+            <title>
+                sec1.1
+            <section ids="fc1-1-1" names="fc1.1.1">
+                <title>
+                    fc1.1.1
+            <section ids="fc1-1-2" names="fc1.1.2">
+                <title>
+                    fc1.1.2
+                <section ids="fc1-1-2-1" names="fc1.1.2.1">
+                    <title>
+                        fc1.1.2.1
+                    <paragraph>
+                        This text belongs into the last nested section.
+        <section ids="sec1-2" names="sec1.2">
+            <title>
+                sec1.2
+"""],
 ]
 
 

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