Skip to content
Merged

Tweaks #1608

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 @@ -63,6 +63,7 @@ prompt is displayed.
`add_alert()`. This new function is thread-safe and does not require you to acquire a mutex
before calling it like the previous functions did.
- Removed `Cmd.default_to_shell`.
- Removed `Cmd.ruler` since `cmd2` no longer uses it.
- Enhancements
- New `cmd2.Cmd` parameters
- **auto_suggest**: (boolean) if `True`, provide fish shell style auto-suggestions. These
Expand Down
10 changes: 3 additions & 7 deletions cmd2/argparse_completer.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,12 @@
from rich.text import Text

from .constants import INFINITY
from .rich_utils import Cmd2SimpleTable

if TYPE_CHECKING: # pragma: no cover
from .cmd2 import Cmd

from rich.box import SIMPLE_HEAD
from rich.table import (
Column,
Table,
)
from rich.table import Column

from .argparse_custom import (
ChoicesCallable,
Expand All @@ -46,7 +43,6 @@
all_display_numeric,
)
from .exceptions import CompletionError
from .styles import Cmd2Style

# Name of the choice/completer function argument that, if present, will be passed a dictionary of
# command line tokens up through the token being completed mapped to their argparse destination name.
Expand Down Expand Up @@ -658,7 +654,7 @@ def _build_completion_table(self, arg_state: _ArgumentState, completions: Comple
)

# Build the table
table = Table(*rich_columns, box=SIMPLE_HEAD, show_edge=False, border_style=Cmd2Style.TABLE_BORDER)
table = Cmd2SimpleTable(*rich_columns)
for item in completions:
table.add_row(Text.from_ansi(item.display), *item.table_data)

Expand Down
47 changes: 21 additions & 26 deletions cmd2/cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@
cast,
)

