11from abc import ABC , abstractmethod
22from enum import IntEnum , auto
3+ from textwrap import dedent
34from types import SimpleNamespace
45from typing import Callable , Match , Union , List , Dict
56import re
@@ -299,8 +300,8 @@ def inline_markdown(self):
299300SECTION_DIRECTIVES : Dict [str , List [Directive ]] = {
300301 'Parameters' : [
301302 Directive (
302- pattern = r'^(?P<other_args>\*\*kwargs|\*args)$' ,
303- replacement = r'- `\g<other_args>`'
303+ pattern = r'^(?P<other_args>(\w[\w\d_\.]*)| \*\*kwargs|\*args)$' ,
304+ replacement = r'- `\g<other_args>`: '
304305 ),
305306 Directive (
306307 pattern = r'^(?P<arg1>[^:\s]+\d), (?P<arg2>[^:\s]+\d), \.\.\. : (?P<type>.+)$' ,
@@ -336,6 +337,7 @@ def _find_directive_pattern(name: str):
336337
337338
338339def looks_like_rst (value : str ) -> bool :
340+ value = dedent (value )
339341 # check if any of the characteristic sections (and the properly formatted underline) is there
340342 for section in _RST_SECTIONS :
341343 if (section + '\n ' + '-' * len (section ) + '\n ' ) in value :
@@ -542,10 +544,20 @@ class BlockParser(IParser):
542544 follower : Union ['IParser' , None ] = None
543545 _buffer : List [str ]
544546 _block_started : bool
547+ _indent : Union [int , None ]
548+ should_measure_indent = True
545549
546550 def __init__ (self ):
547551 self ._buffer = []
548552 self ._block_started = False
553+ self ._indent = None
554+
555+ def measure_indent (self , line : str ):
556+ line_indent = len (line ) - len (line .lstrip ())
557+ if self ._indent is None :
558+ self ._indent = line_indent
559+ else :
560+ self ._indent = min (line_indent , self ._indent )
549561
550562 @abstractmethod
551563 def can_parse (self , line : str ) -> bool :
@@ -558,24 +570,33 @@ def _start_block(self, language: str):
558570 def consume (self , line : str ):
559571 if not self ._block_started :
560572 raise ValueError ('Block has not started' ) # pragma: no cover
573+ if self .should_measure_indent :
574+ self .measure_indent (line )
561575 self ._buffer .append (line )
562576
563577 def finish_consumption (self , final : bool ) -> str :
564578 # if the last line is empty (e.g. a separator of intended block), discard it
565579 if self ._buffer [len (self ._buffer ) - 1 ].strip () == '' :
566580 self ._buffer .pop ()
567581 self ._buffer .append (self .enclosure + '\n ' )
568- result = '\n ' .join (self ._buffer )
582+ indent = " " * (self ._indent or 0 )
583+ intermediate = '\n ' .join (self ._buffer )
584+ result = '\n ' .join ([
585+ (indent + line ) if line else line
586+ for line in intermediate .splitlines ()
587+ ]) if indent else intermediate
569588 if not final :
570589 result += '\n '
571590 self ._buffer = []
572591 self ._block_started = False
592+ self ._indent = None
573593 return result
574594
575595
576596class IndentedBlockParser (BlockParser , ABC ):
577597 _is_block_beginning : bool
578598 _block_indent_size : Union [int , None ]
599+ should_measure_indent = False
579600
580601 def __init__ (self ):
581602 super (IndentedBlockParser , self ).__init__ ()
@@ -599,6 +620,7 @@ def consume(self, line: str):
599620 return
600621 if self ._block_indent_size is None :
601622 self ._block_indent_size = len (line ) - len (line .lstrip ())
623+ self .measure_indent (line )
602624 super ().consume (line [self ._block_indent_size :])
603625
604626 def finish_consumption (self , final : bool ) -> str :
@@ -684,6 +706,7 @@ def can_parse(self, line: str):
684706 return line .strip () in self .directives
685707
686708 def initiate_parsing (self , line : str , current_language : str ):
709+ self .measure_indent (line )
687710 admonition = self .directives [line .strip ()]
688711 self ._start_block (f'\n { admonition .block_markdown } \n ' )
689712 return IBlockBeginning (remainder = '' )
@@ -694,6 +717,7 @@ def can_parse(self, line: str) -> bool:
694717 return re .match (CODE_BLOCK_PATTERN , line ) is not None
695718
696719 def initiate_parsing (self , line : str , current_language : str ) -> IBlockBeginning :
720+ self .measure_indent (line )
697721 match = re .match (CODE_BLOCK_PATTERN , line )
698722 # already checked in can_parse
699723 assert match
@@ -753,6 +777,8 @@ def rst_to_markdown(text: str, extract_signature: bool = True) -> str:
753777 most_recent_section : Union [str , None ] = None
754778 is_first_line = True
755779
780+ text = dedent (text )
781+
756782 def flush_buffer ():
757783 nonlocal lines_buffer
758784 lines = '\n ' .join (lines_buffer )
@@ -766,7 +792,8 @@ def flush_buffer():
766792 lines_buffer = []
767793 return lines
768794
769- for line in text .split ('\n ' ):
795+ lines = text .split ('\n ' )
796+ for i , line in enumerate (lines ):
770797 if is_first_line :
771798 if extract_signature :
772799 signature_match = re .match (r'^(?P<name>\S+)\((?P<params>.*)\)$' , line )
@@ -809,7 +836,9 @@ def flush_buffer():
809836 else :
810837 if most_recent_section in SECTION_DIRECTIVES :
811838 for section_directive in SECTION_DIRECTIVES [most_recent_section ]:
812- if re .match (section_directive .pattern , trimmed_line ):
839+ next_line = lines [i + 1 ] if i + 1 < len (lines ) else ""
840+ is_next_line_section = set (next_line .strip ()) == {"-" }
841+ if re .match (section_directive .pattern , line ) and not is_next_line_section :
813842 line = re .sub (section_directive .pattern , section_directive .replacement , trimmed_line )
814843 break
815844 if trimmed_line .rstrip () in RST_SECTIONS :
0 commit comments