Skip to content

Commit 18831d1

Browse files
committed
Fix: rich-argparse help text truncation
Created Cmd2RichArgparseConsole, which doesn't enable soft wrapping. This resolves a conflict with the rich-argparse formatter's explicit no_wrap and overflow settings.
1 parent 13ddf69 commit 18831d1

File tree

5 files changed

+90
-38
lines changed

5 files changed

+90
-38
lines changed

cmd2/argparse_completer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from .constants import (
2020
INFINITY,
2121
)
22-
from .rich_utils import Cmd2Console
22+
from .rich_utils import Cmd2GeneralConsole
2323

2424
if TYPE_CHECKING: # pragma: no cover
2525
from .cmd2 import (
@@ -590,7 +590,7 @@ def _format_completions(self, arg_state: _ArgumentState, completions: list[str]
590590
hint_table.add_row(item, *item.descriptive_data)
591591

592592
# Generate the hint table string
593-
console = Cmd2Console()
593+
console = Cmd2GeneralConsole()
594594
with console.capture() as capture:
595595
console.print(hint_table, end="")
596596
self._cmd2_app.formatted_completions = capture.get()

cmd2/argparse_custom.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ def get_items(self) -> list[CompletionItems]:
295295
)
296296

297297
from . import constants
298-
from .rich_utils import Cmd2Console
298+
from .rich_utils import Cmd2RichArgparseConsole
299299
from .styles import Cmd2Style
300300

301301
if TYPE_CHECKING: # pragma: no cover
@@ -1113,12 +1113,12 @@ def __init__(
11131113
max_help_position: int = 24,
11141114
width: int | None = None,
11151115
*,
1116-
console: Cmd2Console | None = None,
1116+
console: Cmd2RichArgparseConsole | None = None,
11171117
**kwargs: Any,
11181118
) -> None:
11191119
"""Initialize Cmd2HelpFormatter."""
11201120
if console is None:
1121-
console = Cmd2Console()
1121+
console = Cmd2RichArgparseConsole()
11221122

11231123
super().__init__(prog, indent_increment, max_help_position, width, console=console, **kwargs)
11241124

@@ -1481,7 +1481,7 @@ def error(self, message: str) -> NoReturn:
14811481
# Add error style to message
14821482
console = self._get_formatter().console
14831483
with console.capture() as capture:
1484-
console.print(formatted_message, style=Cmd2Style.ERROR)
1484+
console.print(formatted_message, style=Cmd2Style.ERROR, crop=False)
14851485
formatted_message = f"{capture.get()}"
14861486

14871487
self.exit(2, f'{formatted_message}\n')

cmd2/cmd2.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@
130130
shlex_split,
131131
)
132132
from .rich_utils import (
133-
Cmd2Console,
133+
Cmd2GeneralConsole,
134134
RichPrintKwargs,
135135
)
136136
from .styles import Cmd2Style
@@ -161,7 +161,7 @@
161161

