summaryrefslogtreecommitdiff
path: root/cmd2
diff options
context:
space:
mode:
authorKevin Van Brunt <kmvanbrunt@gmail.com>2021-11-15 21:28:26 -0500
committerKevin Van Brunt <kmvanbrunt@gmail.com>2021-11-18 16:11:22 -0500
commitf2c4fd160d58aa540c7ccd0989d4e0e68ff56216 (patch)
tree55f6b6585fe0e4f5ea9e66a5ec0293aed2a0760e /cmd2
parent75f8008287923cd7a1728d56502f4fda6343933b (diff)
downloadcmd2-git-f2c4fd160d58aa540c7ccd0989d4e0e68ff56216.tar.gz
ArgparseCompleter now sorts CompletionItems created with numerical values as numbers.
Completion hint tables now right-align the left column if the hints have a numerical type. Fixed issue introduced in 2.3.0 with AlternatingTable, BorderedTable, and SimpleTable that caused header alignment settings to be overridden by data alignment settings.
Diffstat (limited to 'cmd2')
-rw-r--r--cmd2/argparse_completer.py43
-rw-r--r--cmd2/argparse_custom.py6
-rw-r--r--cmd2/table_creator.py41
3 files changed, 59 insertions, 31 deletions
diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py
index 26835513..9ec6677a 100644
--- a/cmd2/argparse_completer.py
+++ b/cmd2/argparse_completer.py
@@ -49,6 +49,7 @@ from .exceptions import (
)
from .table_creator import (
Column,
+ HorizontalAlignment,
SimpleTable,
)
@@ -547,15 +548,32 @@ class ArgparseCompleter:
return matches
def _format_completions(self, arg_state: _ArgumentState, completions: Union[List[str], List[CompletionItem]]) -> List[str]:
- # Check if the results are CompletionItems and that there aren't too many to display
- if 1 < len(completions) <= self._cmd2_app.max_completion_items and isinstance(completions[0], CompletionItem):
- completion_items = cast(List[CompletionItem], completions)
- four_spaces = 4 * ' '
+ """Format CompletionItems into hint table"""
+
+ # Nothing to do if we don't have at least 2 completions which are all CompletionItems
+ if len(completions) < 2 or not all(isinstance(c, CompletionItem) for c in completions):
+ return cast(List[str], completions)
+
+ completion_items = cast(List[CompletionItem], completions)
- # If the user has not already sorted the CompletionItems, then sort them before appending the descriptions
- if not self._cmd2_app.matches_sorted:
+ # Check if the data being completed have a numerical type
+ all_nums = all(isinstance(c.orig_value, numbers.Number) for c in completion_items)
+
+ # Sort CompletionItems before building the hint table
+ if not self._cmd2_app.matches_sorted:
+ # If all orig_value types are numbers, then sort by that value
+ if all_nums:
+ completion_items.sort(key=lambda c: c.orig_value) # type: ignore[no-any-return]
+
+ # Otherwise sort as strings
+ else:
completion_items.sort(key=self._cmd2_app.default_sort_key)
- self._cmd2_app.matches_sorted = True
+
+ self._cmd2_app.matches_sorted = True
+
+ # Check if there are too many CompletionItems to display as a table
+ if len(completions) <= self._cmd2_app.max_completion_items:
+ four_spaces = 4 * ' '
# If a metavar was defined, use that instead of the dest field
destination = arg_state.action.metavar if arg_state.action.metavar else arg_state.action.dest
@@ -588,13 +606,22 @@ class ArgparseCompleter:
desc_width = max(widest_line(item.description), desc_width)
cols = list()
- cols.append(Column(destination.upper(), width=token_width))
+ dest_alignment = HorizontalAlignment.RIGHT if all_nums else HorizontalAlignment.LEFT
+ cols.append(
+ Column(
+ destination.upper(),
+ width=token_width,
+ header_horiz_align=dest_alignment,
+ data_horiz_align=dest_alignment,
+ )
+ )
cols.append(Column(desc_header, width=desc_width))
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)
+ # Return sorted list of completions
return cast(List[str], completions)
def complete_subcommand_help(self, text: str, line: str, begidx: int, endidx: int, tokens: List[str]) -> List[str]:
diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py
index 871f9d25..80e405f3 100644
--- a/cmd2/argparse_custom.py
+++ b/cmd2/argparse_custom.py
@@ -332,12 +332,12 @@ class CompletionItem(str):
# Save the original value to support CompletionItems as argparse choices.
# cmd2 has patched argparse so input is compared to this value instead of the CompletionItem instance.
- self.__orig_value = value
+ self._orig_value = value
@property
def orig_value(self) -> Any:
- """Read-only property for __orig_value"""
- return self.__orig_value
+ """Read-only property for _orig_value"""
+ return self._orig_value
############################################################################################################
diff --git a/cmd2/table_creator.py b/cmd2/table_creator.py
index 19eead49..1e9755c7 100644
--- a/cmd2/table_creator.py
+++ b/cmd2/table_creator.py
@@ -416,8 +416,9 @@ class TableCreator:
def generate_row(
self,
+ row_data: Sequence[Any],
+ is_header: bool,
*,
- row_data: Optional[Sequence[Any]] = None,
fill_char: str = SPACE,
pre_line: str = EMPTY,
inter_cell: str = (2 * SPACE),
@@ -426,8 +427,9 @@ class TableCreator:
"""
Generate a header or data table row
- :param row_data: If this is None then a header row is generated. Otherwise data should have an entry for each
- column in the row. (Defaults to None)
+ :param row_data: data with an entry for each column in the row
+ :param is_header: True if writing a header cell, otherwise writing a data cell. This determines whether to
+ use header or data alignment settings defined in the Columns.
:param fill_char: character that fills remaining space in a cell. Defaults to space. If this is a tab,
then it will be converted to one space. (Cannot be a line breaking character)
:param pre_line: string to print before each line of a row. This can be used for a left row border and
@@ -453,13 +455,8 @@ class TableCreator:
# Display width of this cell
self.width = 0
- if row_data is None:
- row_data = [col.header for col in self.cols]
- is_header = True
- else:
- if len(row_data) != len(self.cols):
- raise ValueError("Length of row_data must match length of cols")
- is_header = False
+ if len(row_data) != len(self.cols):
+ raise ValueError("Length of row_data must match length of cols")
# Replace tabs (tabs in data strings will be handled in _generate_cell_lines())
fill_char = fill_char.replace('\t', SPACE)
@@ -654,14 +651,14 @@ class SimpleTable(TableCreator):
# Apply background color to header text in Columns which allow it
to_display: List[Any] = []
- for index, col in enumerate(self.cols):
+ for col in self.cols:
if col.style_header_text:
to_display.append(self.apply_header_bg(col.header))
else:
to_display.append(col.header)
# Create the header labels
- header_labels = self.generate_row(row_data=to_display, fill_char=fill_char, inter_cell=inter_cell)
+ header_labels = self.generate_row(to_display, is_header=True, fill_char=fill_char, inter_cell=inter_cell)
header_buf.write(header_labels)
# Add the divider if necessary
@@ -696,7 +693,7 @@ class SimpleTable(TableCreator):
else:
to_display.append(row_data[index])
- return self.generate_row(row_data=to_display, fill_char=fill_char, inter_cell=inter_cell)
+ return self.generate_row(to_display, is_header=False, fill_char=fill_char, inter_cell=inter_cell)
def generate_table(self, table_data: Sequence[Sequence[Any]], *, include_header: bool = True, row_spacing: int = 1) -> str:
"""
@@ -855,7 +852,8 @@ class BorderedTable(TableCreator):
post_line = self.padding * '═' + '╗'
return self.generate_row(
- row_data=self.empty_data,
+ self.empty_data,
+ is_header=False,
fill_char=self.apply_border_color(fill_char),
pre_line=self.apply_border_color(pre_line),
inter_cell=self.apply_border_color(inter_cell),
@@ -876,7 +874,8 @@ class BorderedTable(TableCreator):
post_line = self.padding * '═' + '╣'
return self.generate_row(
- row_data=self.empty_data,
+ self.empty_data,
+ is_header=False,
fill_char=self.apply_border_color(fill_char),
pre_line=self.apply_border_color(pre_line),
inter_cell=self.apply_border_color(inter_cell),
@@ -898,7 +897,8 @@ class BorderedTable(TableCreator):
post_line = self.padding * '─' + '╢'
return self.generate_row(
- row_data=self.empty_data,
+ self.empty_data,
+ is_header=False,
fill_char=self.apply_border_color(fill_char),
pre_line=self.apply_border_color(pre_line),
inter_cell=self.apply_border_color(inter_cell),
@@ -919,7 +919,8 @@ class BorderedTable(TableCreator):
post_line = self.padding * '═' + '╝'
return self.generate_row(
- row_data=self.empty_data,
+ self.empty_data,
+ is_header=False,
fill_char=self.apply_border_color(fill_char),
pre_line=self.apply_border_color(pre_line),
inter_cell=self.apply_border_color(inter_cell),
@@ -941,7 +942,7 @@ class BorderedTable(TableCreator):
# Apply background color to header text in Columns which allow it
to_display: List[Any] = []
- for index, col in enumerate(self.cols):
+ for col in self.cols:
if col.style_header_text:
to_display.append(self.apply_header_bg(col.header))
else:
@@ -953,7 +954,7 @@ class BorderedTable(TableCreator):
header_buf.write('\n')
header_buf.write(
self.generate_row(
- row_data=to_display, fill_char=fill_char, pre_line=pre_line, inter_cell=inter_cell, post_line=post_line
+ to_display, is_header=True, fill_char=fill_char, pre_line=pre_line, inter_cell=inter_cell, post_line=post_line
)
)
header_buf.write('\n')
@@ -988,7 +989,7 @@ class BorderedTable(TableCreator):
to_display.append(row_data[index])
return self.generate_row(
- row_data=to_display, fill_char=fill_char, pre_line=pre_line, inter_cell=inter_cell, post_line=post_line
+ to_display, is_header=False, fill_char=fill_char, pre_line=pre_line, inter_cell=inter_cell, post_line=post_line
)
def generate_table(self, table_data: Sequence[Sequence[Any]], *, include_header: bool = True) -> str: