Skip to content

Commit b3dd76a

Browse files
committed
Merge branch 'main' into update_examples
2 parents 8e0a67e + 5d49d55 commit b3dd76a

File tree

11 files changed

+229
-268
lines changed

11 files changed

+229
-268
lines changed

cmd2/argparse_completer.py

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,31 +16,26 @@
1616
cast,
1717
)
1818

19-
from .constants import (
20-
INFINITY,
21-
)
19+
from .constants import INFINITY
2220
from .rich_utils import Cmd2GeneralConsole
2321

2422
if TYPE_CHECKING: # pragma: no cover
25-
from .cmd2 import (
26-
Cmd,
27-
)
23+
from .cmd2 import Cmd
2824

2925
from rich.box import SIMPLE_HEAD
30-
from rich.table import Column, Table
26+
from rich.table import (
27+
Column,
28+
Table,
29+
)
3130

3231
from .argparse_custom import (
3332
ChoicesCallable,
3433
ChoicesProviderFuncWithTokens,
3534
CompletionItem,
3635
generate_range_error,
3736
)
38-
from .command_definition import (
39-
CommandSet,
40-
)
41-
from .exceptions import (
42-
CompletionError,
43-
)
37+
from .command_definition import CommandSet
38+
from .exceptions import CompletionError
4439
from .styles import Cmd2Style
4540

