Skip to content

Commit 3a76fe6

Browse files
authored
Added Rich-based pretty print method. (#1617)
1 parent b8651b0 commit 3a76fe6

File tree

4 files changed

+100
-24
lines changed

4 files changed

+100
-24
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ prompt is displayed.
7979
- **pre_prompt**: hook method that is called before the prompt is displayed, but after
8080
`prompt-toolkit` event loop has started
8181
- **read_secret**: read secrets like passwords without displaying them to the terminal
82+
- **ppretty**: a cmd2-compatible replacement for `rich.pretty.pprint()`
8283
- New settables:
8384
- **max_column_completion_results**: (int) the maximum number of completion results to
8485
display in a single column

cmd2/cmd2.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
RenderableType,
8989
)
9090
from rich.highlighter import ReprHighlighter
91+
from rich.pretty import Pretty
9192
from rich.rule import Rule
9293
from rich.style import (
9394
Style,
@@ -1822,6 +1823,56 @@ def ppaged(
18221823
rich_print_kwargs=rich_print_kwargs,
18231824
)
18241825

1826+
def ppretty(
1827+
self,
1828+
obj: Any,
1829+
*,
1830+
file: IO[str] | None = None,
1831+
indent_size: int = 4,
1832+
indent_guides: bool = True,
1833+
max_length: int | None = None,
1834+
max_string: int | None = None,
1835+
max_depth: int | None = None,
1836+
expand_all: bool = False,
1837+
end: str = "\n",
1838+
) -> None:
1839+
"""Pretty print an object.
1840+
1841+
This is a cmd2-compatible replacement for rich.pretty.pprint().
1842+
1843+
:param obj: object to pretty print
1844+
:param file: file stream being written to or None for self.stdout.
1845+
Defaults to None.
1846+
:param indent_size: number of spaces in indent. Defaults to 4.
1847+
:param indent_guides: enable indentation guides. Defaults to True.
1848+
:param max_length: maximum length of containers before abbreviating, or None for no abbreviation.
1849+
Defaults to None.
1850+
:param max_string: maximum length of strings before truncating, or None to disable. Defaults to None.
1851+
:param max_depth: maximum depth for nested data structures, or None for unlimited depth. Defaults to None.
1852+
:param expand_all: Expand all containers. Defaults to False.
1853+
:param end: string to write at end of printed text. Defaults to a newline.
1854+
"""
1855+
# The overflow and soft_wrap values match those in rich.pretty.pprint().
1856+
# This ensures long strings are neither truncated with ellipses nor broken
1857+
# up by injected newlines.
1858+
pretty_obj = Pretty(
1859+
obj,
1860+
indent_size=indent_size,
1861+
indent_guides=indent_guides,
1862+
max_length=max_length,
1863+
max_string=max_string,
1864+
max_depth=max_depth,
1865+
expand_all=expand_all,
1866+
overflow="ignore",
1867+
)
1868+
1869+
self.print_to(
1870+
file or self.stdout,
1871+
pretty_obj,
1872+
soft_wrap=True,
1873+
end=end,
1874+
)
1875+
18251876
def get_bottom_toolbar(self) -> list[str | tuple[str, str]] | None:
18261877
"""Get the bottom toolbar content.
18271878

examples/pretty_print.py

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,26 @@
11
#!/usr/bin/env python3
2-
"""A simple example demonstrating how to pretty print JSON data in a cmd2 app using rich."""
3-
4-
from rich.json import JSON
2+
"""A simple example demonstrating how to pretty print data."""
53

64
import cmd2
75

86
EXAMPLE_DATA = {
97
"name": "John Doe",
108
"age": 30,
119
"address": {"street": "123 Main St", "city": "Anytown", "state": "CA"},
12-
"hobbies": ["reading", "hiking", "coding"],
10+
"hobbies": ["reading", "hiking", "coding", "cooking", "running", "painting", "music", "photography", "cycling"],
11+
"member": True,
12+
"vip": False,
13+
"phone": None,
1314
}
1415

1516

1617
class Cmd2App(cmd2.Cmd):
1718
def __init__(self) -> None:
1819
super().__init__()
19-
self.data = EXAMPLE_DATA
20-
21-
def do_normal(self, _) -> None:
22-
"""Display the data using the normal poutput method."""
23-
self.poutput(self.data)
24-
25-
def do_pretty(self, _) -> None:
26-
"""Display the JSON data in a pretty way using rich."""
2720

28-
json_renderable = JSON.from_data(
29-
self.data,
30-
indent=2,
31-
highlight=True,
32-
skip_keys=False,
33-
ensure_ascii=False,
34-
check_circular=True,
35-
allow_nan=True,
36-
default=None,
37-
sort_keys=False,
38-
)
39-
self.poutput(json_renderable)
21+
def do_pretty(self, _: cmd2.Statement) -> None:
22+
"""Print an object using ppretty()."""
23+
self.ppretty(EXAMPLE_DATA)
4024

4125

4226
if __name__ == '__main__':

tests/test_cmd2.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3490,6 +3490,46 @@ def test_ppaged_terminal_restoration_oserror(outsim_app, monkeypatch) -> None:
34903490
assert not termios_mock.tcsetattr.called
34913491

34923492

3493+
def test_ppretty(base_app: cmd2.Cmd) -> None:
3494+
# Mock the Pretty class and the print_to() method
3495+
with mock.patch('cmd2.cmd2.Pretty') as mock_pretty, mock.patch.object(cmd2.Cmd, 'print_to') as mock_print_to:
3496+
# Set up the mock return value for Pretty
3497+
mock_pretty_obj = mock.Mock()
3498+
mock_pretty.return_value = mock_pretty_obj
3499+
3500+
test_obj = {"key": "value"}
3501+
3502+
# Call ppretty() with some custom arguments
3503+
base_app.ppretty(
3504+
test_obj,
3505+
indent_size=2,
3506+
max_depth=5,
3507+
expand_all=True,
3508+
end="\n\n",
3509+
)
3510+
3511+
# Verify Pretty was instantiated with the correct arguments
3512+
mock_pretty.assert_called_once_with(
3513+
test_obj,
3514+
indent_size=2,
3515+
indent_guides=True,
3516+
max_length=None,
3517+
max_string=None,
3518+
max_depth=5,
3519+
expand_all=True,
3520+
overflow="ignore",
3521+
)
3522+
3523+
# Verify print_to() was called with the mock pretty object and soft_wrap=True
3524+
# It should default to self.stdout when no file is provided
3525+
mock_print_to.assert_called_once_with(
3526+
base_app.stdout,
3527+
mock_pretty_obj,
3528+
soft_wrap=True,
3529+
end="\n\n",
3530+
)
3531+
3532+
34933533
# we override cmd.parseline() so we always get consistent
34943534
# command parsing by parent methods we don't override
34953535
# don't need to test all the parsing logic here, because

0 commit comments

Comments
 (0)