summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md10
-rw-r--r--cmd2/argparse_completer.py2
-rw-r--r--cmd2/argparse_custom.py1
-rw-r--r--cmd2/cmd2.py89
-rw-r--r--cmd2/table_creator.py5
-rw-r--r--docs/features/builtin_commands.rst26
-rw-r--r--docs/features/settings.rst8
-rw-r--r--tests/conftest.py40
-rw-r--r--tests/test_argparse_completer.py24
-rwxr-xr-xtests/test_cmd2.py47
-rw-r--r--tests/test_table_creator.py7
-rw-r--r--tests/transcripts/regex_set.txt31
-rw-r--r--tests_isolated/test_commandset/conftest.py22
-rw-r--r--tests_isolated/test_commandset/test_commandset.py12
14 files changed, 189 insertions, 135 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 06db3f6d..9f8f840a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,13 @@
+## 2.2.0 (TBD, 2021)
+* Enhancements
+ * Using `SimpleTable` in the output for the following commands to improve appearance.
+ * help
+ * set (command and tab completion of Settables)
+ * alias tab completion
+ * macro tab completion
+ * Tab completion of `CompletionItems` now includes divider row comprised of `Cmd.ruler` character.
+ * Removed `--verbose` flag from set command since descriptions always show now.
+
## 2.1.2 (July 5, 2021)
* Enhancements
* Added the following accessor methods for cmd2-specific attributes to the `argparse.Action` class
diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py
index a244941c..967e3f1c 100644
--- a/cmd2/argparse_completer.py
+++ b/cmd2/argparse_completer.py
@@ -578,7 +578,7 @@ class ArgparseCompleter:
cols.append(Column(destination.upper(), width=token_width))
cols.append(Column(desc_header, width=desc_width))
- hint_table = SimpleTable(cols, divider_char=None)
+ hint_table = SimpleTable(cols, divider_char=self._cmd2_app.ruler)
table_data = [[item, item.description] for item in completion_items]
self._cmd2_app.formatted_completions = hint_table.generate_table(table_data, row_spacing=0)
diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py
index 8d0630fe..faeb0789 100644
--- a/cmd2/argparse_custom.py
+++ b/cmd2/argparse_custom.py
@@ -130,6 +130,7 @@ tokens with descriptions instead of just a table of tokens::
The user sees this:
ITEM_ID Item Name
+ ============================
1 My item
2 Another item
3 Yet another item
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index d479e484..35398088 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -2156,17 +2156,44 @@ class Cmd(cmd.Cmd):
if command not in self.hidden_commands and command not in self.disabled_commands
]
+ # Table displayed when tab completing aliases
+ _alias_completion_table = SimpleTable([Column('Value', width=80)], divider_char=None)
+
def _get_alias_completion_items(self) -> List[CompletionItem]:
- """Return list of current alias names and values as CompletionItems"""
- return [CompletionItem(cur_key, self.aliases[cur_key]) for cur_key in self.aliases]
+ """Return list of alias names and values as CompletionItems"""
+ results: List[CompletionItem] = []
+
+ for cur_key in self.aliases:
+ row_data = [self.aliases[cur_key]]
+ results.append(CompletionItem(cur_key, self._alias_completion_table.generate_data_row(row_data)))
+
+ return results
+
+ # Table displayed when tab completing macros
+ _macro_completion_table = SimpleTable([Column('Value', width=80)], divider_char=None)
def _get_macro_completion_items(self) -> List[CompletionItem]:
- """Return list of current macro names and values as CompletionItems"""
- return [CompletionItem(cur_key, self.macros[cur_key].value) for cur_key in self.macros]
+ """Return list of macro names and values as CompletionItems"""
+ results: List[CompletionItem] = []
+
+ for cur_key in self.macros:
+ row_data = [self.macros[cur_key].value]
+ results.append(CompletionItem(cur_key, self._macro_completion_table.generate_data_row(row_data)))
+
+ return results
+
+ # Table displayed when tab completing Settables
+ _settable_completion_table = SimpleTable([Column('Value', width=30), Column('Description', width=60)], divider_char=None)
def _get_settable_completion_items(self) -> List[CompletionItem]:
- """Return list of current settable names and descriptions as CompletionItems"""
- return [CompletionItem(cur_key, self.settables[cur_key].description) for cur_key in self.settables]
+ """Return list of Settable names, values, and descriptions as CompletionItems"""
+ results: List[CompletionItem] = []
+
+ for cur_key in self.settables:
+ row_data = [self.settables[cur_key].get_value(), self.settables[cur_key].description]
+ results.append(CompletionItem(cur_key, self._settable_completion_table.generate_data_row(row_data)))
+
+ return results
def _get_commands_aliases_and_macros_for_completion(self) -> List[str]:
"""Return a list of visible commands, aliases, and macros for tab completion"""
@@ -3167,7 +3194,7 @@ class Cmd(cmd.Cmd):
nargs=argparse.ZERO_OR_MORE,
help='alias(es) to delete',
choices_provider=_get_alias_completion_items,
- descriptive_header='Value',
+ descriptive_header=_alias_completion_table.generate_header(),
)
@as_subcommand_to('alias', 'delete', alias_delete_parser, help=alias_delete_help)
@@ -3201,7 +3228,7 @@ class Cmd(cmd.Cmd):
nargs=argparse.ZERO_OR_MORE,
help='alias(es) to list',
choices_provider=_get_alias_completion_items,
- descriptive_header='Value',
+ descriptive_header=_alias_completion_table.generate_header(),
)
@as_subcommand_to('alias', 'list', alias_list_parser, help=alias_list_help)
@@ -3393,7 +3420,7 @@ class Cmd(cmd.Cmd):
nargs=argparse.ZERO_OR_MORE,
help='macro(s) to delete',
choices_provider=_get_macro_completion_items,
- descriptive_header='Value',
+ descriptive_header=_macro_completion_table.generate_header(),
)
@as_subcommand_to('macro', 'delete', macro_delete_parser, help=macro_delete_help)
@@ -3427,7 +3454,7 @@ class Cmd(cmd.Cmd):
nargs=argparse.ZERO_OR_MORE,
help='macro(s) to list',
choices_provider=_get_macro_completion_items,
- descriptive_header='Value',
+ descriptive_header=_macro_completion_table.generate_header(),
)
@as_subcommand_to('macro', 'list', macro_list_parser, help=macro_list_help)
@@ -3683,12 +3710,11 @@ class Cmd(cmd.Cmd):
# Find the widest command
widest = max([ansi.style_aware_wcswidth(command) for command in cmds])
- # Define the topic table
+ # Define the table structure
name_column = Column('', width=max(widest, 20))
desc_column = Column('', width=80)
- divider_char = self.ruler if self.ruler else None
- topic_table = SimpleTable([name_column, desc_column], divider_char=divider_char)
+ topic_table = SimpleTable([name_column, desc_column], divider_char=self.ruler)
# Build the topic table
table_str_buf = io.StringIO()
@@ -3875,14 +3901,11 @@ class Cmd(cmd.Cmd):
)
set_parser_parent = DEFAULT_ARGUMENT_PARSER(description=set_description, add_help=False)
set_parser_parent.add_argument(
- '-v', '--verbose', action='store_true', help='include description of parameters when viewing'
- )
- set_parser_parent.add_argument(
'param',
nargs=argparse.OPTIONAL,
help='parameter to set or view',
choices_provider=_get_settable_completion_items,
- descriptive_header='Description',
+ descriptive_header=_settable_completion_table.generate_header(),
)
# Create the parser for the set command
@@ -3924,21 +3947,25 @@ class Cmd(cmd.Cmd):
# Show all settables
to_show = list(self.settables.keys())
- # Build the result strings
- max_len = 0
- results = dict()
- for param in to_show:
+ # Define the table structure
+ name_label = 'Name'
+ max_name_width = max([ansi.style_aware_wcswidth(param) for param in to_show])
+ max_name_width = max(max_name_width, ansi.style_aware_wcswidth(name_label))
+
+ cols: List[Column] = [
+ Column(name_label, width=max_name_width),
+ Column('Value', width=30),
+ Column('Description', width=60),
+ ]
+
+ table = SimpleTable(cols, divider_char=self.ruler)
+ self.poutput(table.generate_header())
+
+ # Build the table
+ for param in sorted(to_show, key=self.default_sort_key):
settable = self.settables[param]
- results[param] = f"{param}: {settable.get_value()!r}"
- max_len = max(max_len, ansi.style_aware_wcswidth(results[param]))
-
- # Display the results
- for param in sorted(results, key=self.default_sort_key):
- result_str = results[param]
- if args.verbose:
- self.poutput(f'{utils.align_left(result_str, width=max_len)} # {self.settables[param].description}')
- else:
- self.poutput(result_str)
+ row_data = [param, settable.get_value(), settable.description]
+ self.poutput(table.generate_data_row(row_data))
shell_parser = DEFAULT_ARGUMENT_PARSER(description="Execute a command as if at the OS prompt")
shell_parser.add_argument('command', help='the command to run', completer=shell_cmd_complete)
diff --git a/cmd2/table_creator.py b/cmd2/table_creator.py
index 38102a07..5420ebec 100644
--- a/cmd2/table_creator.py
+++ b/cmd2/table_creator.py
@@ -545,7 +545,7 @@ class SimpleTable(TableCreator):
:param column_spacing: how many spaces to place between columns. Defaults to 2.
:param tab_width: all tabs will be replaced with this many spaces. If a row's fill_char is a tab,
then it will be converted to one space.
- :param divider_char: optional character used to build the header divider row. Set this to None if you don't
+ :param divider_char: optional character used to build the header divider row. Set this to blank or None if you don't
want a divider row. Defaults to dash. (Cannot be a line breaking character)
:raises: ValueError if column_spacing is less than 0
:raises: ValueError if tab_width is less than 1
@@ -556,6 +556,9 @@ class SimpleTable(TableCreator):
raise ValueError("Column spacing cannot be less than 0")
self.inter_cell = column_spacing * SPACE
+ if divider_char == '':
+ divider_char = None
+
if divider_char is not None:
if len(ansi.strip_style(divider_char)) != 1:
raise TypeError("Divider character must be exactly one character long")
diff --git a/docs/features/builtin_commands.rst b/docs/features/builtin_commands.rst
index 14f340c2..97b160b5 100644
--- a/docs/features/builtin_commands.rst
+++ b/docs/features/builtin_commands.rst
@@ -102,16 +102,22 @@ within a running application:
.. code-block:: text
- (Cmd) set --verbose
- allow_style: 'Terminal' # Allow ANSI text style sequences in output (valid values: Terminal, Always, Never)
- always_show_hint: False # Display tab completion hint even when completion suggestions print
- debug: True # Show full traceback on exception
- echo: False # Echo command issued into output
- editor: 'vi' # Program used by 'edit'
- feedback_to_output: False # Include nonessentials in '|', '>' results
- max_completion_items: 50 # Maximum number of CompletionItems to display during tab completion
- quiet: False # Don't print nonessential feedback
- timing: False # Report execution times
+ (Cmd) set
+ Name Value Description
+ ==================================================================================================================
+ allow_style Terminal Allow ANSI text style sequences in output (valid values:
+ Terminal, Always, Never)
+ always_show_hint False Display tab completion hint even when completion suggestions
+ print
+ debug True Show full traceback on exception
+ echo False Echo command issued into output
+ editor vi Program used by 'edit'
+ feedback_to_output False Include nonessentials in '|', '>' results
+ max_completion_items 50 Maximum number of CompletionItems to display during tab
+ completion
+ quiet False Don't print nonessential feedback
+ timing False Report execution times
+
Any of these user-settable parameters can be set while running your app with
the ``set`` command like so:
diff --git a/docs/features/settings.rst b/docs/features/settings.rst
index c21b3258..0be46292 100644
--- a/docs/features/settings.rst
+++ b/docs/features/settings.rst
@@ -134,10 +134,10 @@ changes a setting, and will receive both the old value and the new value.
.. code-block:: text
- (Cmd) set --verbose | grep sunny
- sunny: False # Is it sunny outside?
- (Cmd) set --verbose | grep degrees
- degrees_c: 22 # Temperature in Celsius
+ (Cmd) set | grep sunny
+ sunny False Is it sunny outside?
+ (Cmd) set | grep degrees
+ degrees_c 22 Temperature in Celsius
(Cmd) sunbathe
Too dim.
(Cmd) set degrees_c 41
diff --git a/tests/conftest.py b/tests/conftest.py
index a5c47d97..0829da2f 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -104,29 +104,23 @@ SHORTCUTS_TXT = """Shortcuts for other commands:
@@: _relative_run_script
"""
-# Output from the show command with default settings
-SHOW_TXT = """allow_style: 'Terminal'
-always_show_hint: False
-debug: False
-echo: False
-editor: 'vim'
-feedback_to_output: False
-max_completion_items: 50
-quiet: False
-timing: False
-"""
-
-SHOW_LONG = """
-allow_style: 'Terminal' # Allow ANSI text style sequences in output (valid values: Terminal, Always, Never)
-always_show_hint: False # Display tab completion hint even when completion suggestions print
-debug: False # Show full traceback on exception
-echo: False # Echo command issued into output
-editor: 'vim' # Program used by 'edit'
-feedback_to_output: False # Include nonessentials in '|', '>' results
-max_completion_items: 50 # Maximum number of CompletionItems to display during tab completion
-quiet: False # Don't print nonessential feedback
-timing: False # Report execution times
-"""
+# Output from the set command
+SET_TXT = (
+ "Name Value Description \n"
+ "==================================================================================================================\n"
+ "allow_style Terminal Allow ANSI text style sequences in output (valid values: \n"
+ " Terminal, Always, Never) \n"
+ "always_show_hint False Display tab completion hint even when completion suggestions\n"
+ " print \n"
+ "debug False Show full traceback on exception \n"
+ "echo False Echo command issued into output \n"
+ "editor vim Program used by 'edit' \n"
+ "feedback_to_output False Include nonessentials in '|', '>' results \n"
+ "max_completion_items 50 Maximum number of CompletionItems to display during tab \n"
+ " completion \n"
+ "quiet False Don't print nonessential feedback \n"
+ "timing False Report execution times \n"
+)
def normalize(block):
diff --git a/tests/test_argparse_completer.py b/tests/test_argparse_completer.py
index 6002a856..25a13157 100644
--- a/tests/test_argparse_completer.py
+++ b/tests/test_argparse_completer.py
@@ -725,10 +725,14 @@ def test_completion_items(ac_app, num_aliases, show_description):
assert bool(ac_app.formatted_completions) == show_description
if show_description:
- # If show_description is True, the table will show both the alias name and result
- first_result_line = normalize(ac_app.formatted_completions)[1]
- assert 'fake_alias0' in first_result_line
- assert 'help' in first_result_line
+ # If show_description is True, the table will show both the alias name and value
+ description_displayed = False
+ for line in ac_app.formatted_completions.splitlines():
+ if 'fake_alias0' in line and 'help' in line:
+ description_displayed = True
+ break
+
+ assert description_displayed
def test_completion_item_choices(ac_app):
@@ -742,10 +746,14 @@ def test_completion_item_choices(ac_app):
assert len(ac_app.completion_matches) == len(ac_app.completion_item_choices)
assert len(ac_app.display_matches) == len(ac_app.completion_item_choices)
- # Make sure a completion table was created
- first_result_line = normalize(ac_app.formatted_completions)[1]
- assert 'choice_1' in first_result_line
- assert 'A description' in first_result_line
+ # The table will show both the choice and description
+ description_displayed = False
+ for line in ac_app.formatted_completions.splitlines():
+ if 'choice_1' in line and 'A description' in line:
+ description_displayed = True
+ break
+
+ assert description_displayed
@pytest.mark.parametrize(
diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py
index 928c323a..c541259e 100755
--- a/tests/test_cmd2.py
+++ b/tests/test_cmd2.py
@@ -31,9 +31,8 @@ from cmd2 import (
from .conftest import (
HELP_HISTORY,
+ SET_TXT,
SHORTCUTS_TXT,
- SHOW_LONG,
- SHOW_TXT,
complete_tester,
normalize,
odd_file_names,
@@ -107,7 +106,7 @@ def test_base_argparse_help(base_app):
def test_base_invalid_option(base_app):
out, err = run_cmd(base_app, 'set -z')
- assert err[0] == 'Usage: set [-h] [-v] [param] [value]'
+ assert err[0] == 'Usage: set [-h] [param] [value]'
assert 'Error: unrecognized arguments: -z' in err[1]
@@ -123,19 +122,11 @@ def test_command_starts_with_shortcut():
assert "Invalid command name 'help'" in str(excinfo.value)
-def test_base_show(base_app):
+def test_base_set(base_app):
# force editor to be 'vim' so test is repeatable across platforms
base_app.editor = 'vim'
out, err = run_cmd(base_app, 'set')
- expected = normalize(SHOW_TXT)
- assert out == expected
-
-
-def test_base_show_long(base_app):
- # force editor to be 'vim' so test is repeatable across platforms
- base_app.editor = 'vim'
- out, err = run_cmd(base_app, 'set -v')
- expected = normalize(SHOW_LONG)
+ expected = normalize(SET_TXT)
assert out == expected
@@ -150,7 +141,14 @@ now: True
assert out == expected
out, err = run_cmd(base_app, 'set quiet')
- assert out == ['quiet: True']
+ expected = normalize(
+ """
+Name Value Description
+===================================================================================================
+quiet True Don't print nonessential feedback
+"""
+ )
+ assert out == expected
def test_set_val_empty(base_app):
@@ -1752,7 +1750,8 @@ def test_get_alias_completion_items(base_app):
for cur_res in results:
assert cur_res in base_app.aliases
- assert cur_res.description == base_app.aliases[cur_res]
+ # Strip trailing spaces from table output
+ assert cur_res.description.rstrip() == base_app.aliases[cur_res]
def test_get_macro_completion_items(base_app):
@@ -1764,14 +1763,26 @@ def test_get_macro_completion_items(base_app):
for cur_res in results:
assert cur_res in base_app.macros
- assert cur_res.description == base_app.macros[cur_res].value
+ # Strip trailing spaces from table output
+ assert cur_res.description.rstrip() == base_app.macros[cur_res].value
def test_get_settable_completion_items(base_app):
results = base_app._get_settable_completion_items()
+ assert len(results) == len(base_app.settables)
+
for cur_res in results:
- assert cur_res in base_app.settables
- assert cur_res.description == base_app.settables[cur_res].description
+ cur_settable = base_app.settables.get(cur_res)
+ assert cur_settable is not None
+
+ # These CompletionItem descriptions are a two column table (Settable Value and Settable Description)
+ # First check if the description text starts with the value
+ str_value = str(cur_settable.get_value())
+ assert cur_res.description.startswith(str_value)
+
+ # The second column is likely to have wrapped long text. So we will just examine the
+ # first couple characters to look for the Settable's description.
+ assert cur_settable.description[0:10] in cur_res.description
def test_alias_no_subcommand(base_app):
diff --git a/tests/test_table_creator.py b/tests/test_table_creator.py
index 70b77bad..e1bc8883 100644
--- a/tests/test_table_creator.py
+++ b/tests/test_table_creator.py
@@ -364,9 +364,12 @@ def test_simple_table_creation():
# No divider
st = SimpleTable([column_1, column_2], divider_char=None)
- table = st.generate_table(row_data)
+ no_divider_1 = st.generate_table(row_data)
- assert table == (
+ st = SimpleTable([column_1, column_2], divider_char='')
+ no_divider_2 = st.generate_table(row_data)
+
+ assert no_divider_1 == no_divider_2 == (
'Col 1 Col 2 \n'
'Col 1 Row 1 Col 2 Row 1 \n'
'\n'
diff --git a/tests/transcripts/regex_set.txt b/tests/transcripts/regex_set.txt
index 623df8ed..68e61e30 100644
--- a/tests/transcripts/regex_set.txt
+++ b/tests/transcripts/regex_set.txt
@@ -3,14 +3,25 @@
# The regex for editor will match whatever program you use.
# Regexes on prompts just make the trailing space obvious
+(Cmd) set allow_style Terminal
+allow_style - was: '/.*/'
+now: 'Terminal'
+(Cmd) set editor vim
+editor - was: '/.*/'
+now: 'vim'
(Cmd) set
-allow_style: /'(Terminal|Always|Never)'/
-always_show_hint: False
-debug: False
-echo: False
-editor: /'.*'/
-feedback_to_output: False
-max_completion_items: 50
-maxrepeats: 3
-quiet: False
-timing: False
+Name Value Description/ +/
+==================================================================================================================
+allow_style Terminal Allow ANSI text style sequences in output (valid values:/ +/
+ Terminal, Always, Never)/ +/
+always_show_hint False Display tab completion hint even when completion suggestions
+ print/ +/
+debug False Show full traceback on exception/ +/
+echo False Echo command issued into output/ +/
+editor vim Program used by 'edit'/ +/
+feedback_to_output False Include nonessentials in '|', '>' results/ +/
+max_completion_items 50 Maximum number of CompletionItems to display during tab/ +/
+ completion/ +/
+maxrepeats 3 Max number of `--repeat`s allowed/ +/
+quiet False Don't print nonessential feedback/ +/
+timing False Report execution times/ +/
diff --git a/tests_isolated/test_commandset/conftest.py b/tests_isolated/test_commandset/conftest.py
index 32def64d..43e2af3e 100644
--- a/tests_isolated/test_commandset/conftest.py
+++ b/tests_isolated/test_commandset/conftest.py
@@ -107,28 +107,6 @@ SHORTCUTS_TXT = """Shortcuts for other commands:
@@: _relative_run_script
"""
-# Output from the show command with default settings
-SHOW_TXT = """allow_style: 'Terminal'
-debug: False
-echo: False
-editor: 'vim'
-feedback_to_output: False
-max_completion_items: 50
-quiet: False
-timing: False
-"""
-
-SHOW_LONG = """
-allow_style: 'Terminal' # Allow ANSI text style sequences in output (valid values: Terminal, Always, Never)
-debug: False # Show full traceback on exception
-echo: False # Echo command issued into output
-editor: 'vim' # Program used by 'edit'
-feedback_to_output: False # Include nonessentials in '|', '>' results
-max_completion_items: 50 # Maximum number of CompletionItems to display during tab completion
-quiet: False # Don't print nonessential feedback
-timing: False # Report execution times
-"""
-
def normalize(block):
"""Normalize a block of text to perform comparison.
diff --git a/tests_isolated/test_commandset/test_commandset.py b/tests_isolated/test_commandset/test_commandset.py
index 7e4e1821..89bac976 100644
--- a/tests_isolated/test_commandset/test_commandset.py
+++ b/tests_isolated/test_commandset/test_commandset.py
@@ -987,9 +987,10 @@ def test_commandset_settables():
# verify the settable shows up
out, err = run_cmd(app, 'set')
- assert 'arbitrary_value: 5' in out
+ any(['arbitrary_value' in line and '5' in line for line in out])
+
out, err = run_cmd(app, 'set arbitrary_value')
- assert out == ['arbitrary_value: 5']
+ any(['arbitrary_value' in line and '5' in line for line in out])
# change the value and verify the value changed
out, err = run_cmd(app, 'set arbitrary_value 10')
@@ -999,7 +1000,7 @@ now: 10
"""
assert out == normalize(expected)
out, err = run_cmd(app, 'set arbitrary_value')
- assert out == ['arbitrary_value: 10']
+ any(['arbitrary_value' in line and '10' in line for line in out])
# can't add to cmd2 now because commandset already has this settable
with pytest.raises(KeyError):
@@ -1058,9 +1059,10 @@ Parameter 'arbitrary_value' not supported (type 'set' for list of parameters).
assert 'some.arbitrary_value' in app.settables.keys()
out, err = run_cmd(app, 'set')
- assert 'some.arbitrary_value: 5' in out
+ any(['some.arbitrary_value' in line and '5' in line for line in out])
+
out, err = run_cmd(app, 'set some.arbitrary_value')
- assert out == ['some.arbitrary_value: 5']
+ any(['some.arbitrary_value' in line and '5' in line for line in out])
# verify registering a commandset with duplicate prefix and settable names fails
with pytest.raises(CommandSetRegistrationError):