1
1
from abc import ABC , abstractmethod
2
2
from enum import IntEnum , auto
3
+ from textwrap import dedent
3
4
from types import SimpleNamespace
4
5
from typing import Callable , Match , Union , List , Dict
5
6
import re
@@ -299,8 +300,8 @@ def inline_markdown(self):
299
300
SECTION_DIRECTIVES : Dict [str , List [Directive ]] = {
300
301
'Parameters' : [
301
302
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>`: '
304
305
),
305
306
Directive (
306
307
pattern = r'^(?P<arg1>[^:\s]+\d), (?P<arg2>[^:\s]+\d), \.\.\. : (?P<type>.+)$' ,
@@ -336,6 +337,7 @@ def _find_directive_pattern(name: str):
336
337
337
338
338
339
def looks_like_rst (value : str ) -> bool :
340
+ value = dedent (value )
339
341
# check if any of the characteristic sections (and the properly formatted underline) is there
340
342
for section in _RST_SECTIONS :
341
343
if (section + '\n ' + '-' * len (section ) + '\n ' ) in value :
@@ -542,10 +544,20 @@ class BlockParser(IParser):
542
544
follower : Union ['IParser' , None ] = None
543
545
_buffer : List [str ]
544
546
_block_started : bool
547
+ _indent : Union [int , None ]
548
+ should_measure_indent = True
545
549
546
550
def __init__ (self ):
547
551
self ._buffer = []
548
552
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 )
549
561
550
562
@abstractmethod
551
563
def can_parse (self , line : str ) -> bool :
@@ -558,24 +570,33 @@ def _start_block(self, language: str):
558
570
def consume (self , line : str ):
559
571
if not self ._block_started :
560
572
raise ValueError ('Block has not started' ) # pragma: no cover
573
+ if self .should_measure_indent :
574
+ self .measure_indent (line )
561
575
self ._buffer .append (line )
562
576
563
577
def finish_consumption (self , final : bool ) -> str :
564
578
# if the last line is empty (e.g. a separator of intended block), discard it
565
579
if self ._buffer [len (self ._buffer ) - 1 ].strip () == '' :
566
580
self ._buffer .pop ()
567
581
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
569
588
if not final :
570
589
result += '\n '
571
590
self ._buffer = []
572
591
self ._block_started = False
592
+ self ._indent = None
573
593
return result
574
594
575
595
576
596
class IndentedBlockParser (BlockParser , ABC ):
577
597
_is_block_beginning : bool
578
598
_block_indent_size : Union [int , None ]
599
+ should_measure_indent = False
579
600
580
601
def __init__ (self ):
581
602
super (IndentedBlockParser , self ).__init__ ()
@@ -599,6 +620,7 @@ def consume(self, line: str):
599
620
return
600
621
if self ._block_indent_size is None :
601
622
self ._block_indent_size = len (line ) - len (line .lstrip ())
623
+ self .measure_indent (line )
602
624
super ().consume (line [self ._block_indent_size :])
603
625
604
626
def finish_consumption (self , final : bool ) -> str :
@@ -684,6 +706,7 @@ def can_parse(self, line: str):
684
706
return line .strip () in self .directives
685
707
686
708
def initiate_parsing (self , line : str , current_language : str ):
709
+ self .measure_indent (line )
687
710
admonition = self .directives [line .strip ()]
688
711
self ._start_block (f'\n { admonition .block_markdown } \n ' )
689
712
return IBlockBeginning (remainder = '' )
@@ -694,6 +717,7 @@ def can_parse(self, line: str) -> bool:
694
717
return re .match (CODE_BLOCK_PATTERN , line ) is not None
695
718
696
719
def initiate_parsing (self , line : str , current_language : str ) -> IBlockBeginning :
720
+ self .measure_indent (line )
697
721
match = re .match (CODE_BLOCK_PATTERN , line )
698
722
# already checked in can_parse
699
723
assert match
@@ -753,6 +777,8 @@ def rst_to_markdown(text: str, extract_signature: bool = True) -> str:
753
777
most_recent_section : Union [str , None ] = None
754
778
is_first_line = True
755
779
780
+ text = dedent (text )
781
+
756
782
def flush_buffer ():
757
783
nonlocal lines_buffer
758
784
lines = '\n ' .join (lines_buffer )
@@ -766,7 +792,8 @@ def flush_buffer():
766
792
lines_buffer = []
767
793
return lines
768
794
769
- for line in text .split ('\n ' ):
795
+ lines = text .split ('\n ' )
796
+ for i , line in enumerate (lines ):
770
797
if is_first_line :
771
798
if extract_signature :
772
799
signature_match = re .match (r'^(?P<name>\S+)\((?P<params>.*)\)$' , line )
@@ -809,7 +836,9 @@ def flush_buffer():
809
836
else :
810
837
if most_recent_section in SECTION_DIRECTIVES :
811
838
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 :
813
842
line = re .sub (section_directive .pattern , section_directive .replacement , trimmed_line )
814
843
break
815
844
if trimmed_line .rstrip () in RST_SECTIONS :
0 commit comments