import rich.box
from prompt_toolkit import (
filters,
print_formatted_text,
Expand Down Expand Up @@ -160,6 +159,7 @@
Cmd2BaseConsole,
Cmd2ExceptionConsole,
Cmd2GeneralConsole,
Cmd2SimpleTable,
RichPrintKwargs,
)
from .styles import Cmd2Style
Expand Down Expand Up @@ -517,9 +517,6 @@ def __init__(
# Used to keep track of whether we are redirecting or piping output
self._redirecting = False

# Characters used to draw a horizontal rule. Should not be blank.
self.ruler = "─"

# Set text which prints right before all of the help tables are listed.
self.doc_leader = ""

Expand Down Expand Up @@ -4185,6 +4182,15 @@ def do_help(self, args: argparse.Namespace) -> None:
self.perror(err_msg, style=None)
self.last_result = False

def _create_help_grid(self, title: str, *content: RenderableType) -> Table:
"""Create a titled grid for help headers with a ruler and optional content."""
grid = Table.grid()
grid.add_row(Text(title, style=Cmd2Style.HELP_HEADER))
grid.add_row(Rule(style=Cmd2Style.TABLE_BORDER))
for item in content:
grid.add_row(item)
return grid

def print_topics(self, header: str, cmds: Sequence[str] | None, cmdlen: int, maxcol: int) -> None: # noqa: ARG002
"""Print groups of commands and topics in columns and an optional header.

Expand All @@ -4198,12 +4204,11 @@ def print_topics(self, header: str, cmds: Sequence[str] | None, cmdlen: int, max
if not cmds:
return

# Print a row that looks like a table header.
if header:
header_grid = Table.grid()
header_grid.add_row(Text(header, style=Cmd2Style.HELP_HEADER))
header_grid.add_row(Rule(characters=self.ruler, style=Cmd2Style.TABLE_BORDER))
self.poutput(header_grid, soft_wrap=False)
self.poutput(
self._create_help_grid(header),
soft_wrap=False,
)

# Subtract 1 from maxcol to account for a one-space right margin.
maxcol = min(maxcol, ru.console_width()) - 1
Expand All @@ -4221,17 +4226,9 @@ def _print_documented_command_topics(self, header: str, cmds: Sequence[str], ver
self.print_topics(header, cmds, 15, 80)
return

# Create a grid to hold the header and the topics table
category_grid = Table.grid()
category_grid.add_row(Text(header, style=Cmd2Style.HELP_HEADER))
category_grid.add_row(Rule(characters=self.ruler, style=Cmd2Style.TABLE_BORDER))

topics_table = Table(
topic_table = Cmd2SimpleTable(
Column("Name", no_wrap=True),
Column("Description", overflow="fold"),
box=rich.box.SIMPLE_HEAD,
show_edge=False,
border_style=Cmd2Style.TABLE_BORDER,
)

# Try to get the documentation string for each command
Expand Down Expand Up @@ -4268,10 +4265,12 @@ def _print_documented_command_topics(self, header: str, cmds: Sequence[str], ver
cmd_desc = strip_doc_annotations(doc) if doc else ''

# Add this command to the table
topics_table.add_row(command, cmd_desc)
topic_table.add_row(command, cmd_desc)

category_grid.add_row(topics_table)
self.poutput(category_grid, soft_wrap=False)
self.poutput(
self._create_help_grid(header, topic_table),
soft_wrap=False,
)
self.poutput()

def render_columns(self, str_list: Sequence[str] | None, display_width: int = 80) -> str:
Expand Down Expand Up @@ -4560,14 +4559,10 @@ def do_set(self, args: argparse.Namespace) -> None:
# Show all settables
to_show = list(self.settables.keys())

# Define the table structure
settable_table = Table(
settable_table = Cmd2SimpleTable(
Column("Name", no_wrap=True),
Column("Value", overflow="fold"),
Column("Description", overflow="fold"),
box=rich.box.SIMPLE_HEAD,
show_edge=False,
border_style=Cmd2Style.TABLE_BORDER,
)

# Build the table and populate self.last_result
Expand Down
25 changes: 24 additions & 1 deletion cmd2/rich_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
TypedDict,
)

from rich.box import SIMPLE_HEAD
from rich.console import (
Console,
ConsoleRenderable,
Expand All @@ -29,7 +30,10 @@
from rich.theme import Theme
from rich_argparse import RichHelpFormatter

from .styles import DEFAULT_CMD2_STYLES
from .styles import (
DEFAULT_CMD2_STYLES,
Cmd2Style,
)

# Matches ANSI SGR (Select Graphic Rendition) sequences for text styling.
# \x1b[ - the CSI (Control Sequence Introducer)
Expand Down Expand Up @@ -395,6 +399,19 @@ def __init__(self, *, file: IO[str] | None = None) -> None:
)


class Cmd2SimpleTable(Table):
"""A clean, lightweight Rich Table tailored for cmd2's internal use."""

def __init__(self, *headers: Column | str) -> None:
"""Cmd2SimpleTable initializer."""
super().__init__(
*headers,
box=SIMPLE_HEAD,
show_edge=False,
border_style=Cmd2Style.TABLE_BORDER,
)


def console_width() -> int:
"""Return the width of the console."""
return Console().width
Expand All @@ -409,7 +426,13 @@ def rich_text_to_string(text: Text) -> str:

:param text: the text object to convert
:return: the resulting string with ANSI styles preserved.
:raises TypeError: if text is not a rich.text.Text object
"""
# Strictly enforce Text type. While console.print() can render any object,
# this function is specifically tailored to convert Text instances to strings.
if not isinstance(text, Text):
raise TypeError(f"rich_text_to_string() expected a rich.text.Text object, but got {type(text).__name__}")

console = Console(
force_terminal=True,
soft_wrap=True,
Expand Down
6 changes: 6 additions & 0 deletions tests/test_rich_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ def test_rich_text_to_string(rich_text: Text, string: str) -> None:
assert ru.rich_text_to_string(rich_text) == string


def test_rich_text_to_string_type_error() -> None:
with pytest.raises(TypeError) as excinfo:
ru.rich_text_to_string("not a Text object") # type: ignore[arg-type]
assert "rich_text_to_string() expected a rich.text.Text object, but got str" in str(excinfo.value)


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