162162
# Set up readline
163163
if rl_type == RlType.NONE: # pragma: no cover
164-
Cmd2Console(sys.stderr).print(rl_warning, style=Cmd2Style.WARNING)
164+
Cmd2GeneralConsole(sys.stderr).print(rl_warning, style=Cmd2Style.WARNING)
165165
else:
166166
from .rl_utils import (
167167
readline,
@@ -1221,7 +1221,7 @@ def print_to(
12211221
terminal width; instead, any text that doesn't fit will run onto the following line(s),
12221222
similar to the built-in print() function. Set to False to enable automatic word-wrapping.
12231223
If None (the default for this parameter), the output will default to no word-wrapping, as
1224-
configured by the Cmd2Console.
1224+
configured by the Cmd2GeneralConsole.
12251225
:param rich_print_kwargs: optional additional keyword arguments to pass to Rich's Console.print().
12261226
:param kwargs: Arbitrary keyword arguments. This allows subclasses to extend the signature of this
12271227
method and still call `super()` without encountering unexpected keyword argument errors.
@@ -1230,7 +1230,7 @@ def print_to(
12301230
prepared_objects = ru.prepare_objects_for_rich_print(*objects)
12311231

12321232
try:
1233-
Cmd2Console(file).print(
1233+
Cmd2GeneralConsole(file).print(
12341234
*prepared_objects,
12351235
sep=sep,
12361236
end=end,
@@ -1245,7 +1245,7 @@ def print_to(
12451245
# warning message, then set the broken_pipe_warning attribute
12461246
# to the message you want printed.
12471247
if self.broken_pipe_warning and file != sys.stderr:
1248-
Cmd2Console(sys.stderr).print(self.broken_pipe_warning)
1248+
Cmd2GeneralConsole(sys.stderr).print(self.broken_pipe_warning)
12491249

12501250
def poutput(
12511251
self,
@@ -1267,7 +1267,7 @@ def poutput(
12671267
terminal width; instead, any text that doesn't fit will run onto the following line(s),
12681268
similar to the built-in print() function. Set to False to enable automatic word-wrapping.
12691269
If None (the default for this parameter), the output will default to no word-wrapping, as
1270-
configured by the Cmd2Console.
1270+
configured by the Cmd2GeneralConsole.
12711271
:param rich_print_kwargs: optional additional keyword arguments to pass to Rich's Console.print().
12721272
:param kwargs: Arbitrary keyword arguments. This allows subclasses to extend the signature of this
12731273
method and still call `super()` without encountering unexpected keyword argument errors.
@@ -1303,7 +1303,7 @@ def perror(
13031303
terminal width; instead, any text that doesn't fit will run onto the following line(s),
13041304
similar to the built-in print() function. Set to False to enable automatic word-wrapping.
13051305
If None (the default for this parameter), the output will default to no word-wrapping, as
1306-
configured by the Cmd2Console.
1306+
configured by the Cmd2GeneralConsole.
13071307
:param rich_print_kwargs: optional additional keyword arguments to pass to Rich's Console.print().
13081308
:param kwargs: Arbitrary keyword arguments. This allows subclasses to extend the signature of this
13091309
method and still call `super()` without encountering unexpected keyword argument errors.
@@ -1337,7 +1337,7 @@ def psuccess(
13371337
terminal width; instead, any text that doesn't fit will run onto the following line(s),
13381338
similar to the built-in print() function. Set to False to enable automatic word-wrapping.
13391339
If None (the default for this parameter), the output will default to no word-wrapping, as
1340-
configured by the Cmd2Console.
1340+
configured by the Cmd2GeneralConsole.
13411341
:param rich_print_kwargs: optional additional keyword arguments to pass to Rich's Console.print().
13421342
:param kwargs: Arbitrary keyword arguments. This allows subclasses to extend the signature of this
13431343
method and still call `super()` without encountering unexpected keyword argument errors.
@@ -1370,7 +1370,7 @@ def pwarning(
13701370
terminal width; instead, any text that doesn't fit will run onto the following line(s),
13711371
similar to the built-in print() function. Set to False to enable automatic word-wrapping.
13721372
If None (the default for this parameter), the output will default to no word-wrapping, as
1373-
configured by the Cmd2Console.
1373+
configured by the Cmd2GeneralConsole.
13741374
:param rich_print_kwargs: optional additional keyword arguments to pass to Rich's Console.print().
13751375
:param kwargs: Arbitrary keyword arguments. This allows subclasses to extend the signature of this
13761376
method and still call `super()` without encountering unexpected keyword argument errors.
@@ -1404,7 +1404,7 @@ def pexcept(
14041404
final_msg = Text()
14051405

14061406
if self.debug and sys.exc_info() != (None, None, None):
1407-
console = Cmd2Console(sys.stderr)
1407+
console = Cmd2GeneralConsole(sys.stderr)
14081408
console.print_exception(word_wrap=True)
14091409
else:
14101410
final_msg += f"EXCEPTION of type '{type(exception).__name__}' occurred with message: {exception}"
@@ -1442,7 +1442,7 @@ def pfeedback(
14421442
terminal width; instead, any text that doesn't fit will run onto the following line(s),
14431443
similar to the built-in print() function. Set to False to enable automatic word-wrapping.
14441444
If None (the default for this parameter), the output will default to no word-wrapping, as
1445-
configured by the Cmd2Console.
1445+
configured by the Cmd2GeneralConsole.
14461446
:param rich_print_kwargs: optional additional keyword arguments to pass to Rich's Console.print().
14471447
:param kwargs: Arbitrary keyword arguments. This allows subclasses to extend the signature of this
14481448
method and still call `super()` without encountering unexpected keyword argument errors.
@@ -1498,7 +1498,7 @@ def ppaged(
14981498
terminal width; instead, any text that doesn't fit will run onto the following line(s),
14991499
similar to the built-in print() function. Set to False to enable automatic word-wrapping.
15001500
If None (the default for this parameter), the output will default to no word-wrapping, as
1501-
configured by the Cmd2Console.
1501+
configured by the Cmd2GeneralConsole.
15021502
Note: If chop is True and a pager is used, soft_wrap is automatically set to True.
15031503
:param rich_print_kwargs: optional additional keyword arguments to pass to Rich's Console.print().
15041504
:param kwargs: Arbitrary keyword arguments. This allows subclasses to extend the signature of this
@@ -1525,7 +1525,7 @@ def ppaged(
15251525
soft_wrap = True
15261526

15271527
# Generate the bytes to send to the pager
1528-
console = Cmd2Console(self.stdout)
1528+
console = Cmd2GeneralConsole(self.stdout)
15291529
with console.capture() as capture:
15301530
console.print(
15311531
*prepared_objects,

cmd2/rich_utils.py

Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@
1616
RenderableType,
1717
RichCast,
1818
)
19-
from rich.style import (
20-
StyleType,
21-
)
19+
from rich.style import StyleType
2220
from rich.text import Text
2321
from rich.theme import Theme
2422
from rich_argparse import RichHelpFormatter
@@ -108,14 +106,33 @@ class RichPrintKwargs(TypedDict, total=False):
108106
new_line_start: bool
109107

110108

111-
class Cmd2Console(Console):
112-
"""Rich console with characteristics appropriate for cmd2-based applications."""
109+
class Cmd2BaseConsole(Console):
110+
"""A base class for Rich consoles in cmd2-based applications."""
113111

114-
def __init__(self, file: IO[str] | None = None) -> None:
115-
"""Cmd2Console initializer.
112+
def __init__(self, file: IO[str] | None = None, **kwargs: Any) -> None:
113+
"""Cmd2BaseConsole initializer.
116114
117-
:param file: Optional file object where the console should write to. Defaults to sys.stdout.
115+
:param file: optional file object where the console should write to. Defaults to sys.stdout.
118116
"""
117+
# Don't allow force_terminal or force_interactive to be passed in, as their
118+
# behavior is controlled by the ALLOW_STYLE setting.
119+
if "force_terminal" in kwargs:
120+
raise TypeError(
121+
"Passing 'force_terminal' is not allowed. Its behavior is controlled by the 'ALLOW_STYLE' setting."
122+
)
123+
if "force_interactive" in kwargs:
124+
raise TypeError(
125+
"Passing 'force_interactive' is not allowed. Its behavior is controlled by the 'ALLOW_STYLE' setting."
126+
)
127+
128+
# Don't allow a theme to be passed in, as it is controlled by the global APP_THEME.
129+
# Use cmd2.rich_utils.set_theme() to set the global theme or use a temporary
130+
# theme with console.use_theme().
131+
if "theme" in kwargs:
132+
raise TypeError(
133+
"Passing 'theme' is not allowed. Its behavior is controlled by the global APP_THEME and set_theme()."
134+
)
135+
119136
force_terminal: bool | None = None
120137
force_interactive: bool | None = None
121138

@@ -128,20 +145,12 @@ def __init__(self, file: IO[str] | None = None) -> None:
128145
elif ALLOW_STYLE == AllowStyle.NEVER:
129146
force_terminal = False
130147

131-
# The console's defaults are configured to handle pre-formatted text. It enables soft wrap,
132-
# which prevents automatic word-wrapping, and disables Rich's automatic processing for
133-
# markup, emoji, and highlighting. While these features are off by default, the console
134-
# can still fully render explicit Rich objects like Panels and Tables. These defaults can
135-
# be overridden in calls to Cmd2Console.print() and cmd2's print methods.
136148
super().__init__(
137149
file=file,
138150
force_terminal=force_terminal,
139151
force_interactive=force_interactive,
140-
soft_wrap=True,
141-
markup=False,
142-
emoji=False,
143-
highlight=False,
144152
theme=APP_THEME,
153+
**kwargs,
145154
)
146155

147156
def on_broken_pipe(self) -> None:
@@ -150,9 +159,37 @@ def on_broken_pipe(self) -> None:
150159
raise BrokenPipeError
151160

152161

162+
class Cmd2GeneralConsole(Cmd2BaseConsole):
163+
"""Rich console for general-purpose printing in cmd2-based applications."""
164+
165+
def __init__(self, file: IO[str] | None = None) -> None:
166+
"""Cmd2GeneralConsole initializer.
167+
168+
:param file: optional file object where the console should write to. Defaults to sys.stdout.
169+
"""
170+
# This console is configured for general-purpose printing. It enables soft wrap
171+
# and disables Rich's automatic processing for markup, emoji, and highlighting.
172+
# These defaults can be overridden in calls to the console's or cmd2's print methods.
173+
super().__init__(
174+
file=file,
175+
soft_wrap=True,
176+
markup=False,
177+
emoji=False,
178+
highlight=False,
179+
)
180+
181+
182+
class Cmd2RichArgparseConsole(Cmd2BaseConsole):
183+
"""Rich console for rich-argparse output in cmd2-based applications.
184+
185+
This class ensures long lines in help text are not truncated by avoiding soft_wrap,
186+
which conflicts with rich-argparse's explicit no_wrap and overflow settings.
187+
"""
188+
189+
153190
def console_width() -> int:
154191
"""Return the width of the console."""
155-
return Cmd2Console().width
192+
return Cmd2BaseConsole().width
156193

157194

158195
def rich_text_to_string(text: Text) -> str:

tests/test_rich_utils.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,21 @@
1212
from cmd2 import string_utils as su
1313

1414

15+
def test_cmd2_base_console() -> None:
16+
# Test the keyword arguments which are not allowed.
17+
with pytest.raises(TypeError) as excinfo:
18+
ru.Cmd2BaseConsole(force_terminal=True)
19+
assert 'force_terminal' in str(excinfo.value)
20+
21+
with pytest.raises(TypeError) as excinfo:
22+
ru.Cmd2BaseConsole(force_interactive=True)
23+
assert 'force_interactive' in str(excinfo.value)
24+
25+
with pytest.raises(TypeError) as excinfo:
26+
ru.Cmd2BaseConsole(theme=None)
27+
assert 'theme' in str(excinfo.value)
28+
29+
1530
def test_string_to_rich_text() -> None:
1631
# Line breaks recognized by str.splitlines().
1732
# Source: https://docs.python.org/3/library/stdtypes.html#str.splitlines
@@ -56,7 +71,7 @@ def test_rich_text_to_string(rich_text: Text, string: str) -> None:
5671
assert ru.rich_text_to_string(rich_text) == string
5772

5873

59-
def test_set_style() -> None:
74+
def test_set_theme() -> None:
6075
# Save a cmd2, rich-argparse, and rich-specific style.
6176
cmd2_style_key = Cmd2Style.ERROR
6277
argparse_style_key = "argparse.args"

0 commit comments

Comments
 (0)