Skip to content

Commit fefb01e

Browse files
authored
Added regular expression to detect ANSI style sequences. (#1492)
1 parent 378e825 commit fefb01e

File tree

4 files changed

+25
-24
lines changed

4 files changed

+25
-24
lines changed

cmd2/argparse_custom.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ def __init__(self, value: object, descriptive_data: Sequence[Any], *args: Any) -
398398
# Make sure all objects are renderable by a Rich table.
399399
renderable_data = [obj if is_renderable(obj) else str(obj) for obj in descriptive_data]
400400

401-
# Convert objects with ANSI styles to Rich Text for correct display width.
401+
# Convert strings containing ANSI style sequences to Rich Text objects for correct display width.
402402
self.descriptive_data = ru.prepare_objects_for_rendering(*renderable_data)
403403

404404
# Save the original value to support CompletionItems as argparse choices.

cmd2/cmd2.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1191,12 +1191,12 @@ def _completion_supported(self) -> bool:
11911191

11921192
@property
11931193
def visible_prompt(self) -> str:
1194-
"""Read-only property to get the visible prompt with any ANSI style escape codes stripped.
1194+
"""Read-only property to get the visible prompt with any ANSI style sequences stripped.
11951195
1196-
Used by transcript testing to make it easier and more reliable when users are doing things like coloring the
1197-
prompt using ANSI color codes.
1196+
Used by transcript testing to make it easier and more reliable when users are doing things like
1197+
coloring the prompt.
11981198
1199-
:return: prompt stripped of any ANSI escape codes
1199+
:return: the stripped prompt
12001200
"""
12011201
return su.strip_style(self.prompt)
12021202

@@ -4214,7 +4214,7 @@ def _print_documented_command_topics(self, header: str, cmds: list[str], verbose
42144214
def render_columns(self, str_list: list[str] | None, display_width: int = 80) -> str:
42154215
"""Render a list of single-line strings as a compact set of columns.
42164216
4217-
This method correctly handles strings containing ANSI escape codes and
4217+
This method correctly handles strings containing ANSI style sequences and
42184218
full-width characters (like those used in CJK languages). Each column is
42194219
only as wide as necessary and columns are separated by two spaces.
42204220

cmd2/rich_utils.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Provides common utilities to support Rich in cmd2-based applications."""
22

3+
import re
34
from collections.abc import Mapping
45
from enum import Enum
56
from typing import (
@@ -28,13 +29,16 @@
2829

2930
from .styles import DEFAULT_CMD2_STYLES
3031

32+
# A compiled regular expression to detect ANSI style sequences.
33+
ANSI_STYLE_SEQUENCE_RE = re.compile(r"\x1b\[[0-9;?]*m")
34+
3135

3236
class AllowStyle(Enum):
3337
"""Values for ``cmd2.rich_utils.ALLOW_STYLE``."""
3438

35-
ALWAYS = 'Always' # Always output ANSI style sequences
36-
NEVER = 'Never' # Remove ANSI style sequences from all output
37-
TERMINAL = 'Terminal' # Remove ANSI style sequences if the output is not going to the terminal
39+
ALWAYS = "Always" # Always output ANSI style sequences
40+
NEVER = "Never" # Remove ANSI style sequences from all output
41+
TERMINAL = "Terminal" # Remove ANSI style sequences if the output is not going to the terminal
3842

3943
def __str__(self) -> str:
4044
"""Return value instead of enum name for printing in cmd2's set command."""
@@ -234,7 +238,7 @@ def rich_text_to_string(text: Text) -> str:
234238
"""Convert a Rich Text object to a string.
235239
236240
This function's purpose is to render a Rich Text object, including any styles (e.g., color, bold),
237-
to a plain Python string with ANSI escape codes. It differs from `text.plain`, which strips
241+
to a plain Python string with ANSI style sequences. It differs from `text.plain`, which strips
238242
all formatting.
239243
240244
:param text: the text object to convert
@@ -259,7 +263,7 @@ def rich_text_to_string(text: Text) -> str:
259263

260264

261265
def string_to_rich_text(text: str) -> Text:
262-
r"""Create a Text object from a string which can contain ANSI escape codes.
266+
r"""Create a Rich Text object from a string which can contain ANSI style sequences.
263267
264268
This wraps rich.Text.from_ansi() to handle an issue where it removes the
265269
trailing line break from a string (e.g. "Hello\n" becomes "Hello").
@@ -323,9 +327,9 @@ def prepare_objects_for_rendering(*objects: Any) -> tuple[Any, ...]:
323327
"""Prepare a tuple of objects for printing by Rich's Console.print().
324328
325329
This function converts any non-Rich object whose string representation contains
326-
ANSI style codes into a rich.Text object. This ensures correct display width
327-
calculation, as Rich can then properly parse and account for the non-printing
328-
ANSI codes. All other objects are left untouched, allowing Rich's native
330+
ANSI style sequences into a Rich Text object. This ensures correct display width
331+
calculation, as Rich can then properly parse and account for these non-printing
332+
codes. All other objects are left untouched, allowing Rich's native
329333
renderers to handle them.
330334
331335
:param objects: objects to prepare
@@ -342,12 +346,10 @@ def prepare_objects_for_rendering(*objects: Any) -> tuple[Any, ...]:
342346
if isinstance(renderable, ConsoleRenderable):
343347
continue
344348

345-
# Check if the object's string representation contains ANSI styles, and if so,
346-
# replace it with a Rich Text object for correct width calculation.
347349
renderable_as_str = str(renderable)
348-
renderable_as_text = string_to_rich_text(renderable_as_str)
349350

350-
if renderable_as_text.plain != renderable_as_str:
351-
object_list[i] = renderable_as_text
351+
# Check for any ANSI style sequences in the string.
352+
if ANSI_STYLE_SEQUENCE_RE.search(renderable_as_str):
353+
object_list[i] = string_to_rich_text(renderable_as_str)
352354

353355
return tuple(object_list)

cmd2/string_utils.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Provides string utility functions.
22
33
This module offers a collection of string utility functions built on the Rich library.
4-
These utilities are designed to correctly handle strings with ANSI escape codes and
4+
These utilities are designed to correctly handle strings with ANSI style sequences and
55
full-width characters (like those used in CJK languages).
66
"""
77

@@ -94,13 +94,12 @@ def stylize(val: str, style: StyleType) -> str:
9494

9595

9696
def strip_style(val: str) -> str:
97-
"""Strip all ANSI styles from a string.
97+
"""Strip all ANSI style sequences from a string.
9898
9999
:param val: string to be stripped
100100
:return: the stripped string
101101
"""
102-
text = ru.string_to_rich_text(val)
103-
return text.plain
102+
return ru.ANSI_STYLE_SEQUENCE_RE.sub("", val)
104103

105104

106105
def str_width(val: str) -> int:
@@ -163,4 +162,4 @@ def norm_fold(val: str) -> str:
163162
"""
164163
import unicodedata
165164

166-
return unicodedata.normalize('NFC', val).casefold()
165+
return unicodedata.normalize("NFC", val).casefold()

0 commit comments

Comments
 (0)