diff options
| author | Kevin Van Brunt <kmvanbrunt@gmail.com> | 2021-11-15 21:28:26 -0500 |
|---|---|---|
| committer | Kevin Van Brunt <kmvanbrunt@gmail.com> | 2021-11-18 16:11:22 -0500 |
| commit | f2c4fd160d58aa540c7ccd0989d4e0e68ff56216 (patch) | |
| tree | 55f6b6585fe0e4f5ea9e66a5ec0293aed2a0760e /cmd2 | |
| parent | 75f8008287923cd7a1728d56502f4fda6343933b (diff) | |
| download | cmd2-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.py | 43 | ||||
| -rw-r--r-- | cmd2/argparse_custom.py | 6 | ||||
| -rw-r--r-- | cmd2/table_creator.py | 41 |
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: |
