Skip to content

Commit fd6011a

Browse files
kmvanbrunttleonhardt
authored andcommitted
Reformatted cmd2 tables and help output.
Added Cmd2Style.TABLE_BORDER style.
1 parent aa112e0 commit fd6011a

File tree

5 files changed

+60
-53
lines changed

5 files changed

+60
-53
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: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,9 @@
6363
cast,
6464
)
6565

66-
from rich.box import SIMPLE_HEAD
66+
import rich.box
6767
from rich.console import Group
68+
from rich.padding import Padding
6869
from rich.rule import Rule
6970
from rich.style import Style, StyleType
7071
from rich.table import (
@@ -2125,7 +2126,7 @@ def _display_matches_gnu_readline(
21252126
if self.formatted_completions:
21262127
if not hint_printed:
21272128
sys.stdout.write('\n')
2128-
sys.stdout.write('\n' + self.formatted_completions + '\n')
2129+
sys.stdout.write(self.formatted_completions)
21292130

21302131
# Otherwise use readline's formatter
21312132
else:
@@ -2182,7 +2183,7 @@ def _display_matches_pyreadline(self, matches: list[str]) -> None: # pragma: no
21822183
if self.formatted_completions:
21832184
if not hint_printed:
21842185
sys.stdout.write('\n')
2185-
sys.stdout.write('\n' + self.formatted_completions + '\n')
2186+
sys.stdout.write(self.formatted_completions)
21862187

21872188
# Redraw the prompt and input lines
21882189
rl_force_redisplay()
@@ -4121,8 +4122,13 @@ def do_help(self, args: argparse.Namespace) -> None:
41214122
cmds_cats, cmds_doc, cmds_undoc, help_topics = self._build_command_info()
41224123

41234124
if self.doc_leader:
4125+
# Indent doc_leader to align with the help tables.
41244126
self.poutput()
4125-
self.poutput(self.doc_leader, style=Cmd2Style.HELP_LEADER, soft_wrap=False)
4127+
self.poutput(
4128+
Padding.indent(self.doc_leader, 1),
4129+
style=Cmd2Style.HELP_LEADER,
4130+
soft_wrap=False,
4131+
)
41264132
self.poutput()
41274133

41284134
if not cmds_cats:
@@ -4167,6 +4173,9 @@ def print_topics(self, header: str, cmds: list[str] | None, cmdlen: int, maxcol:
41674173
41684174
Override of cmd's print_topics() to use Rich.
41694175
4176+
The output for both the header and the commands is indented by one space to align
4177+
with the tables printed by the `help -v` command.
4178+
41704179
:param header: string to print above commands being printed
41714180
:param cmds: list of topics to print
41724181
:param cmdlen: unused, even by cmd's version
@@ -4177,9 +4186,13 @@ def print_topics(self, header: str, cmds: list[str] | None, cmdlen: int, maxcol:
41774186

41784187
header_grid = Table.grid()
41794188
header_grid.add_row(header, style=Cmd2Style.HELP_HEADER)
4180-
header_grid.add_row(Rule(characters=self.ruler))
4181-
self.poutput(header_grid)
4182-
self.columnize(cmds, maxcol - 1)
4189+
header_grid.add_row(Rule(characters=self.ruler, style=Cmd2Style.TABLE_BORDER))
4190+
self.poutput(Padding.indent(header_grid, 1))
4191+
4192+
# Subtract 1 from maxcol to account for indentation.
4193+
maxcol = min(maxcol, ru.console_width()) - 1
4194+
columnized_cmds = self.render_columns(cmds, maxcol)
4195+
self.poutput(Padding.indent(columnized_cmds, 1), soft_wrap=False)
41834196
self.poutput()
41844197

41854198
def _print_documented_command_topics(self, header: str, cmds: list[str], verbose: bool) -> None:
@@ -4193,15 +4206,17 @@ def _print_documented_command_topics(self, header: str, cmds: list[str], verbose
41934206
self.print_topics(header, cmds, 15, 80)
41944207
return
41954208

4196-
category_grid = Table.grid()
4197-
category_grid.add_row(header, style=Cmd2Style.HELP_HEADER)
4198-
category_grid.add_row(Rule(characters=self.ruler))
4209+
# Indent header to align with the help tables.
4210+
self.poutput(
4211+
Padding.indent(header, 1),
4212+
style=Cmd2Style.HELP_HEADER,
4213+
soft_wrap=False,
4214+
)
41994215
topics_table = Table(
42004216
Column("Name", no_wrap=True),
42014217
Column("Description", overflow="fold"),
4202-
box=SIMPLE_HEAD,
4203-
border_style=Cmd2Style.RULE_LINE,
4204-
show_edge=False,
4218+
box=rich.box.HORIZONTALS,
4219+
border_style=Cmd2Style.TABLE_BORDER,
42054220
)
42064221

42074222
# Try to get the documentation string for each command
@@ -4240,8 +4255,8 @@ def _print_documented_command_topics(self, header: str, cmds: list[str], verbose
42404255
# Add this command to the table
42414256
topics_table.add_row(command, cmd_desc)
42424257

4243-
category_grid.add_row(topics_table)
4244-
self.poutput(category_grid, "")
4258+
self.poutput(topics_table)
4259+
self.poutput()
42454260

42464261
def render_columns(self, str_list: list[str] | None, display_width: int = 80) -> str:
42474262
"""Render a list of single-line strings as a compact set of columns.
@@ -4519,9 +4534,8 @@ def do_set(self, args: argparse.Namespace) -> None:
45194534
Column("Name", no_wrap=True),
45204535
Column("Value", overflow="fold"),
45214536
Column("Description", overflow="fold"),
4522-
box=SIMPLE_HEAD,
4523-
border_style=Cmd2Style.RULE_LINE,
4524-
show_edge=False,
4537+
box=rich.box.SIMPLE_HEAD,
4538+
border_style=Cmd2Style.TABLE_BORDER,
45254539
)
45264540

45274541
# Build the table and populate self.last_result
@@ -4532,9 +4546,7 @@ def do_set(self, args: argparse.Namespace) -> None:
45324546
settable_table.add_row(param, str(settable.get_value()), settable.description)
45334547
self.last_result[param] = settable.get_value()
45344548

4535-
self.poutput()
45364549
self.poutput(settable_table)
4537-
self.poutput()
45384550

45394551
@classmethod
45404552
def _build_shell_parser(cls) -> Cmd2ArgumentParser:

cmd2/styles.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class Cmd2Style(StrEnum):
3636
HELP_LEADER = "cmd2.help.leader" # Text right before the help tables are listed
3737
RULE_LINE = "rule.line" # 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

@@ -47,5 +48,6 @@ class Cmd2Style(StrEnum):
4748
Cmd2Style.HELP_LEADER: Style(color=Color.CYAN, bold=True),
4849
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
}

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(

tests/test_cmd2.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -184,15 +184,14 @@ def test_set(base_app) -> None:
184184
assert out == expected
185185
assert base_app.last_result is True
186186

187+
line_found = False
187188
out, err = run_cmd(base_app, 'set quiet')
188-
expected = normalize(
189-
"""
190-
Name Value Description
191-
───────────────────────────────────────────────────
192-
quiet True Don't print nonessential feedback
193-
"""
194-
)
195-
assert out == expected
189+
for line in out:
190+
if "quiet" in line and "True" in line and "False" not in line:
191+
line_found = True
192+
break
193+
194+
assert line_found
196195
assert len(base_app.last_result) == 1
197196
assert base_app.last_result['quiet'] is True
198197

0 commit comments

Comments
 (0)