Skip to content

Commit a2e824d

Browse files
committed
Added ability to pass a console object to Cmd.print_to().
1 parent 6c48851 commit a2e824d

File tree

3 files changed

+72
-15
lines changed

3 files changed

+72
-15
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
# 3.2.3 (TBD)
2+
3+
- Enhancements
4+
- Added ability to pass a console object to `Cmd.print_to()`. This provides support for
5+
things like wrapping a `print_to()` call in a `console.status()` or `console.capture()`
6+
context manager.
7+
18
## 3.2.2 (February 21, 2026)
29

310
- Bug Fixes

cmd2/cmd2.py

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,11 @@
6565
)
6666

6767
import rich.box
68-
from rich.console import Group, RenderableType
68+
from rich.console import (
69+
Console,
70+
Group,
71+
RenderableType,
72+
)
6973
from rich.highlighter import ReprHighlighter
7074
from rich.rule import Rule
7175
from rich.style import Style, StyleType
@@ -133,6 +137,7 @@
133137
shlex_split,
134138
)
135139
from .rich_utils import (
140+
Cmd2BaseConsole,
136141
Cmd2ExceptionConsole,
137142
Cmd2GeneralConsole,
138143
RichPrintKwargs,
@@ -1247,7 +1252,7 @@ def visible_prompt(self) -> str:
12471252

12481253
def print_to(
12491254
self,
1250-
file: IO[str],
1255+
destination: IO[str] | Cmd2BaseConsole,
12511256
*objects: Any,
12521257
sep: str = " ",
12531258
end: str = "\n",
@@ -1259,13 +1264,30 @@ def print_to(
12591264
rich_print_kwargs: RichPrintKwargs | None = None,
12601265
**kwargs: Any, # noqa: ARG002
12611266
) -> None:
1262-
"""Print objects to a given file stream.
1267+
"""Print objects to a given destination (file stream or cmd2 console).
12631268
12641269
This method is configured for general-purpose printing. By default, it enables
12651270
soft wrap and disables Rich's automatic detection for markup, emoji, and highlighting.
12661271
These defaults can be overridden by passing explicit keyword arguments.
12671272
1268-
:param file: file stream being written to
1273+
See the Rich documentation for more details on emoji codes, markup tags, and highlighting.
1274+
1275+
!!! note
1276+
1277+
To ensure consistent behavior, this method requires a file-like object or
1278+
an instance of ``Cmd2BaseConsole``.
1279+
Consoles not derived from ``Cmd2BaseConsole`` are disallowed because:
1280+
1281+
1. **Style Control**: They ignore the global ``ALLOW_STYLE`` setting.
1282+
2. **Theming**: They do not respect the application-wide ``APP_THEME``.
1283+
3. **Error Handling**: They trigger a ``SystemExit`` on broken pipes.
1284+
``Cmd2BaseConsole`` instead raises a catchable ``BrokenPipeError``,
1285+
ensuring the CLI application remains alive if a pipe is closed.
1286+
1287+
:param destination: The output target. File-like objects are automatically
1288+
wrapped in a ``Cmd2GeneralConsole`` to ensure they respect
1289+
cmd2 global settings; otherwise, this must be an
1290+
instance of ``Cmd2BaseConsole``.
12691291
:param objects: objects to print
12701292
:param sep: string to write between printed text. Defaults to " ".
12711293
:param end: string to write at end of printed text. Defaults to a newline.
@@ -1290,13 +1312,28 @@ def print_to(
12901312
:param kwargs: Arbitrary keyword arguments. This allows subclasses to extend the signature of this
12911313
method and still call `super()` without encountering unexpected keyword argument errors.
12921314
These arguments are not passed to Rich's Console.print().
1293-
1294-
See the Rich documentation for more details on emoji codes, markup tags, and highlighting.
1315+
:raises TypeError: If ``destination`` is a non-cmd2 ``Console`` instance that
1316+
does not derive from ``Cmd2BaseConsole``.
12951317
"""
1318+
if isinstance(destination, Console):
1319+
if not isinstance(destination, Cmd2BaseConsole):
1320+
# Explicitly reject non-cmd2 consoles to ensure safe behavior
1321+
raise TypeError(
1322+
f"destination must be a 'Cmd2BaseConsole' or a file-like object, "
1323+
f"not a non-cmd2 '{type(destination).__name__}'. "
1324+
"Consoles not derived from 'Cmd2BaseConsole' bypass cmd2's "
1325+
"'ALLOW_STYLE' logic, 'APP_THEME' settings, and trigger 'SystemExit' "
1326+
"on broken pipes."
1327+
)
1328+
console = destination
1329+
else:
1330+
# It's a file-like object (e.g., sys.stdout, StringIO)
1331+
console = Cmd2GeneralConsole(destination)
1332+
12961333
prepared_objects = ru.prepare_objects_for_rendering(*objects)
12971334

12981335
try:
1299-
Cmd2GeneralConsole(file).print(
1336+
console.print(
13001337
*prepared_objects,
13011338
sep=sep,
13021339
end=end,
@@ -1313,7 +1350,7 @@ def print_to(
13131350
# writing. If you would like your application to print a
13141351
# warning message, then set the broken_pipe_warning attribute
13151352
# to the message you want printed.
1316-
if self.broken_pipe_warning and file != sys.stderr:
1353+
if self.broken_pipe_warning and console.file != sys.stderr:
13171354
Cmd2GeneralConsole(sys.stderr).print(self.broken_pipe_warning)
13181355

13191356
def poutput(
@@ -1581,17 +1618,16 @@ def ppaged(
15811618

15821619
# Check if we are outputting to a pager.
15831620
if functional_terminal and can_block:
1584-
prepared_objects = ru.prepare_objects_for_rendering(*objects)
1585-
15861621
# Chopping overrides soft_wrap
15871622
if chop:
15881623
soft_wrap = True
15891624

15901625
# Generate the bytes to send to the pager
15911626
console = Cmd2GeneralConsole(self.stdout)
15921627
with console.capture() as capture:
1593-
console.print(
1594-
*prepared_objects,
1628+
self.print_to(
1629+
console,
1630+
*objects,
15951631
sep=sep,
15961632
end=end,
15971633
style=style,

tests/test_cmd2.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@
66
import signal
77
import sys
88
import tempfile
9-
from code import (
10-
InteractiveConsole,
11-
)
9+
from code import InteractiveConsole
1210
from typing import NoReturn
1311
from unittest import mock
1412

@@ -2130,6 +2128,22 @@ def test_read_command_line_eof(base_app, monkeypatch) -> None:
21302128
assert line == 'eof'
21312129

21322130

2131+
def test_print_to_custom_console(base_app) -> None:
2132+
console = ru.Cmd2GeneralConsole()
2133+
with console.capture() as capture:
2134+
base_app.print_to(console, "hello")
2135+
assert capture.get() == "hello\n"
2136+
2137+
2138+
def test_print_to_invalid_console_type(base_app) -> None:
2139+
from rich.console import Console
2140+
2141+
console = Console()
2142+
with pytest.raises(TypeError) as excinfo:
2143+
base_app.print_to(console, "hello")
2144+
assert "destination must be a 'Cmd2BaseConsole'" in str(excinfo.value)
2145+
2146+
21332147
def test_poutput_string(outsim_app) -> None:
21342148
msg = 'This is a test'
21352149
outsim_app.poutput(msg)

0 commit comments

Comments
 (0)