diff options
| author | Kevin Van Brunt <kmvanbrunt@gmail.com> | 2020-08-13 14:19:05 -0400 |
|---|---|---|
| committer | Kevin Van Brunt <kmvanbrunt@gmail.com> | 2020-08-13 14:20:31 -0400 |
| commit | e6da8596c433f46bc337c7e8a14c7de1b0310e4c (patch) | |
| tree | 09f5a3225376e26dcb03419d6243c8fc52433b07 /cmd2 | |
| parent | 5dd2d03ef35a3d33ff53d82c8039d68e263246ee (diff) | |
| download | cmd2-git-e6da8596c433f46bc337c7e8a14c7de1b0310e4c.tar.gz | |
Replaced choices_function / choices_method with choices_provider.
Replaced completer_function / completer_method with completer.
ArgparseCompleter now always passes cmd2.Cmd or CommandSet instance as the self
argument to choices_provider and completer functions.
Moved basic_complete from utils into cmd2.Cmd class.
Moved CompletionError to exceptions.py
Diffstat (limited to 'cmd2')
| -rw-r--r-- | cmd2/__init__.py | 4 | ||||
| -rw-r--r-- | cmd2/argparse_completer.py | 25 | ||||
| -rw-r--r-- | cmd2/argparse_custom.py | 148 | ||||
| -rw-r--r-- | cmd2/cmd2.py | 76 | ||||
| -rw-r--r-- | cmd2/exceptions.py | 25 | ||||
| -rw-r--r-- | cmd2/utils.py | 62 |
6 files changed, 139 insertions, 201 deletions
diff --git a/cmd2/__init__.py b/cmd2/__init__.py index 9f0bb176..ca080ee8 100644 --- a/cmd2/__init__.py +++ b/cmd2/__init__.py @@ -32,8 +32,8 @@ from .command_definition import CommandSet, with_default_category from .constants import COMMAND_NAME, DEFAULT_SHORTCUTS from .decorators import with_argument_list, with_argparser, with_argparser_and_unknown_args, with_category, \ as_subcommand_to -from .exceptions import Cmd2ArgparseError, SkipPostcommandHooks, CommandSetRegistrationError +from .exceptions import Cmd2ArgparseError, CommandSetRegistrationError, CompletionError, SkipPostcommandHooks from . import plugin from .parsing import Statement from .py_bridge import CommandResult -from .utils import categorize, CompletionError, Settable +from .utils import categorize, Settable diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py index 0efaebe9..8bfaec80 100644 --- a/cmd2/argparse_completer.py +++ b/cmd2/argparse_completer.py @@ -24,8 +24,8 @@ from .argparse_custom import ( generate_range_error, ) from .command_definition import CommandSet +from .exceptions import CompletionError from .table_creator import Column, SimpleTable -from .utils import CompletionError, basic_complete # If no descriptive header is supplied, then this will be used instead DEFAULT_DESCRIPTIVE_HEADER = 'Description' @@ -459,7 +459,7 @@ class ArgparseCompleter: if action.help != argparse.SUPPRESS: match_against.append(flag) - return basic_complete(text, line, begidx, endidx, match_against) + return self._cmd2_app.basic_complete(text, line, begidx, endidx, match_against) def _format_completions(self, arg_state: _ArgumentState, completions: List[Union[str, CompletionItem]]) -> List[str]: # Check if the results are CompletionItems and that there aren't too many to display @@ -533,7 +533,7 @@ class ArgparseCompleter: return completer.complete_subcommand_help(tokens[token_index:], text, line, begidx, endidx) elif token_index == len(tokens) - 1: # Since this is the last token, we will attempt to complete it - return basic_complete(text, line, begidx, endidx, self._subcommand_action.choices) + return self._cmd2_app.basic_complete(text, line, begidx, endidx, self._subcommand_action.choices) else: break return [] @@ -577,16 +577,15 @@ class ArgparseCompleter: args = [] kwargs = {} if isinstance(arg_choices, ChoicesCallable): - if arg_choices.is_method: - # The completer may or may not be defined in the same class as the command. Since completer - # functions are registered with the command argparser before anything is instantiated, we - # need to find an instance at runtime that matches the types during declaration - cmd_set = self._cmd2_app._resolve_func_self(arg_choices.to_call, cmd_set) - if cmd_set is None: - # No cases matched, raise an error - raise CompletionError('Could not find CommandSet instance matching defining type for completer') + # The completer may or may not be defined in the same class as the command. Since completer + # functions are registered with the command argparser before anything is instantiated, we + # need to find an instance at runtime that matches the types during declaration + self_arg = self._cmd2_app._resolve_func_self(arg_choices.to_call, cmd_set) + if self_arg is None: + # No cases matched, raise an error + raise CompletionError('Could not find CommandSet instance matching defining type for completer') - args.append(cmd_set) + args.append(self_arg) # Check if arg_choices.to_call expects arg_tokens to_call_params = inspect.signature(arg_choices.to_call).parameters @@ -630,6 +629,6 @@ class ArgparseCompleter: arg_choices = [choice for choice in arg_choices if choice not in used_values] # Do tab completion on the choices - results = basic_complete(text, line, begidx, endidx, arg_choices) + results = self._cmd2_app.basic_complete(text, line, begidx, endidx, arg_choices) return self._format_completions(arg_state, results) diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index 12c18644..0687dc74 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -39,75 +39,36 @@ hints about the current argument that print when tab is pressed. In addition, you can add tab completion for each argument's values using parameters passed to add_argument(). -Below are the 5 add_argument() parameters for enabling tab completion of an +Below are the 3 add_argument() parameters for enabling tab completion of an argument's value. Only one can be used at a time. ``choices`` - pass a list of values to the choices parameter. Example:: - parser.add_argument('-o', '--options', choices=['An Option', 'SomeOtherOption']) + my_list = ['An Option', 'SomeOtherOption'] parser.add_argument('-o', '--options', choices=my_list) -``choices_function`` - pass a function that returns choices. This is good in +``choices_provider`` - pass a function that returns choices. This is good in cases where the choice list is dynamically generated when the user hits tab. Example:: - def my_choices_function(): + def my_choices_provider(self): ... return my_generated_list - parser.add_argument('-o', '--options', choices_function=my_choices_function) + parser.add_argument("arg", choices_provider=my_choices_provider) -``choices_method`` - this is equivalent to choices_function, but the function -needs to be an instance method of a cmd2.Cmd or cmd2.CommandSet subclass. When -ArgparseCompleter calls the method, it well detect whether is is bound to a -CommandSet or Cmd subclass. -If bound to a cmd2.Cmd subclass, it will pass the app instance as the `self` -argument. This is good in cases where the list of choices being generated -relies on state data of the cmd2-based app. -If bound to a cmd2.CommandSet subclass, it will pass the CommandSet instance -as the `self` argument. +``completer`` - pass a tab completion function that does custom completion. - Example:: - - def my_choices_method(self): - ... - return my_generated_list - - parser.add_argument("arg", choices_method=my_choices_method) - - -``completer_function`` - pass a tab completion function that does custom -completion. Since custom tab completion operations commonly need to modify -cmd2's instance variables related to tab completion, it will be rare to need a -completer function. completer_method should be used in those cases. - - Example:: - - def my_completer_function(text, line, begidx, endidx): - ... - return completions - parser.add_argument('-o', '--options', completer_function=my_completer_function) - -``completer_method`` - this is equivalent to completer_function, but the function -needs to be an instance method of a cmd2.Cmd or cmd2.CommandSet subclass. When -ArgparseCompleter calls the method, it well detect whether is is bound to a -CommandSet or Cmd subclass. -If bound to a cmd2.Cmd subclass, it will pass the app instance as the `self` -argument. This is good in cases where the list of choices being generated -relies on state data of the cmd2-based app. -If bound to a cmd2.CommandSet subclass, it will pass the CommandSet instance -as the `self` argument, and the app instance as the positional argument. -cmd2 provides a few completer methods for convenience (e.g., -path_complete, delimiter_complete) +cmd2 provides a few completer methods for convenience (e.g., path_complete, +delimiter_complete) Example:: # This adds file-path completion to an argument - parser.add_argument('-o', '--options', completer_method=cmd2.Cmd.path_complete) - + parser.add_argument('-o', '--options', completer=cmd2.Cmd.path_complete) You can use functools.partial() to prepopulate values of the underlying choices and completer functions/methods. @@ -115,13 +76,22 @@ path_complete, delimiter_complete) Example:: # This says to call path_complete with a preset value for its path_filter argument - completer_method = functools.partial(path_complete, - path_filter=lambda path: os.path.isdir(path)) - parser.add_argument('-o', '--options', choices_method=completer_method) - -Of the 5 tab completion parameters, choices is the only one where argparse + dir_completer = functools.partial(path_complete, + path_filter=lambda path: os.path.isdir(path)) + parser.add_argument('-o', '--options', completer=dir_completer) + +For `choices_provider` and `completer`, do not set them to a bound method. This +is because ArgparseCompleter passes the `self` argument explicitly to these +functions. When ArgparseCompleter calls one, it will detect whether it is bound +to a `Cmd` subclass or `CommandSet`. If bound to a `cmd2.Cmd subclass`, it will +pass the app instance as the `self` argument. If bound to a `cmd2.CommandSet` +subclass, it will pass the `CommandSet` instance as the `self` argument. +Therefore instead of passing something like `self.path_complete`, pass +`cmd2.Cmd.path_complete`. + +Of the 3 tab completion parameters, choices is the only one where argparse validates user input against items in the choices list. This is because the -other 4 parameters are meant to tab complete data sets that are viewed as +other 2 parameters are meant to tab complete data sets that are viewed as dynamic. Therefore it is up to the developer to validate if the user has typed an acceptable value for these arguments. @@ -129,10 +99,8 @@ The following functions exist in cases where you may want to manually add a choice-providing function/method to an existing argparse action. For instance, in __init__() of a custom action class. - - set_choices_function(action, func) - - set_choices_method(action, method) - - set_completer_function(action, func) - - set_completer_method(action, method) + - set_choices_provider(action, func) + - set_completer(action, func) There are times when what's being tab completed is determined by a previous argument on the command line. In theses cases, Autocompleter can pass a @@ -142,8 +110,8 @@ choices/completer function should have an argument called arg_tokens. Example:: - def my_choices_method(self, arg_tokens) - def my_completer_method(self, text, line, begidx, endidx, arg_tokens) + def my_choices_provider(self, arg_tokens) + def my_completer(self, text, line, begidx, endidx, arg_tokens) All values of the arg_tokens dictionary are lists, even if a particular argument expects only 1 token. Since ArgparseCompleter is for tab completion, @@ -295,15 +263,13 @@ class ChoicesCallable: Enables using a callable as the choices provider for an argparse argument. While argparse has the built-in choices attribute, it is limited to an iterable. """ - def __init__(self, is_method: bool, is_completer: bool, to_call: Callable): + def __init__(self, is_completer: bool, to_call: Callable): """ Initializer - :param is_method: True if to_call is an instance method of a cmd2 app. False if it is a function. :param is_completer: True if to_call is a tab completion routine which expects the args: text, line, begidx, endidx :param to_call: the callable object that will be called to provide choices for the argument """ - self.is_method = is_method self.is_completer = is_completer self.to_call = to_call @@ -318,34 +284,24 @@ def _set_choices_callable(action: argparse.Action, choices_callable: ChoicesCall # Verify consistent use of parameters if action.choices is not None: err_msg = ("None of the following parameters can be used alongside a choices parameter:\n" - "choices_function, choices_method, completer_function, completer_method") + "choices_provider, completer") raise (TypeError(err_msg)) elif action.nargs == 0: err_msg = ("None of the following parameters can be used on an action that takes no arguments:\n" - "choices_function, choices_method, completer_function, completer_method") + "choices_provider, completer") raise (TypeError(err_msg)) setattr(action, ATTR_CHOICES_CALLABLE, choices_callable) -def set_choices_function(action: argparse.Action, choices_function: Callable) -> None: - """Set choices_function on an argparse action""" - _set_choices_callable(action, ChoicesCallable(is_method=False, is_completer=False, to_call=choices_function)) - - -def set_choices_method(action: argparse.Action, choices_method: Callable) -> None: - """Set choices_method on an argparse action""" - _set_choices_callable(action, ChoicesCallable(is_method=True, is_completer=False, to_call=choices_method)) - - -def set_completer_function(action: argparse.Action, completer_function: Callable) -> None: - """Set completer_function on an argparse action""" - _set_choices_callable(action, ChoicesCallable(is_method=False, is_completer=True, to_call=completer_function)) +def set_choices_provider(action: argparse.Action, choices_provider: Callable) -> None: + """Set choices_provider on an argparse action""" + _set_choices_callable(action, ChoicesCallable(is_completer=False, to_call=choices_provider)) -def set_completer_method(action: argparse.Action, completer_method: Callable) -> None: - """Set completer_method on an argparse action""" - _set_choices_callable(action, ChoicesCallable(is_method=True, is_completer=True, to_call=completer_method)) +def set_completer(action: argparse.Action, completer: Callable) -> None: + """Set completer on an argparse action""" + _set_choices_callable(action, ChoicesCallable(is_completer=True, to_call=completer)) ############################################################################################################ @@ -359,10 +315,8 @@ orig_actions_container_add_argument = argparse._ActionsContainer.add_argument def _add_argument_wrapper(self, *args, nargs: Union[int, str, Tuple[int], Tuple[int, int], None] = None, - choices_function: Optional[Callable] = None, - choices_method: Optional[Callable] = None, - completer_function: Optional[Callable] = None, - completer_method: Optional[Callable] = None, + choices_provider: Optional[Callable] = None, + completer: Optional[Callable] = None, suppress_tab_hint: bool = False, descriptive_header: Optional[str] = None, **kwargs) -> argparse.Action: @@ -378,10 +332,8 @@ def _add_argument_wrapper(self, *args, to specify a max value with no upper bound, use a 1-item tuple (min,) # Added args used by ArgparseCompleter - :param choices_function: function that provides choices for this argument - :param choices_method: cmd2-app method that provides choices for this argument - :param completer_function: tab completion function that provides choices for this argument - :param completer_method: cmd2-app tab completion method that provides choices for this argument + :param choices_provider: function that provides choices for this argument + :param completer: tab completion function that provides choices for this argument :param suppress_tab_hint: when ArgparseCompleter has no results to show during tab completion, it displays the current argument's help text as a hint. Set this to True to suppress the hint. If this argument's help text is set to argparse.SUPPRESS, then tab hints will not display @@ -393,7 +345,7 @@ def _add_argument_wrapper(self, *args, :param kwargs: keyword-arguments recognized by argparse._ActionsContainer.add_argument Note: You can only use 1 of the following in your argument: - choices, choices_function, choices_method, completer_function, completer_method + choices, choices_provider, completer See the header of this file for more information @@ -401,12 +353,12 @@ def _add_argument_wrapper(self, *args, :raises: ValueError on incorrect parameter usage """ # Verify consistent use of arguments - choices_callables = [choices_function, choices_method, completer_function, completer_method] + choices_callables = [choices_provider, completer] num_params_set = len(choices_callables) - choices_callables.count(None) if num_params_set > 1: err_msg = ("Only one of the following parameters may be used at a time:\n" - "choices_function, choices_method, completer_function, completer_method") + "choices_provider, completer") raise (ValueError(err_msg)) # Pre-process special ranged nargs @@ -465,14 +417,10 @@ def _add_argument_wrapper(self, *args, # Set the custom attributes setattr(new_arg, ATTR_NARGS_RANGE, nargs_range) - if choices_function: - set_choices_function(new_arg, choices_function) - elif choices_method: - set_choices_method(new_arg, choices_method) - elif completer_function: - set_completer_function(new_arg, completer_function) - elif completer_method: - set_completer_method(new_arg, completer_method) + if choices_provider: + set_choices_provider(new_arg, choices_provider) + elif completer: + set_completer(new_arg, completer) setattr(new_arg, ATTR_SUPPRESS_TAB_HINT, suppress_tab_hint) setattr(new_arg, ATTR_DESCRIPTIVE_COMPLETION_HEADER, descriptive_header) diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 610ce4a3..aaa45af6 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -51,6 +51,7 @@ from .decorators import with_argparser, as_subcommand_to from .exceptions import ( CommandSetRegistrationError, Cmd2ShlexError, + CompletionError, EmbeddedConsoleExit, EmptyStatement, RedirectionError, @@ -59,7 +60,7 @@ from .exceptions import ( from .history import History, HistoryItem from .parsing import Macro, MacroArg, Statement, StatementParser, shlex_split from .rl_utils import RlType, rl_get_point, rl_make_safe_prompt, rl_set_prompt, rl_type, rl_warning, vt100_support -from .utils import CompletionError, get_defining_class, Settable +from .utils import get_defining_class, Settable # Set up readline if rl_type == RlType.NONE: # pragma: no cover @@ -1042,6 +1043,21 @@ class Cmd(cmd.Cmd): return tokens, raw_tokens + # noinspection PyMethodMayBeStatic, PyUnusedLocal + def basic_complete(self, text: str, line: str, begidx: int, endidx: int, match_against: Iterable) -> List[str]: + """ + Basic tab completion function that matches against a list of strings without considering line contents + or cursor position. The args required by this function are defined in the header of Python's cmd.py. + + :param text: the string prefix we are attempting to match (all matches must begin with it) + :param line: the current input line with leading whitespace removed + :param begidx: the beginning index of the prefix text + :param endidx: the ending index of the prefix text + :param match_against: the strings being matched against + :return: a list of possible tab completions + """ + return [cur_match for cur_match in match_against if cur_match.startswith(text)] + def delimiter_complete(self, text: str, line: str, begidx: int, endidx: int, match_against: Iterable, delimiter: str) -> List[str]: """ @@ -1076,7 +1092,7 @@ class Cmd(cmd.Cmd): :param delimiter: what delimits each portion of the matches (ex: paths are delimited by a slash) :return: a list of possible tab completions """ - matches = utils.basic_complete(text, line, begidx, endidx, match_against) + matches = self.basic_complete(text, line, begidx, endidx, match_against) # Display only the portion of the match that's being completed based on delimiter if matches: @@ -1137,7 +1153,7 @@ class Cmd(cmd.Cmd): # Perform tab completion using an Iterable if isinstance(match_against, Iterable): - completions_matches = utils.basic_complete(text, line, begidx, endidx, match_against) + completions_matches = self.basic_complete(text, line, begidx, endidx, match_against) # Perform tab completion using a function elif callable(match_against): @@ -1181,7 +1197,7 @@ class Cmd(cmd.Cmd): # Perform tab completion using a Iterable if isinstance(match_against, Iterable): - matches = utils.basic_complete(text, line, begidx, endidx, match_against) + matches = self.basic_complete(text, line, begidx, endidx, match_against) # Perform tab completion using a function elif callable(match_against): @@ -1765,7 +1781,7 @@ class Cmd(cmd.Cmd): # Otherwise complete token against anything a user can run else: match_against = self._get_commands_aliases_and_macros_for_completion() - self.completion_matches = utils.basic_complete(text, line, begidx, endidx, match_against) + self.completion_matches = self.basic_complete(text, line, begidx, endidx, match_against) # If we have one result and we are at the end of the line, then add a space if allowed if len(self.completion_matches) == 1 and endidx == len(line) and self.allow_appended_space: @@ -2695,9 +2711,9 @@ class Cmd(cmd.Cmd): epilog=alias_create_epilog) alias_create_parser.add_argument('name', help='name of this alias') alias_create_parser.add_argument('command', help='what the alias resolves to', - choices_method=_get_commands_aliases_and_macros_for_completion) + choices_provider=_get_commands_aliases_and_macros_for_completion) alias_create_parser.add_argument('command_args', nargs=argparse.REMAINDER, help='arguments to pass to command', - completer_method=path_complete) + completer=path_complete) @as_subcommand_to('alias', 'create', alias_create_parser, help=alias_create_description.lower()) def _alias_create(self, args: argparse.Namespace) -> None: @@ -2738,7 +2754,7 @@ class Cmd(cmd.Cmd): alias_delete_parser = DEFAULT_ARGUMENT_PARSER(add_help=False, description=alias_delete_description) alias_delete_parser.add_argument('names', nargs=argparse.ZERO_OR_MORE, help='alias(es) to delete', - choices_method=_get_alias_completion_items, descriptive_header='Value') + choices_provider=_get_alias_completion_items, descriptive_header='Value') alias_delete_parser.add_argument('-a', '--all', action='store_true', help="delete all aliases") @as_subcommand_to('alias', 'delete', alias_delete_parser, help=alias_delete_help) @@ -2766,7 +2782,7 @@ class Cmd(cmd.Cmd): alias_list_parser = DEFAULT_ARGUMENT_PARSER(add_help=False, description=alias_list_description) alias_list_parser.add_argument('names', nargs=argparse.ZERO_OR_MORE, help='alias(es) to list', - choices_method=_get_alias_completion_items, descriptive_header='Value') + choices_provider=_get_alias_completion_items, descriptive_header='Value') @as_subcommand_to('alias', 'list', alias_list_parser, help=alias_delete_help) def _alias_list(self, args: argparse.Namespace) -> None: @@ -2846,9 +2862,9 @@ class Cmd(cmd.Cmd): epilog=macro_create_epilog) macro_create_parser.add_argument('name', help='name of this macro') macro_create_parser.add_argument('command', help='what the macro resolves to', - choices_method=_get_commands_aliases_and_macros_for_completion) + choices_provider=_get_commands_aliases_and_macros_for_completion) macro_create_parser.add_argument('command_args', nargs=argparse.REMAINDER, - help='arguments to pass to command', completer_method=path_complete) + help='arguments to pass to command', completer=path_complete) @as_subcommand_to('macro', 'create', macro_create_parser, help=macro_create_help) def _macro_create(self, args: argparse.Namespace) -> None: @@ -2935,7 +2951,7 @@ class Cmd(cmd.Cmd): macro_delete_description = "Delete specified macros or all macros if --all is used" macro_delete_parser = DEFAULT_ARGUMENT_PARSER(add_help=False, description=macro_delete_description) macro_delete_parser.add_argument('names', nargs=argparse.ZERO_OR_MORE, help='macro(s) to delete', - choices_method=_get_macro_completion_items, descriptive_header='Value') + choices_provider=_get_macro_completion_items, descriptive_header='Value') macro_delete_parser.add_argument('-a', '--all', action='store_true', help="delete all macros") @as_subcommand_to('macro', 'delete', macro_delete_parser, help=macro_delete_help) @@ -2963,7 +2979,7 @@ class Cmd(cmd.Cmd): macro_list_parser = DEFAULT_ARGUMENT_PARSER(add_help=False, description=macro_list_description) macro_list_parser.add_argument('names', nargs=argparse.ZERO_OR_MORE, help='macro(s) to list', - choices_method=_get_macro_completion_items, descriptive_header='Value') + choices_provider=_get_macro_completion_items, descriptive_header='Value') @as_subcommand_to('macro', 'list', macro_list_parser, help=macro_list_help) def _macro_list(self, args: argparse.Namespace) -> None: @@ -2985,7 +3001,7 @@ class Cmd(cmd.Cmd): topics = set(self.get_help_topics()) visible_commands = set(self.get_visible_commands()) strs_to_match = list(topics | visible_commands) - return utils.basic_complete(text, line, begidx, endidx, strs_to_match) + return self.basic_complete(text, line, begidx, endidx, strs_to_match) def complete_help_subcommands(self, text: str, line: str, begidx: int, endidx: int, arg_tokens: Dict[str, List[str]]) -> List[str]: @@ -3012,9 +3028,9 @@ class Cmd(cmd.Cmd): help_parser = DEFAULT_ARGUMENT_PARSER(description="List available commands or provide " "detailed help for a specific command") help_parser.add_argument('command', nargs=argparse.OPTIONAL, help="command to retrieve help for", - completer_method=complete_help_command) + completer=complete_help_command) help_parser.add_argument('subcommands', nargs=argparse.REMAINDER, help="subcommand(s) to retrieve help for", - completer_method=complete_help_subcommands) + completer=complete_help_subcommands) help_parser.add_argument('-v', '--verbose', action='store_true', help="print a list of all commands with descriptions of each") @@ -3276,10 +3292,8 @@ class Cmd(cmd.Cmd): arg_name = 'value' settable_parser.add_argument(arg_name, metavar=arg_name, help=settable.description, choices=settable.choices, - choices_function=settable.choices_function, - choices_method=settable.choices_method, - completer_function=settable.completer_function, - completer_method=settable.completer_method) + choices_provider=settable.choices_provider, + completer=settable.completer) from .argparse_completer import ArgparseCompleter completer = ArgparseCompleter(settable_parser, self) @@ -3297,12 +3311,12 @@ class Cmd(cmd.Cmd): 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_method=_get_settable_completion_items, descriptive_header='Description') + choices_provider=_get_settable_completion_items, descriptive_header='Description') # Create the parser for the set command set_parser = DEFAULT_ARGUMENT_PARSER(parents=[set_parser_parent]) set_parser.add_argument('value', nargs=argparse.OPTIONAL, help='new value for settable', - completer_method=complete_set_value) + completer=complete_set_value) # Preserve quotes so users can pass in quoted empty strings and flags (e.g. -h) as the value @with_argparser(set_parser, preserve_quotes=True) @@ -3363,9 +3377,9 @@ class Cmd(cmd.Cmd): self.poutput(result_str) 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_method=shell_cmd_complete) + shell_parser.add_argument('command', help='the command to run', completer=shell_cmd_complete) shell_parser.add_argument('command_args', nargs=argparse.REMAINDER, help='arguments to pass to command', - completer_method=path_complete) + completer=path_complete) # Preserve quotes since we are passing these strings to the shell @with_argparser(shell_parser, preserve_quotes=True) @@ -3656,9 +3670,9 @@ class Cmd(cmd.Cmd): return py_bridge.stop run_pyscript_parser = DEFAULT_ARGUMENT_PARSER(description="Run a Python script file inside the console") - run_pyscript_parser.add_argument('script_path', help='path to the script file', completer_method=path_complete) + run_pyscript_parser.add_argument('script_path', help='path to the script file', completer=path_complete) run_pyscript_parser.add_argument('script_arguments', nargs=argparse.REMAINDER, - help='arguments to pass to script', completer_method=path_complete) + help='arguments to pass to script', completer=path_complete) @with_argparser(run_pyscript_parser) def do_run_pyscript(self, args: argparse.Namespace) -> Optional[bool]: @@ -3752,10 +3766,10 @@ class Cmd(cmd.Cmd): help='edit and then run selected history items') history_action_group.add_argument('-o', '--output_file', metavar='FILE', help='output commands to a script file, implies -s', - completer_method=path_complete) + completer=path_complete) history_action_group.add_argument('-t', '--transcript', metavar='TRANSCRIPT_FILE', help='output commands and results to a transcript file,\nimplies -s', - completer_method=path_complete) + completer=path_complete) history_action_group.add_argument('-c', '--clear', action='store_true', help='clear all history') history_format_group = history_parser.add_argument_group(title='formatting') @@ -4062,7 +4076,7 @@ class Cmd(cmd.Cmd): edit_parser = DEFAULT_ARGUMENT_PARSER(description=edit_description) edit_parser.add_argument('file_path', nargs=argparse.OPTIONAL, - help="optional path to a file to open in editor", completer_method=path_complete) + help="optional path to a file to open in editor", completer=path_complete) @with_argparser(edit_parser) def do_edit(self, args: argparse.Namespace) -> None: @@ -4105,8 +4119,8 @@ class Cmd(cmd.Cmd): run_script_parser = DEFAULT_ARGUMENT_PARSER(description=run_script_description) run_script_parser.add_argument('-t', '--transcript', metavar='TRANSCRIPT_FILE', help='record the output of the script as a transcript file', - completer_method=path_complete) - run_script_parser.add_argument('script_path', help="path to the script file", completer_method=path_complete) + completer=path_complete) + run_script_parser.add_argument('script_path', help="path to the script file", completer=path_complete) @with_argparser(run_script_parser) def do_run_script(self, args: argparse.Namespace) -> Optional[bool]: diff --git a/cmd2/exceptions.py b/cmd2/exceptions.py index d253985a..832794bd 100644 --- a/cmd2/exceptions.py +++ b/cmd2/exceptions.py @@ -31,6 +31,31 @@ class CommandSetRegistrationError(Exception): """ pass + +class CompletionError(Exception): + """ + Raised during tab completion operations to report any sort of error you want printed. This can also be used + just to display a message, even if it's not an error. For instance, ArgparseCompleter raises CompletionErrors + to display tab completion hints and sets apply_style to False so hints aren't colored like error text. + + Example use cases + + - Reading a database to retrieve a tab completion data set failed + - A previous command line argument that determines the data set being completed is invalid + - Tab completion hints + """ + def __init__(self, *args, apply_style: bool = True, **kwargs): + """ + Initializer for CompletionError + :param apply_style: If True, then ansi.style_error will be applied to the message text when printed. + Set to False in cases where the message text already has the desired style. + Defaults to True. + """ + self.apply_style = apply_style + + # noinspection PyArgumentList + super().__init__(*args, **kwargs) + ############################################################################################################ # The following exceptions are NOT part of the public API and are intended for internal use only. ############################################################################################################ diff --git a/cmd2/utils.py b/cmd2/utils.py index a2b1c854..d8d6b7cc 100644 --- a/cmd2/utils.py +++ b/cmd2/utils.py @@ -74,40 +74,13 @@ def str_to_bool(val: str) -> bool: raise ValueError("must be True or False (case-insensitive)") -class CompletionError(Exception): - """ - Raised during tab completion operations to report any sort of error you want printed. This can also be used - just to display a message, even if it's not an error. For instance, ArgparseCompleter raises CompletionErrors - to display tab completion hints and sets apply_style to False so hints aren't colored like error text. - - Example use cases - - - Reading a database to retrieve a tab completion data set failed - - A previous command line argument that determines the data set being completed is invalid - - Tab completion hints - """ - def __init__(self, *args, apply_style: bool = True, **kwargs): - """ - Initializer for CompletionError - :param apply_style: If True, then ansi.style_error will be applied to the message text when printed. - Set to False in cases where the message text already has the desired style. - Defaults to True. - """ - self.apply_style = apply_style - - # noinspection PyArgumentList - super().__init__(*args, **kwargs) - - class Settable: """Used to configure a cmd2 instance member to be settable via the set command in the CLI""" def __init__(self, name: str, val_type: Callable, description: str, *, onchange_cb: Callable[[str, Any, Any], Any] = None, choices: Iterable = None, - choices_function: Optional[Callable] = None, - choices_method: Optional[Callable] = None, - completer_function: Optional[Callable] = None, - completer_method: Optional[Callable] = None): + choices_provider: Optional[Callable] = None, + completer: Optional[Callable] = None): """ Settable Initializer @@ -129,14 +102,11 @@ class Settable: same settings in argparse-based tab completion. A maximum of one of these should be provided. :param choices: iterable of accepted values - :param choices_function: function that provides choices for this argument - :param choices_method: cmd2-app method that provides choices for this argument (See note below) - :param completer_function: tab completion function that provides choices for this argument - :param completer_method: cmd2-app tab completion method that provides choices - for this argument (See note below) + :param choices_provider: function that provides choices for this argument + :param completer: tab completion function that provides choices for this argument Note: - For choices_method and completer_method, do not set them to a bound method. This is because + For choices_provider and completer, do not set them to a bound method. This is because ArgparseCompleter passes the self argument explicitly to these functions. Therefore instead of passing something like self.path_complete, pass cmd2.Cmd.path_complete. @@ -150,10 +120,8 @@ class Settable: self.description = description self.onchange_cb = onchange_cb self.choices = choices - self.choices_function = choices_function - self.choices_method = choices_method - self.completer_function = completer_function - self.completer_method = completer_method + self.choices_provider = choices_provider + self.completer = completer def namedtuple_with_defaults(typename: str, field_names: Union[str, List[str]], @@ -689,22 +657,6 @@ class RedirectionSavedState: self.saved_redirecting = saved_redirecting -# noinspection PyUnusedLocal -def basic_complete(text: str, line: str, begidx: int, endidx: int, match_against: Iterable) -> List[str]: - """ - Basic tab completion function that matches against a list of strings without considering line contents - or cursor position. The args required by this function are defined in the header of Python's cmd.py. - - :param text: the string prefix we are attempting to match (all matches must begin with it) - :param line: the current input line with leading whitespace removed - :param begidx: the beginning index of the prefix text - :param endidx: the ending index of the prefix text - :param match_against: the strings being matched against - :return: a list of possible tab completions - """ - return [cur_match for cur_match in match_against if cur_match.startswith(text)] - - class TextAlignment(Enum): """Horizontal text alignment""" LEFT = 1 |
