1717from typing import Iterable , Optional
1818
1919
20+ NUMBERED_LIST_REGEX = r"^\d+\. "
21+
22+
2023def sort_lines (text : str , dedupe : bool = True ) -> str :
2124 """Sort the individual lines of a block of text.
2225
@@ -40,6 +43,49 @@ def sort_lines(text: str, dedupe: bool = True) -> str:
4043 return f'{ leading } { answer } { trailing } '
4144
4245
46+ def get_subsequent_line_indentation_level (list_item : str ) -> int :
47+ """
48+ Given a list item return the indentation level for subsequent lines.
49+ For example, if it is a numbered list, the indentation level should be 3
50+ as shown below.
51+
52+ Here subsequent lines should be indented by 2
53+
54+ - The quick brown fox jumps over the lazy dog. The quick brown fox jumps
55+ over the lazy dog
56+
57+ Here subsequent lines should be indented by 2
58+
59+ + The quick brown fox jumps over the lazy dog. The quick brown fox jumps
60+ over the lazy dog
61+
62+ Here subsequent lines should be indented by 4 to cater for double digits
63+
64+ 1. The quick brown fox jumps over the lazy dog. The quick brown fox jumps
65+ over the lazy dog
66+
67+ 22. The quick brown fox jumps over the lazy dog. The quick brown fox jumps
68+ over the lazy dog
69+ """
70+ if len (list_item ) >= 2 and list_item [0 :2 ] in ['- ' , '+ ' ]:
71+ indentation_level = 2
72+ elif len (list_item ) >= 4 and re .match (NUMBERED_LIST_REGEX , list_item ):
73+ indentation_level = 4
74+ else :
75+ # Don't use any intentation level if the list item marker is not known
76+ indentation_level = 0
77+ return indentation_level
78+
79+
80+ def is_list_item (list_item : str ) -> bool :
81+ """
82+ Given a string return a boolean indicating whether a list is identified.
83+ """
84+ if len (list_item ) < 3 :
85+ return False
86+ return list_item .startswith ('- ' ) or list_item .startswith ('+ ' ) or bool (re .match (NUMBERED_LIST_REGEX , list_item ))
87+
88+
4389def wrap (text : str , width : int , * , offset : Optional [int ] = None , indent : int = 0 ) -> str :
4490 """Wrap the given string to the given width.
4591
@@ -93,11 +139,12 @@ def wrap(text: str, width: int, *, offset: Optional[int] = None, indent: int = 0
93139 break_on_hyphens = False ,
94140 )
95141 # Strip the first \n from the text so it is not misidentified as an
96- # intentionally short line below, except when the text contains `:`
97- # as the new line is required for lists.
142+ # intentionally short line below, except when the text contains a list,
143+ # as the new line is required for lists. Look for a list item marker in
144+ # the remaining text which indicates that a list is present.
98145 if '\n ' in text :
99- initial_text = text .split ('\n ' )[0 ]
100- if ":" not in initial_text :
146+ remaining_text = "" . join ( text .split ('\n ' )[1 :])
147+ if not is_list_item ( remaining_text . strip ()) :
101148 text = text .replace ('\n ' , ' ' , 1 )
102149
103150 # Save the new `first` line.
@@ -121,9 +168,9 @@ def wrap(text: str, width: int, *, offset: Optional[int] = None, indent: int = 0
121168 tokens = []
122169 token = ''
123170 for line in text .split ('\n ' ):
124- # Ensure that lines that start with a hyphen are always on a new line
171+ # Ensure that lines that start with a list item marker are always on a new line
125172 # Ensure that blank lines are preserved
126- if (line .strip (). startswith ( '-' ) or not len (line )) and token :
173+ if (is_list_item ( line .strip ()) or not len (line )) and token :
127174 tokens .append (token )
128175 token = ''
129176 token += line + '\n '
@@ -145,7 +192,7 @@ def wrap(text: str, width: int, *, offset: Optional[int] = None, indent: int = 0
145192 initial_indent = ' ' * indent ,
146193 # ensure that subsequent lines for lists are indented 2 spaces
147194 subsequent_indent = ' ' * indent + \
148- ( ' ' if token .strip (). startswith ( '-' ) else '' ),
195+ ' ' * get_subsequent_line_indentation_level ( token .strip ()),
149196 text = token ,
150197 width = width ,
151198 break_on_hyphens = False ,
0 commit comments