Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
51 changes: 51 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,56 @@ 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().
# This ensures long strings are neither truncated with ellipses nor broken
# up by injected newlines.
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,
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,
}


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