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
4 changes: 2 additions & 2 deletions cmd2/argparse_completer.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from .constants import (
INFINITY,
)
from .rich_utils import Cmd2Console
from .rich_utils import Cmd2GeneralConsole

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

# Generate the hint table string
console = Cmd2Console()
console = Cmd2GeneralConsole()
with console.capture() as capture:
console.print(hint_table, end="")
self._cmd2_app.formatted_completions = capture.get()
Expand Down
8 changes: 4 additions & 4 deletions cmd2/argparse_custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ def get_items(self) -> list[CompletionItems]:
)

from . import constants
from .rich_utils import Cmd2Console
from .rich_utils import Cmd2RichArgparseConsole
from .styles import Cmd2Style

if TYPE_CHECKING: # pragma: no cover
Expand Down Expand Up @@ -1113,12 +1113,12 @@ def __init__(
max_help_position: int = 24,
width: int | None = None,
*,
console: Cmd2Console | None = None,
console: Cmd2RichArgparseConsole | None = None,
**kwargs: Any,
) -> None:
"""Initialize Cmd2HelpFormatter."""
if console is None:
console = Cmd2Console()
console = Cmd2RichArgparseConsole()

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

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

self.exit(2, f'{formatted_message}\n')
Expand Down
26 changes: 13 additions & 13 deletions cmd2/cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@
shlex_split,
)
from .rich_utils import (
Cmd2Console,
Cmd2GeneralConsole,
RichPrintKwargs,
)
from .styles import Cmd2Style
Expand Down Expand Up @@ -161,7 +161,7 @@

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

