Skip to content

Commit 0873b4d

Browse files
committed
Allowing for colored fill char in align_text
Added function to index all style sequences found in a string
1 parent f72e1dd commit 0873b4d

File tree

2 files changed

+44
-15
lines changed

2 files changed

+44
-15
lines changed

cmd2/utils.py

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import threading
1212
import unicodedata
1313
from enum import Enum
14-
from typing import Any, Callable, Iterable, List, Optional, TextIO, Union
14+
from typing import Any, Callable, Iterable, List, OrderedDict, Optional, TextIO, Union
1515

1616
from . import constants
1717

@@ -696,7 +696,7 @@ def align_text(text: str, alignment: TextAlignment, *, fill_char: str = ' ',
696696
:param truncate: if True, then each line will be shortened to fit within the display width. The truncated
697697
portions are replaced by a '…' character. Defaults to False.
698698
:return: aligned text
699-
:raises: TypeError if fill_char is more than one character
699+
:raises: TypeError if fill_char is more than one character (not including ANSI style sequences)
700700
ValueError if text or fill_char contains an unprintable character
701701
ValueError if width is less than 1
702702
"""
@@ -716,7 +716,7 @@ def align_text(text: str, alignment: TextAlignment, *, fill_char: str = ' ',
716716
if fill_char == '\t':
717717
fill_char = ' '
718718

719-
if len(fill_char) != 1:
719+
if len(ansi.strip_style(fill_char)) != 1:
720720
raise TypeError("Fill character must be exactly one character long")
721721

722722
fill_char_width = ansi.style_aware_wcswidth(fill_char)
@@ -788,7 +788,7 @@ def align_left(text: str, *, fill_char: str = ' ', width: Optional[int] = None,
788788
:param truncate: if True, then text will be shortened to fit within the display width. The truncated portion is
789789
replaced by a '…' character. Defaults to False.
790790
:return: left-aligned text
791-
:raises: TypeError if fill_char is more than one character
791+
:raises: TypeError if fill_char is more than one character (not including ANSI style sequences)
792792
ValueError if text or fill_char contains an unprintable character
793793
ValueError if width is less than 1
794794
"""
@@ -811,7 +811,7 @@ def align_center(text: str, *, fill_char: str = ' ', width: Optional[int] = None
811811
:param truncate: if True, then text will be shortened to fit within the display width. The truncated portion is
812812
replaced by a '…' character. Defaults to False.
813813
:return: centered text
814-
:raises: TypeError if fill_char is more than one character
814+
:raises: TypeError if fill_char is more than one character (not including ANSI style sequences)
815815
ValueError if text or fill_char contains an unprintable character
816816
ValueError if width is less than 1
817817
"""
@@ -834,7 +834,7 @@ def align_right(text: str, *, fill_char: str = ' ', width: Optional[int] = None,
834834
:param truncate: if True, then text will be shortened to fit within the display width. The truncated portion is
835835
replaced by a '…' character. Defaults to False.
836836
:return: right-aligned text
837-
:raises: TypeError if fill_char is more than one character
837+
:raises: TypeError if fill_char is more than one character (not including ANSI style sequences)
838838
ValueError if text or fill_char contains an unprintable character
839839
ValueError if width is less than 1
840840
"""
@@ -878,14 +878,7 @@ def truncate_line(line: str, max_width: int, *, tab_width: int = 4) -> str:
878878
return line
879879

880880
# Find all style sequences in the line
881-
start = 0
882-
styles = collections.OrderedDict()
883-
while True:
884-
match = ansi.ANSI_STYLE_RE.search(line, start)
885-
if match is None:
886-
break
887-
styles[match.start()] = match.group()
888-
start += len(match.group())
881+
styles = get_styles_in_text(line)
889882

890883
# Add characters one by one and preserve all style sequences
891884
done = False
@@ -919,3 +912,30 @@ def truncate_line(line: str, max_width: int, *, tab_width: int = 4) -> str:
919912
truncated_buf.write(''.join(styles.values()))
920913

921914
return truncated_buf.getvalue()
915+
916+
917+
def get_styles_in_text(text: str) -> OrderedDict[int, str]:
918+
"""
919+
Return an OrderedDict containing all ANSI style sequences found in a string
920+
921+
The structure of the dictionary is:
922+
key: index where sequences begins
923+
value: ANSI style sequence found at index in text
924+
925+
Keys are in ascending order
926+
927+
:param text: text to search for style sequences
928+
"""
929+
from . import ansi
930+
931+
start = 0
932+
styles = collections.OrderedDict()
933+
934+
while True:
935+
match = ansi.ANSI_STYLE_RE.search(text, start)
936+
if match is None:
937+
break
938+
styles[match.start()] = match.group()
939+
start += len(match.group())
940+
941+
return styles

tests/test_utils.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,15 @@ def test_align_text_fill_char_is_tab():
368368
aligned = cu.align_text(text, cu.TextAlignment.LEFT, fill_char=fill_char, width=width)
369369
assert aligned == text + ' '
370370

371+
def test_align_text_fill_char_has_color():
372+
from cmd2 import ansi
373+
374+
text = 'foo'
375+
fill_char = ansi.fg.bright_yellow + '-' + ansi.fg.reset
376+
width = 5
377+
aligned = cu.align_text(text, cu.TextAlignment.LEFT, fill_char=fill_char, width=width)
378+
assert aligned == text + fill_char * 2
379+
371380
def test_align_text_width_is_too_small():
372381
text = 'foo'
373382
fill_char = '-'
@@ -382,7 +391,7 @@ def test_align_text_fill_char_is_too_long():
382391
with pytest.raises(TypeError):
383392
cu.align_text(text, cu.TextAlignment.LEFT, fill_char=fill_char, width=width)
384393

385-
def test_align_text_fill_char_is_unprintable():
394+
def test_align_text_fill_char_is_newline():
386395
text = 'foo'
387396
fill_char = '\n'
388397
width = 5

0 commit comments

Comments
 (0)