4641
# If no descriptive headers are supplied, then this will be used instead
@@ -583,8 +578,7 @@ def _format_completions(self, arg_state: _ArgumentState, completions: list[str]
583578
hint_table = Table(
584579
*headers,
585580
box=SIMPLE_HEAD,
586-
show_edge=False,
587-
border_style=Cmd2Style.RULE_LINE,
581+
border_style=Cmd2Style.TABLE_BORDER,
588582
)
589583
for item in completion_items:
590584
hint_table.add_row(item, *item.descriptive_data)

cmd2/cmd2.py

Lines changed: 134 additions & 152 deletions
Large diffs are not rendered by default.

cmd2/rich_utils.py

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def __repr__(self) -> str:
4545

4646

4747
def _create_default_theme() -> Theme:
48-
"""Create a default theme for cmd2-based applications.
48+
"""Create a default theme for the application.
4949
5050
This theme combines the default styles from cmd2, rich-argparse, and Rich.
5151
"""
@@ -79,8 +79,7 @@ def set_theme(styles: Mapping[str, StyleType] | None = None) -> None:
7979
RichHelpFormatter.styles[name] = APP_THEME.styles[name]
8080

8181

82-
# The main theme for cmd2-based applications.
83-
# You can change it with set_theme().
82+
# The application-wide theme. You can change it with set_theme().
8483
APP_THEME = _create_default_theme()
8584

8685

@@ -107,12 +106,22 @@ class RichPrintKwargs(TypedDict, total=False):
107106

108107

109108
class Cmd2BaseConsole(Console):
110-
"""A base class for Rich consoles in cmd2-based applications."""
109+
"""Base class for all cmd2 Rich consoles.
111110
112-
def __init__(self, file: IO[str] | None = None, **kwargs: Any) -> None:
111+
This class handles the core logic for managing Rich behavior based on
112+
cmd2's global settings, such as `ALLOW_STYLE` and `APP_THEME`.
113+
"""
114+
115+
def __init__(
116+
self,
117+
file: IO[str] | None = None,
118+
**kwargs: Any,
119+
) -> None:
113120
"""Cmd2BaseConsole initializer.
114121
115-
:param file: optional file object where the console should write to. Defaults to sys.stdout.
122+
:param file: optional file object where the console should write to.
123+
Defaults to sys.stdout.
124+
:param kwargs: keyword arguments passed to the parent Console class.
116125
"""
117126
# Don't allow force_terminal or force_interactive to be passed in, as their
118127
# behavior is controlled by the ALLOW_STYLE setting.
@@ -160,12 +169,13 @@ def on_broken_pipe(self) -> None:
160169

161170

162171
class Cmd2GeneralConsole(Cmd2BaseConsole):
163-
"""Rich console for general-purpose printing in cmd2-based applications."""
172+
"""Rich console for general-purpose printing."""
164173

165174
def __init__(self, file: IO[str] | None = None) -> None:
166175
"""Cmd2GeneralConsole initializer.
167176
168-
:param file: optional file object where the console should write to. Defaults to sys.stdout.
177+
:param file: optional file object where the console should write to.
178+
Defaults to sys.stdout.
169179
"""
170180
# This console is configured for general-purpose printing. It enables soft wrap
171181
# and disables Rich's automatic processing for markup, emoji, and highlighting.
@@ -180,13 +190,20 @@ def __init__(self, file: IO[str] | None = None) -> None:
180190

181191

182192
class Cmd2RichArgparseConsole(Cmd2BaseConsole):
183-
"""Rich console for rich-argparse output in cmd2-based applications.
193+
"""Rich console for rich-argparse output.
184194
185195
This class ensures long lines in help text are not truncated by avoiding soft_wrap,
186196
which conflicts with rich-argparse's explicit no_wrap and overflow settings.
187197
"""
188198

189199

200+
class Cmd2ExceptionConsole(Cmd2BaseConsole):
201+
"""Rich console for printing exceptions.
202+
203+
Ensures that long exception messages word wrap for readability by keeping soft_wrap disabled.
204+
"""
205+
206+
190207
def console_width() -> int:
191208
"""Return the width of the console."""
192209
return Cmd2BaseConsole().width

cmd2/rl_utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,15 +279,15 @@ def rl_in_search_mode() -> bool: # pragma: no cover
279279
readline_state = ctypes.c_int.in_dll(readline_lib, "rl_readline_state").value
280280
return bool(in_search_mode & readline_state)
281281
if rl_type == RlType.PYREADLINE:
282-
from pyreadline3.modes.emacs import ( # type: ignore[import-not-found]
282+
from pyreadline3.modes.emacs import ( # type: ignore[import]
283283
EmacsMode,
284284
)
285285

286286
# These search modes only apply to Emacs mode, which is the default.
287287
if not isinstance(readline.rl.mode, EmacsMode):
288288
return False
289289

290-
# While in search mode, the current keyevent function is set one of the following.
290+
# While in search mode, the current keyevent function is set to one of the following.
291291
search_funcs = (
292292
readline.rl.mode._process_incremental_search_keyevent,
293293
readline.rl.mode._process_non_incremental_search_keyevent,

cmd2/string_utils.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
"""Provides string utility functions.
22
33
This module offers a collection of string utility functions built on the Rich library.
4-
These utilities are designed to correctly handle strings with complex formatting, such as
5-
ANSI escape codes and full-width characters (like those used in CJK languages), which the
6-
standard Python library's string methods do not properly support.
4+
These utilities are designed to correctly handle strings with ANSI escape codes and
5+
full-width characters (like those used in CJK languages).
76
"""
87

98
from rich.align import AlignMethod
@@ -107,7 +106,7 @@ def strip_style(val: str) -> str:
107106
def str_width(val: str) -> int:
108107
"""Return the display width of a string.
109108
110-
This is intended for single line strings.
109+
This is intended for single-line strings.
111110
Replace tabs with spaces before calling this.
112111
113112
:param val: the string being measured

cmd2/styles.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,22 +30,24 @@ class Cmd2Style(StrEnum):
3030
added here must have a corresponding style definition there.
3131
"""
3232

33+
COMMAND_LINE = "cmd2.example" # Command line examples in help text
3334
ERROR = "cmd2.error" # Error text (used by perror())
34-
EXAMPLE = "cmd2.example" # Command line examples in help text
35+
EXCEPTION_TYPE = "cmd2.exception.type" # Used by pexcept to mark an exception type
3536
HELP_HEADER = "cmd2.help.header" # Help table header text
3637
HELP_LEADER = "cmd2.help.leader" # Text right before the help tables are listed
37-
RULE_LINE = "rule.line" # Built-in Rich style for horizontal rules
3838
SUCCESS = "cmd2.success" # Success text (used by psuccess())
39+
TABLE_BORDER = "cmd2.table_border" # Applied to cmd2's table borders
3940
WARNING = "cmd2.warning" # Warning text (used by pwarning())
4041

4142

4243
# Default styles used by cmd2. Tightly coupled with the Cmd2Style enum.
4344
DEFAULT_CMD2_STYLES: dict[str, StyleType] = {
45+
Cmd2Style.COMMAND_LINE: Style(color=Color.CYAN, bold=True),
4446
Cmd2Style.ERROR: Style(color=Color.BRIGHT_RED),
45-
Cmd2Style.EXAMPLE: Style(color=Color.CYAN, bold=True),
47+
Cmd2Style.EXCEPTION_TYPE: Style(color=Color.DARK_ORANGE, bold=True),
4648
Cmd2Style.HELP_HEADER: Style(color=Color.BRIGHT_GREEN, bold=True),
4749
Cmd2Style.HELP_LEADER: Style(color=Color.CYAN, bold=True),
48-
Cmd2Style.RULE_LINE: Style(color=Color.BRIGHT_GREEN),
4950
Cmd2Style.SUCCESS: Style(color=Color.GREEN),
51+
Cmd2Style.TABLE_BORDER: Style(color=Color.BRIGHT_GREEN),
5052
Cmd2Style.WARNING: Style(color=Color.BRIGHT_YELLOW),
5153
}

examples/argparse_completion.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def choices_completion_item(self) -> list[CompletionItem]:
4949
Text("styled text!!", style=Style(color=Color.BRIGHT_YELLOW, underline=True)),
5050
)
5151

52-
table_item = Table("Left Column", "Right Column", box=SIMPLE_HEAD, border_style=Cmd2Style.RULE_LINE)
52+
table_item = Table("Left Column", "Right Column", box=SIMPLE_HEAD, border_style=Cmd2Style.TABLE_BORDER)
5353
table_item.add_row("Yes, it's true.", "CompletionItems can")
5454
table_item.add_row("even display description", "data in tables!")
5555

tests/test_argparse_completer.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -720,8 +720,8 @@ def test_completion_items(ac_app) -> None:
720720
line_found = False
721721
for line in ac_app.formatted_completions.splitlines():
722722
# Since the CompletionItems were created from strings, the left-most column is left-aligned.
723-
# Therefore choice_1 will begin the line (with 1 space for padding).
724-
if line.startswith(' choice_1') and 'A description' in line:
723+
# Therefore choice_1 will begin the line (with 2 spaces for padding).
724+
if line.startswith(' choice_1') and 'A description' in line:
725725
line_found = True
726726
break
727727

@@ -743,7 +743,7 @@ def test_completion_items(ac_app) -> None:
743743
for line in ac_app.formatted_completions.splitlines():
744744
# Since the CompletionItems were created from numbers, the left-most column is right-aligned.
745745
# Therefore 1.5 will be right-aligned.
746-
if line.startswith(" 1.5") and "One.Five" in line:
746+
if line.startswith(" 1.5") and "One.Five" in line:
747747
line_found = True
748748
break
749749

@@ -908,7 +908,7 @@ def test_completion_items_arg_header(ac_app) -> None:
908908
begidx = endidx - len(text)
909909

910910
complete_tester(text, line, begidx, endidx, ac_app)
911-
assert "DESC_HEADER" in normalize(ac_app.formatted_completions)[0]
911+
assert "DESC_HEADER" in normalize(ac_app.formatted_completions)[1]
912912

913913
# Test when metavar is a string
914914
text = ''
@@ -917,7 +917,7 @@ def test_completion_items_arg_header(ac_app) -> None:
917917
begidx = endidx - len(text)
918918

919919
complete_tester(text, line, begidx, endidx, ac_app)
920-
assert ac_app.STR_METAVAR in normalize(ac_app.formatted_completions)[0]
920+
assert ac_app.STR_METAVAR in normalize(ac_app.formatted_completions)[1]
921921

922922
# Test when metavar is a tuple
923923
text = ''
@@ -927,7 +927,7 @@ def test_completion_items_arg_header(ac_app) -> None:
927927

928928
# We are completing the first argument of this flag. The first element in the tuple should be the column header.
929929
complete_tester(text, line, begidx, endidx, ac_app)
930-
assert ac_app.TUPLE_METAVAR[0].upper() in normalize(ac_app.formatted_completions)[0]
930+
assert ac_app.TUPLE_METAVAR[0].upper() in normalize(ac_app.formatted_completions)[1]
931931

932932
text = ''
933933
line = f'choices --tuple_metavar token_1 {text}'
@@ -936,7 +936,7 @@ def test_completion_items_arg_header(ac_app) -> None:
936936

937937
# We are completing the second argument of this flag. The second element in the tuple should be the column header.
938938
complete_tester(text, line, begidx, endidx, ac_app)
939-
assert ac_app.TUPLE_METAVAR[1].upper() in normalize(ac_app.formatted_completions)[0]
939+
assert ac_app.TUPLE_METAVAR[1].upper() in normalize(ac_app.formatted_completions)[1]
940940

941941
text = ''
942942
line = f'choices --tuple_metavar token_1 token_2 {text}'
@@ -946,7 +946,7 @@ def test_completion_items_arg_header(ac_app) -> None:
946946
# We are completing the third argument of this flag. It should still be the second tuple element
947947
# in the column header since the tuple only has two strings in it.
948948
complete_tester(text, line, begidx, endidx, ac_app)
949-
assert ac_app.TUPLE_METAVAR[1].upper() in normalize(ac_app.formatted_completions)[0]
949+
assert ac_app.TUPLE_METAVAR[1].upper() in normalize(ac_app.formatted_completions)[1]
950950

951951

952952
def test_completion_items_descriptive_headers(ac_app) -> None:
@@ -961,7 +961,7 @@ def test_completion_items_descriptive_headers(ac_app) -> None:
961961
begidx = endidx - len(text)
962962

963963
complete_tester(text, line, begidx, endidx, ac_app)
964-
assert ac_app.CUSTOM_DESC_HEADERS[0] in normalize(ac_app.formatted_completions)[0]
964+
assert ac_app.CUSTOM_DESC_HEADERS[0] in normalize(ac_app.formatted_completions)[1]
965965

966966
# This argument did not provide a descriptive header, so it should be DEFAULT_DESCRIPTIVE_HEADERS
967967
text = ''
@@ -970,7 +970,7 @@ def test_completion_items_descriptive_headers(ac_app) -> None:
970970
begidx = endidx - len(text)
971971

972972
complete_tester(text, line, begidx, endidx, ac_app)
973-
assert DEFAULT_DESCRIPTIVE_HEADERS[0] in normalize(ac_app.formatted_completions)[0]
973+
assert DEFAULT_DESCRIPTIVE_HEADERS[0] in normalize(ac_app.formatted_completions)[1]
974974

975975

976976
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)