try:
Cmd2Console(file).print(
Cmd2GeneralConsole(file).print(
*prepared_objects,
sep=sep,
end=end,
Expand All @@ -1245,7 +1245,7 @@ def print_to(
# warning message, then set the broken_pipe_warning attribute
# to the message you want printed.
if self.broken_pipe_warning and file != sys.stderr:
Cmd2Console(sys.stderr).print(self.broken_pipe_warning)
Cmd2GeneralConsole(sys.stderr).print(self.broken_pipe_warning)

def poutput(
self,
Expand All @@ -1267,7 +1267,7 @@ def poutput(
terminal width; instead, any text that doesn't fit will run onto the following line(s),
similar to the built-in print() function. Set to False to enable automatic word-wrapping.
If None (the default for this parameter), the output will default to no word-wrapping, as
configured by the Cmd2Console.
configured by the Cmd2GeneralConsole.
:param rich_print_kwargs: optional additional keyword arguments to pass to Rich's Console.print().
:param kwargs: Arbitrary keyword arguments. This allows subclasses to extend the signature of this
method and still call `super()` without encountering unexpected keyword argument errors.
Expand Down Expand Up @@ -1303,7 +1303,7 @@ def perror(
terminal width; instead, any text that doesn't fit will run onto the following line(s),
similar to the built-in print() function. Set to False to enable automatic word-wrapping.
If None (the default for this parameter), the output will default to no word-wrapping, as
configured by the Cmd2Console.
configured by the Cmd2GeneralConsole.
:param rich_print_kwargs: optional additional keyword arguments to pass to Rich's Console.print().
:param kwargs: Arbitrary keyword arguments. This allows subclasses to extend the signature of this
method and still call `super()` without encountering unexpected keyword argument errors.
Expand Down Expand Up @@ -1337,7 +1337,7 @@ def psuccess(
terminal width; instead, any text that doesn't fit will run onto the following line(s),
similar to the built-in print() function. Set to False to enable automatic word-wrapping.
If None (the default for this parameter), the output will default to no word-wrapping, as
configured by the Cmd2Console.
configured by the Cmd2GeneralConsole.
:param rich_print_kwargs: optional additional keyword arguments to pass to Rich's Console.print().
:param kwargs: Arbitrary keyword arguments. This allows subclasses to extend the signature of this
method and still call `super()` without encountering unexpected keyword argument errors.
Expand Down Expand Up @@ -1370,7 +1370,7 @@ def pwarning(
terminal width; instead, any text that doesn't fit will run onto the following line(s),
similar to the built-in print() function. Set to False to enable automatic word-wrapping.
If None (the default for this parameter), the output will default to no word-wrapping, as
configured by the Cmd2Console.
configured by the Cmd2GeneralConsole.
:param rich_print_kwargs: optional additional keyword arguments to pass to Rich's Console.print().
:param kwargs: Arbitrary keyword arguments. This allows subclasses to extend the signature of this
method and still call `super()` without encountering unexpected keyword argument errors.
Expand Down Expand Up @@ -1404,7 +1404,7 @@ def pexcept(
final_msg = Text()

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

# Generate the bytes to send to the pager
console = Cmd2Console(self.stdout)
console = Cmd2GeneralConsole(self.stdout)
with console.capture() as capture:
console.print(
*prepared_objects,
Expand Down
73 changes: 55 additions & 18 deletions cmd2/rich_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@
RenderableType,
RichCast,
)
from rich.style import (
StyleType,
)
from rich.style import StyleType
from rich.text import Text
from rich.theme import Theme
from rich_argparse import RichHelpFormatter
Expand Down Expand Up @@ -108,14 +106,33 @@ class RichPrintKwargs(TypedDict, total=False):
new_line_start: bool


class Cmd2Console(Console):
"""Rich console with characteristics appropriate for cmd2-based applications."""
class Cmd2BaseConsole(Console):
"""A base class for Rich consoles in cmd2-based applications."""

def __init__(self, file: IO[str] | None = None) -> None:
"""Cmd2Console initializer.
def __init__(self, file: IO[str] | None = None, **kwargs: Any) -> None:
"""Cmd2BaseConsole initializer.

:param file: Optional file object where the console should write to. Defaults to sys.stdout.
:param file: optional file object where the console should write to. Defaults to sys.stdout.
"""
# Don't allow force_terminal or force_interactive to be passed in, as their
# behavior is controlled by the ALLOW_STYLE setting.
if "force_terminal" in kwargs:
raise TypeError(
"Passing 'force_terminal' is not allowed. Its behavior is controlled by the 'ALLOW_STYLE' setting."
)
if "force_interactive" in kwargs:
raise TypeError(
"Passing 'force_interactive' is not allowed. Its behavior is controlled by the 'ALLOW_STYLE' setting."
)

# Don't allow a theme to be passed in, as it is controlled by the global APP_THEME.
# Use cmd2.rich_utils.set_theme() to set the global theme or use a temporary
# theme with console.use_theme().
if "theme" in kwargs:
raise TypeError(
"Passing 'theme' is not allowed. Its behavior is controlled by the global APP_THEME and set_theme()."
)

force_terminal: bool | None = None
force_interactive: bool | None = None

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

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

def on_broken_pipe(self) -> None:
Expand All @@ -150,9 +159,37 @@ def on_broken_pipe(self) -> None:
raise BrokenPipeError


class Cmd2GeneralConsole(Cmd2BaseConsole):
"""Rich console for general-purpose printing in cmd2-based applications."""

def __init__(self, file: IO[str] | None = None) -> None:
"""Cmd2GeneralConsole initializer.

:param file: optional file object where the console should write to. Defaults to sys.stdout.
"""
# This console is configured for general-purpose printing. It enables soft wrap
# and disables Rich's automatic processing for markup, emoji, and highlighting.
# These defaults can be overridden in calls to the console's or cmd2's print methods.
super().__init__(
file=file,
soft_wrap=True,
markup=False,
emoji=False,
highlight=False,
)


class Cmd2RichArgparseConsole(Cmd2BaseConsole):
"""Rich console for rich-argparse output in cmd2-based applications.

This class ensures long lines in help text are not truncated by avoiding soft_wrap,
which conflicts with rich-argparse's explicit no_wrap and overflow settings.
"""


def console_width() -> int:
"""Return the width of the console."""
return Cmd2Console().width
return Cmd2BaseConsole().width


def rich_text_to_string(text: Text) -> str:
Expand Down
17 changes: 16 additions & 1 deletion tests/test_rich_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,21 @@
from cmd2 import string_utils as su


def test_cmd2_base_console() -> None:
# Test the keyword arguments which are not allowed.
with pytest.raises(TypeError) as excinfo:
ru.Cmd2BaseConsole(force_terminal=True)
assert 'force_terminal' in str(excinfo.value)

with pytest.raises(TypeError) as excinfo:
ru.Cmd2BaseConsole(force_interactive=True)
assert 'force_interactive' in str(excinfo.value)

with pytest.raises(TypeError) as excinfo:
ru.Cmd2BaseConsole(theme=None)
assert 'theme' in str(excinfo.value)


def test_string_to_rich_text() -> None:
# Line breaks recognized by str.splitlines().
# Source: https://docs.python.org/3/library/stdtypes.html#str.splitlines
Expand Down Expand Up @@ -56,7 +71,7 @@ def test_rich_text_to_string(rich_text: Text, string: str) -> None:
assert ru.rich_text_to_string(rich_text) == string


def test_set_style() -> None:
def test_set_theme() -> None:
# Save a cmd2, rich-argparse, and rich-specific style.
cmd2_style_key = Cmd2Style.ERROR
argparse_style_key = "argparse.args"
Expand Down
Loading