Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ prompt is displayed.
- **pre_prompt**: hook method that is called before the prompt is displayed, but after
`prompt-toolkit` event loop has started
- **read_secret**: read secrets like passwords without displaying them to the terminal
- **ppretty**: a cmd2-compatible replacement for `rich.pretty.pprint()`
- New settables:
- **max_column_completion_results**: (int) the maximum number of completion results to
display in a single column
Expand Down
49 changes: 49 additions & 0 deletions cmd2/cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
RenderableType,
)
from rich.highlighter import ReprHighlighter
from rich.pretty import Pretty
from rich.rule import Rule
from rich.style import (
Style,
Expand Down Expand Up @@ -1822,6 +1823,54 @@ def ppaged(
rich_print_kwargs=rich_print_kwargs,
)

def ppretty(
self,
obj: Any,
*,
file: IO[str] | None = None,
indent_size: int = 4,
indent_guides: bool = True,
max_length: int | None = None,
max_string: int | None = None,
max_depth: int | None = None,
expand_all: bool = False,
end: str = "\n",
) -> None:
"""Pretty print an object.

This is a cmd2-compatible replacement for rich.pretty.pprint().

:param obj: object to pretty print
:param file: file stream being written to or None for self.stdout.
Defaults to None.
:param indent_size: number of spaces in indent. Defaults to 4.
:param indent_guides: enable indentation guides. Defaults to True.
:param max_length: maximum length of containers before abbreviating, or None for no abbreviation.
Defaults to None.
:param max_string: maximum length of strings before truncating, or None to disable. Defaults to None.
:param max_depth: maximum depth for nested data structures, or None for unlimited depth. Defaults to None.
:param expand_all: Expand all containers. Defaults to False.
:param end: string to write at end of printed text. Defaults to a newline.
"""
# The overflow and soft_wrap values match those in rich.pretty.pprint().
pretty_obj = Pretty(
obj,
indent_size=indent_size,
indent_guides=indent_guides,
max_length=max_length,
max_string=max_string,
max_depth=max_depth,
expand_all=expand_all,
overflow="ignore",
)

self.print_to(
file or self.stdout,
pretty_obj,
soft_wrap=True,
Comment thread
tleonhardt marked this conversation as resolved.
end=end,
)

def get_bottom_toolbar(self) -> list[str | tuple[str, str]] | None:
"""Get the bottom toolbar content.

Expand Down
32 changes: 8 additions & 24 deletions examples/pretty_print.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,26 @@
#!/usr/bin/env python3
"""A simple example demonstrating how to pretty print JSON data in a cmd2 app using rich."""

from rich.json import JSON
"""A simple example demonstrating how to pretty print data."""

import cmd2

EXAMPLE_DATA = {
"name": "John Doe",
"age": 30,
"address": {"street": "123 Main St", "city": "Anytown", "state": "CA"},
"hobbies": ["reading", "hiking", "coding"],
"hobbies": ["reading", "hiking", "coding", "cooking", "running", "painting", "music", "photography", "cycling"],
"member": True,
"vip": False,
"phone": None,
}
Comment thread
kmvanbrunt marked this conversation as resolved.


class Cmd2App(cmd2.Cmd):
def __init__(self) -> None:
super().__init__()
self.data = EXAMPLE_DATA

def do_normal(self, _) -> None:
"""Display the data using the normal poutput method."""
self.poutput(self.data)

def do_pretty(self, _) -> None:
"""Display the JSON data in a pretty way using rich."""

json_renderable = JSON.from_data(
self.data,
indent=2,
highlight=True,
skip_keys=False,
ensure_ascii=False,
check_circular=True,
allow_nan=True,
default=None,
sort_keys=False,
)
self.poutput(json_renderable)
def do_pretty(self, _: cmd2.Statement) -> None:
"""Print an object using ppretty()."""
self.ppretty(EXAMPLE_DATA)


if __name__ == '__main__':
Expand Down
40 changes: 40 additions & 0 deletions tests/test_cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -3490,6 +3490,46 @@ def test_ppaged_terminal_restoration_oserror(outsim_app, monkeypatch) -> None:
assert not termios_mock.tcsetattr.called


def test_ppretty(base_app: cmd2.Cmd) -> None:
# Mock the Pretty class and the print_to() method
with mock.patch('cmd2.cmd2.Pretty') as mock_pretty, mock.patch.object(cmd2.Cmd, 'print_to') as mock_print_to:
# Set up the mock return value for Pretty
mock_pretty_obj = mock.Mock()
mock_pretty.return_value = mock_pretty_obj

test_obj = {"key": "value"}

# Call ppretty() with some custom arguments
base_app.ppretty(
test_obj,
indent_size=2,
max_depth=5,
expand_all=True,
end="\n\n",
)

# Verify Pretty was instantiated with the correct arguments
mock_pretty.assert_called_once_with(
test_obj,
indent_size=2,
indent_guides=True,
max_length=None,
max_string=None,
max_depth=5,
expand_all=True,
overflow="ignore",
)

# Verify print_to() was called with the mock pretty object and soft_wrap=True
# It should default to self.stdout when no file is provided
mock_print_to.assert_called_once_with(
base_app.stdout,
mock_pretty_obj,
soft_wrap=True,
end="\n\n",
)


# we override cmd.parseline() so we always get consistent
# command parsing by parent methods we don't override
# don't need to test all the parsing logic here, because
Expand Down
Loading