diff options
Diffstat (limited to 'cmd2/argparse_completer.py')
-rwxr-xr-x | cmd2/argparse_completer.py | 81 |
1 files changed, 62 insertions, 19 deletions
diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py index d98a6eac..995aeb48 100755 --- a/cmd2/argparse_completer.py +++ b/cmd2/argparse_completer.py @@ -59,6 +59,7 @@ Released under MIT license, see LICENSE file import argparse from colorama import Fore +import os import sys from typing import List, Dict, Tuple, Callable, Union @@ -76,10 +77,20 @@ from .rl_utils import rl_force_redisplay # define the completion choices for the argument. You may provide a Collection or a Function. ACTION_ARG_CHOICES = 'arg_choices' ACTION_SUPPRESS_HINT = 'suppress_hint' +ACTION_DESCRIPTIVE_COMPLETION_HEADER = 'desc_header' + + +class CompletionItem(str): + def __new__(cls, o, desc='', *args, **kwargs) -> str: + return str.__new__(cls, o, *args, **kwargs) + + # noinspection PyMissingConstructor,PyUnusedLocal + def __init__(self, o, desc='', *args, **kwargs) -> None: + self.description = desc class _RangeAction(object): - def __init__(self, nargs: Union[int, str, Tuple[int, int], None]): + def __init__(self, nargs: Union[int, str, Tuple[int, int], None]) -> None: self.nargs_min = None self.nargs_max = None @@ -118,7 +129,7 @@ class _StoreRangeAction(argparse._StoreAction, _RangeAction): choices=None, required=False, help=None, - metavar=None): + metavar=None) -> None: _RangeAction.__init__(self, nargs) @@ -147,7 +158,7 @@ class _AppendRangeAction(argparse._AppendAction, _RangeAction): choices=None, required=False, help=None, - metavar=None): + metavar=None) -> None: _RangeAction.__init__(self, nargs) @@ -164,7 +175,7 @@ class _AppendRangeAction(argparse._AppendAction, _RangeAction): metavar=metavar) -def register_custom_actions(parser: argparse.ArgumentParser): +def register_custom_actions(parser: argparse.ArgumentParser) -> None: """Register custom argument action types""" parser.register('action', None, _StoreRangeAction) parser.register('action', 'store', _StoreRangeAction) @@ -175,14 +186,14 @@ class AutoCompleter(object): """Automatically command line tab completion based on argparse parameters""" class _ArgumentState(object): - def __init__(self): + def __init__(self) -> None: self.min = None self.max = None self.count = 0 self.needed = False self.variable = False - def reset(self): + def reset(self) -> None: """reset tracking values""" self.min = None self.max = None @@ -196,7 +207,7 @@ class AutoCompleter(object): arg_choices: Dict[str, Union[List, Tuple, Callable]] = None, subcmd_args_lookup: dict = None, tab_for_arg_help: bool = True, - cmd2_app=None): + cmd2_app=None) -> None: """ Create an AutoCompleter @@ -413,6 +424,8 @@ class AutoCompleter(object): completion_results = self._complete_for_arg(flag_action, text, line, begidx, endidx, consumed) if not completion_results: self._print_action_help(flag_action) + elif len(completion_results) > 1: + completion_results = self._format_completions(flag_action, completion_results) # ok, we're not a flag, see if there's a positional argument to complete else: @@ -422,9 +435,39 @@ class AutoCompleter(object): completion_results = self._complete_for_arg(pos_action, text, line, begidx, endidx, consumed) if not completion_results: self._print_action_help(pos_action) + elif len(completion_results) > 1: + completion_results = self._format_completions(pos_action, completion_results) return completion_results + def _format_completions(self, action, completions: List[Union[str, CompletionItem]]) -> List[str]: + if completions and len(completions) > 1 and isinstance(completions[0], CompletionItem): + token_width = len(action.dest) + completions_with_desc = [] + + for item in completions: + if len(item) > token_width: + token_width = len(item) + + term_size = os.get_terminal_size() + fill_width = int(term_size.columns * .6) - (token_width + 2) + for item in completions: + entry = '{: <{token_width}}{: <{fill_width}}'.format(item, item.description, + token_width=token_width+2, + fill_width=fill_width) + completions_with_desc.append(entry) + + try: + desc_header = action.desc_header + except AttributeError: + desc_header = 'Description' + header = '\n{: <{token_width}}{}'.format(action.dest.upper(), desc_header, token_width=token_width+2) + + self._cmd2_app.completion_header = header + self._cmd2_app.display_matches = completions_with_desc + + return completions + def complete_command_help(self, tokens: List[str], text: str, line: str, begidx: int, endidx: int) -> List[str]: """Supports the completion of sub-commands for commands through the cmd2 help command.""" for idx, token in enumerate(tokens): @@ -627,7 +670,7 @@ class AutoCompleter(object): class ACHelpFormatter(argparse.HelpFormatter): """Custom help formatter to configure ordering of help text""" - def _format_usage(self, usage, actions, groups, prefix): + def _format_usage(self, usage, actions, groups, prefix) -> str: if prefix is None: prefix = _('Usage: ') @@ -740,7 +783,7 @@ class ACHelpFormatter(argparse.HelpFormatter): # prefix with 'usage:' return '%s%s\n\n' % (prefix, usage) - def _format_action_invocation(self, action): + def _format_action_invocation(self, action) -> str: if not action.option_strings: default = self._get_default_metavar_for_positional(action) metavar, = self._metavar_formatter(action, default)(1) @@ -765,7 +808,7 @@ class ACHelpFormatter(argparse.HelpFormatter): return ', '.join(action.option_strings) + ' ' + args_string # End cmd2 customization - def _metavar_formatter(self, action, default_metavar): + def _metavar_formatter(self, action, default_metavar) -> Callable: if action.metavar is not None: result = action.metavar elif action.choices is not None: @@ -784,7 +827,7 @@ class ACHelpFormatter(argparse.HelpFormatter): return (result, ) * tuple_size return format - def _format_args(self, action, default_metavar): + def _format_args(self, action, default_metavar) -> str: get_metavar = self._metavar_formatter(action, default_metavar) # Begin cmd2 customization (less verbose) if isinstance(action, _RangeAction) and \ @@ -799,7 +842,7 @@ class ACHelpFormatter(argparse.HelpFormatter): result = super()._format_args(action, default_metavar) return result - def _split_lines(self, text, width): + def _split_lines(self, text: str, width) -> List[str]: return text.splitlines() @@ -807,7 +850,7 @@ class ACHelpFormatter(argparse.HelpFormatter): class ACArgumentParser(argparse.ArgumentParser): """Custom argparse class to override error method to change default help text.""" - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: if 'formatter_class' not in kwargs: kwargs['formatter_class'] = ACHelpFormatter @@ -817,7 +860,7 @@ class ACArgumentParser(argparse.ArgumentParser): self._custom_error_message = '' # Begin cmd2 customization - def set_custom_message(self, custom_message=''): + def set_custom_message(self, custom_message: str='') -> None: """ Allows an error message override to the error() function, useful when forcing a re-parse of arguments with newly required parameters @@ -825,7 +868,7 @@ class ACArgumentParser(argparse.ArgumentParser): self._custom_error_message = custom_message # End cmd2 customization - def error(self, message): + def error(self, message: str) -> None: """Custom error override. Allows application to control the error being displayed by argparse""" if len(self._custom_error_message) > 0: message = self._custom_error_message @@ -846,7 +889,7 @@ class ACArgumentParser(argparse.ArgumentParser): self.print_help() sys.exit(1) - def format_help(self): + def format_help(self) -> str: """Copy of format_help() from argparse.ArgumentParser with tweaks to separately display required parameters""" formatter = self._get_formatter() @@ -896,7 +939,7 @@ class ACArgumentParser(argparse.ArgumentParser): # determine help from format above return formatter.format_help() - def _get_nargs_pattern(self, action): + def _get_nargs_pattern(self, action) -> str: # Override _get_nargs_pattern behavior to use the nargs ranges provided by AutoCompleter if isinstance(action, _RangeAction) and \ action.nargs_min is not None and action.nargs_max is not None: @@ -909,7 +952,7 @@ class ACArgumentParser(argparse.ArgumentParser): return nargs_pattern return super(ACArgumentParser, self)._get_nargs_pattern(action) - def _match_argument(self, action, arg_strings_pattern): + def _match_argument(self, action, arg_strings_pattern) -> int: # match the pattern for this action to the arg strings nargs_pattern = self._get_nargs_pattern(action) match = _re.match(nargs_pattern, arg_strings_pattern) @@ -925,7 +968,7 @@ class ACArgumentParser(argparse.ArgumentParser): # This is the official python implementation with a 5 year old patch applied # See the comment below describing the patch - def _parse_known_args(self, arg_strings, namespace): # pragma: no cover + def _parse_known_args(self, arg_strings, namespace) -> Tuple[argparse.Namespace, List[str]]: # pragma: no cover # replace arg strings that are file references if self.fromfile_prefix_chars is not None: arg_strings = self._read_args_from_files(arg_strings) |