summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md15
-rw-r--r--cmd2/__init__.py4
-rw-r--r--cmd2/ansi.py4
-rw-r--r--cmd2/argparse_completer.py77
-rw-r--r--cmd2/argparse_custom.py155
-rw-r--r--cmd2/cmd2.py411
-rw-r--r--cmd2/constants.py1
-rw-r--r--cmd2/decorators.py3
-rw-r--r--cmd2/exceptions.py25
-rw-r--r--cmd2/rl_utils.py4
-rw-r--r--cmd2/table_creator.py6
-rw-r--r--cmd2/utils.py105
-rw-r--r--docs/api/exceptions.rst3
-rw-r--r--docs/api/utils.rst24
-rw-r--r--docs/features/argument_processing.rst8
-rw-r--r--docs/features/completion.rst32
-rwxr-xr-xexamples/arg_decorators.py2
-rw-r--r--examples/argparse_completion.py98
-rwxr-xr-xexamples/async_printing.py14
-rw-r--r--examples/modular_commands/commandset_basic.py3
-rw-r--r--examples/modular_commands/commandset_complex.py3
-rw-r--r--examples/modular_commands_main.py96
-rw-r--r--examples/read_input.py112
-rw-r--r--tests/test_argparse_completer.py253
-rw-r--r--tests/test_argparse_custom.py22
-rwxr-xr-xtests/test_cmd2.py36
-rwxr-xr-xtests/test_completion.py19
-rwxr-xr-xtests/test_history.py1
-rw-r--r--tests_isolated/test_commandset/test_commandset.py18
29 files changed, 852 insertions, 702 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d1f33931..fab390d9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,18 @@
+## 2.0.0 (TBD)
+* Breaking changes
+ * Argparse Completion / Settables
+ * 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 first positional
+ argument` to choices_provider and completer functions.
+ * Moved `basic_complete` from utils into `cmd2.Cmd` class.
+ * Moved `CompletionError` to exceptions.py
+ * ``Namespace.__statement__`` has been removed. Use `Namespace.cmd2_statement.get()` instead.
+* Enhancements
+ * Added support for custom tab completion and up-arrow input history to `cmd2.Cmd2.read_input`.
+ See [read_input.py](https://github.com/python-cmd2/cmd2/blob/master/examples/read_input.py)
+ for an example.
+
## 1.3.10 (September 17, 2020)
* Enhancements
* Added user-settable option called `always_show_hint`. If True, then tab completion hints will always
diff --git a/cmd2/__init__.py b/cmd2/__init__.py
index 81e80efe..f507dc28 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, CompletionMode, CustomCompletionSettings, Settable
diff --git a/cmd2/ansi.py b/cmd2/ansi.py
index f172b87f..afef06ce 100644
--- a/cmd2/ansi.py
+++ b/cmd2/ansi.py
@@ -3,9 +3,9 @@
Support for ANSI escape sequences which are used for things like applying style to text,
setting the window title, and asynchronous alerts.
"""
+import enum
import functools
import re
-from enum import Enum
from typing import IO, Any, List, Union
import colorama
@@ -49,7 +49,7 @@ The default is ``STYLE_TERMINAL``.
ANSI_STYLE_RE = re.compile(r'\x1b\[[^m]*m')
-class ColorBase(Enum):
+class ColorBase(enum.Enum):
"""
Base class used for defining color enums. See fg and bg classes for examples.
diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py
index 7484358d..316d4666 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'
@@ -186,10 +186,19 @@ class ArgparseCompleter:
if isinstance(action, argparse._SubParsersAction):
self._subcommand_action = action
- def complete_command(self, tokens: List[str], text: str, line: str, begidx: int, endidx: int, *,
- cmd_set: Optional[CommandSet] = None) -> List[str]:
+ def complete(self, text: str, line: str, begidx: int, endidx: int, tokens: List[str], *,
+ cmd_set: Optional[CommandSet] = None) -> List[str]:
"""
- Complete the command using the argparse metadata and provided argument dictionary
+ Complete text using argparse metadata
+
+ :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 tokens: list of argument tokens being passed to the parser
+ :param cmd_set: if tab completing a command, the CommandSet the command's function belongs to, if applicable.
+ Defaults to None.
+
:raises: CompletionError for various types of tab completion errors
"""
if not tokens:
@@ -266,7 +275,7 @@ class ArgparseCompleter:
#############################################################################################
# Parse all but the last token
#############################################################################################
- for token_index, token in enumerate(tokens[1:-1], start=1):
+ for token_index, token in enumerate(tokens[:-1]):
# If we're in a positional REMAINDER arg, force all future tokens to go to that
if pos_arg_state is not None and pos_arg_state.is_remainder:
@@ -364,8 +373,8 @@ class ArgparseCompleter:
completer = ArgparseCompleter(self._subcommand_action.choices[token], self._cmd2_app,
parent_tokens=parent_tokens)
- return completer.complete_command(tokens[token_index:], text, line, begidx, endidx,
- cmd_set=cmd_set)
+ return completer.complete(text, line, begidx, endidx, tokens[token_index + 1:],
+ cmd_set=cmd_set)
else:
# Invalid subcommand entered, so no way to complete remaining tokens
return []
@@ -409,9 +418,8 @@ class ArgparseCompleter:
# Check if we are completing a flag's argument
if flag_arg_state is not None:
- completion_results = self._complete_for_arg(flag_arg_state, text, line,
- begidx, endidx, consumed_arg_values,
- cmd_set=cmd_set)
+ completion_results = self._complete_arg(text, line, begidx, endidx, flag_arg_state, consumed_arg_values,
+ cmd_set=cmd_set)
# If we have results, then return them
if completion_results:
@@ -431,9 +439,8 @@ class ArgparseCompleter:
action = remaining_positionals.popleft()
pos_arg_state = _ArgumentState(action)
- completion_results = self._complete_for_arg(pos_arg_state, text, line,
- begidx, endidx, consumed_arg_values,
- cmd_set=cmd_set)
+ completion_results = self._complete_arg(text, line, begidx, endidx, pos_arg_state, consumed_arg_values,
+ cmd_set=cmd_set)
# If we have results, then return them
if completion_results:
@@ -465,7 +472,7 @@ class ArgparseCompleter:
if action.help != argparse.SUPPRESS:
match_against.append(flag)
- matches = basic_complete(text, line, begidx, endidx, match_against)
+ matches = self._cmd2_app.basic_complete(text, line, begidx, endidx, match_against)
# Build a dictionary linking actions with their matched flag names
matched_actions = dict() # type: Dict[argparse.Action, List[str]]
@@ -538,26 +545,26 @@ class ArgparseCompleter:
return completions
- def complete_subcommand_help(self, tokens: List[str], text: str, line: str, begidx: int, endidx: int) -> List[str]:
+ def complete_subcommand_help(self, text: str, line: str, begidx: int, endidx: int, tokens: List[str]) -> List[str]:
"""
Supports cmd2's help command in the completion of subcommand names
- :param tokens: command line tokens
: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 tokens: arguments passed to command/subcommand
:return: List of subcommand completions
"""
# If our parser has subcommands, we must examine the tokens and check if they are subcommands
# If so, we will let the subcommand's parser handle the rest of the tokens via another ArgparseCompleter.
if self._subcommand_action is not None:
- for token_index, token in enumerate(tokens[1:], start=1):
+ for token_index, token in enumerate(tokens):
if token in self._subcommand_action.choices:
completer = ArgparseCompleter(self._subcommand_action.choices[token], self._cmd2_app)
- return completer.complete_subcommand_help(tokens[token_index:], text, line, begidx, endidx)
+ return completer.complete_subcommand_help(text, line, begidx, endidx, tokens[token_index + 1:])
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 []
@@ -565,24 +572,23 @@ class ArgparseCompleter:
def format_help(self, tokens: List[str]) -> str:
"""
Supports cmd2's help command in the retrieval of help text
- :param tokens: command line tokens
+ :param tokens: arguments passed to help command
:return: help text of the command being queried
"""
# If our parser has subcommands, we must examine the tokens and check if they are subcommands
# If so, we will let the subcommand's parser handle the rest of the tokens via another ArgparseCompleter.
if self._subcommand_action is not None:
- for token_index, token in enumerate(tokens[1:], start=1):
+ for token_index, token in enumerate(tokens):
if token in self._subcommand_action.choices:
completer = ArgparseCompleter(self._subcommand_action.choices[token], self._cmd2_app)
- return completer.format_help(tokens[token_index:])
+ return completer.format_help(tokens[token_index + 1:])
else:
break
return self._parser.format_help()
- def _complete_for_arg(self, arg_state: _ArgumentState,
- text: str, line: str, begidx: int, endidx: int,
- consumed_arg_values: Dict[str, List[str]], *,
- cmd_set: Optional[CommandSet] = None) -> List[str]:
+ def _complete_arg(self, text: str, line: str, begidx: int, endidx: int,
+ arg_state: _ArgumentState, consumed_arg_values: Dict[str, List[str]], *,
+ cmd_set: Optional[CommandSet] = None) -> List[str]:
"""
Tab completion routine for an argparse argument
:return: list of completions
@@ -610,16 +616,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
@@ -650,7 +655,7 @@ 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)
if not results:
# Reset the value for matches_sorted. This is because completion of flag names
diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py
index 45abe6b2..4e0a9708 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,27 @@ 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`.
+
+``choices_provider`` and ``completer`` functions can also be implemented as
+standalone functions (i.e. not a member of a class). In this case,
+ArgparseCompleter will pass its ``cmd2.Cmd`` app instance as the first
+positional argument.
+
+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,21 +104,19 @@ 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
+argument on the command line. In theses cases, ArgparseCompleter can pass a
dictionary that maps the command line tokens up through the one being completed
to their argparse argument name. To receive this dictionary, your
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 +268,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 +289,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 +320,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 +337,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 +350,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 +358,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 +422,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 6e1a38ca..af046612 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -30,6 +30,7 @@ Git repository on GitHub at https://github.com/python-cmd2/cmd2
# setting is True
import argparse
import cmd
+import functools
import glob
import inspect
import os
@@ -51,6 +52,7 @@ from .decorators import with_argparser, as_subcommand_to
from .exceptions import (
CommandSetRegistrationError,
Cmd2ShlexError,
+ CompletionError,
EmbeddedConsoleExit,
EmptyStatement,
RedirectionError,
@@ -59,7 +61,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
@@ -1053,7 +1055,7 @@ class Cmd(cmd.Cmd):
tmp_line = line[:endidx]
tmp_line += unclosed_quote
tmp_endidx = endidx + 1
- else:
+ else: # pragma: no cover
# The parsing error is not caused by unclosed quotes.
# Return empty lists since this means the line is malformed.
return [], []
@@ -1072,6 +1074,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]:
"""
@@ -1106,7 +1123,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:
@@ -1153,7 +1170,7 @@ class Cmd(cmd.Cmd):
"""
# Get all tokens through the one being completed
tokens, _ = self.tokens_for_completion(line, begidx, endidx)
- if not tokens:
+ if not tokens: # pragma: no cover
return []
completions_matches = []
@@ -1167,7 +1184,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):
@@ -1195,7 +1212,7 @@ class Cmd(cmd.Cmd):
"""
# Get all tokens through the one being completed
tokens, _ = self.tokens_for_completion(line, begidx, endidx)
- if not tokens:
+ if not tokens: # pragma: no cover
return []
matches = []
@@ -1211,7 +1228,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):
@@ -1405,7 +1422,7 @@ class Cmd(cmd.Cmd):
# Get all tokens through the one being completed. We want the raw tokens
# so we can tell if redirection strings are quoted and ignore them.
_, raw_tokens = self.tokens_for_completion(line, begidx, endidx)
- if not raw_tokens:
+ if not raw_tokens: # pragma: no cover
return []
# Must at least have the command
@@ -1585,49 +1602,96 @@ class Cmd(cmd.Cmd):
# Display matches using actual display function. This also redraws the prompt and line.
orig_pyreadline_display(matches_to_display)
- def _completion_for_command(self, text: str, line: str, begidx: int,
- endidx: int, shortcut_to_restore: str) -> None:
+ def _perform_completion(self, text: str, line: str, begidx: int, endidx: int,
+ custom_settings: Optional[utils.CustomCompletionSettings] = None) -> None:
"""
- Helper function for complete() that performs command-specific tab completion
+ Helper function for complete() that performs the actual completion
: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 shortcut_to_restore: if not blank, then this shortcut was removed from text and needs to be
- prepended to all the matches
+ :param custom_settings: optional prepopulated completion settings
"""
+ from .argparse_completer import ArgparseCompleter
+
unclosed_quote = ''
+ command = None
- # Parse the command line
- statement = self.statement_parser.parse_command_only(line)
- command = statement.command
- cmd_set = self._cmd_to_command_sets[command] if command in self._cmd_to_command_sets else None
- expanded_line = statement.command_and_args
+ # If custom_settings is None, then we are completing a command's arguments
+ if custom_settings is None:
+ # Parse the command line
+ statement = self.statement_parser.parse_command_only(line)
+ command = statement.command
+
+ # Malformed command line (e.g. quoted command token)
+ if not command:
+ return
+
+ expanded_line = statement.command_and_args
- # We overwrote line with a properly formatted but fully stripped version
- # Restore the end spaces since line is only supposed to be lstripped when
- # passed to completer functions according to Python docs
- rstripped_len = len(line) - len(line.rstrip())
- expanded_line += ' ' * rstripped_len
+ # We overwrote line with a properly formatted but fully stripped version
+ # Restore the end spaces since line is only supposed to be lstripped when
+ # passed to completer functions according to Python docs
+ rstripped_len = len(line) - len(line.rstrip())
+ expanded_line += ' ' * rstripped_len
- # Fix the index values if expanded_line has a different size than line
- if len(expanded_line) != len(line):
- diff = len(expanded_line) - len(line)
- begidx += diff
- endidx += diff
+ # Fix the index values if expanded_line has a different size than line
+ if len(expanded_line) != len(line):
+ diff = len(expanded_line) - len(line)
+ begidx += diff
+ endidx += diff
- # Overwrite line to pass into completers
- line = expanded_line
+ # Overwrite line to pass into completers
+ line = expanded_line
# Get all tokens through the one being completed
tokens, raw_tokens = self.tokens_for_completion(line, begidx, endidx)
-
- # Check if we either had a parsing error or are trying to complete the command token
- # The latter can happen if " or ' was entered as the command
- if len(tokens) <= 1:
+ if not tokens: # pragma: no cover
return
+ # Determine the completer function to use
+ if command is not None:
+ # Check if a macro was entered
+ if command in self.macros:
+ completer_func = self.path_complete
+
+ # Check if a command was entered
+ elif command in self.get_all_commands():
+ # Get the completer function for this command
+ completer_func = getattr(self, constants.COMPLETER_FUNC_PREFIX + command, None)
+
+ if completer_func is None:
+ # There's no completer function, next see if the command uses argparse
+ func = self.cmd_func(command)
+ argparser = getattr(func, constants.CMD_ATTR_ARGPARSER, None)
+
+ if func is not None and argparser is not None:
+ cmd_set = self._cmd_to_command_sets[command] if command in self._cmd_to_command_sets else None
+ completer = ArgparseCompleter(argparser, self)
+ preserve_quotes = getattr(func, constants.CMD_ATTR_PRESERVE_QUOTES)
+
+ completer_func = functools.partial(completer.complete,
+ tokens=raw_tokens[1:] if preserve_quotes else tokens[1:],
+ cmd_set=cmd_set)
+ else:
+ completer_func = self.completedefault
+
+ # Not a recognized macro or command
+ else:
+ # Check if this command should be run as a shell command
+ if self.default_to_shell and command in utils.get_exes_in_path(command):
+ completer_func = self.path_complete
+ else:
+ completer_func = self.completedefault
+
+ # Otherwise we are completing the command token or performing custom completion
+ else:
+ completer = ArgparseCompleter(custom_settings.parser, self)
+ completer_func = functools.partial(completer.complete,
+ tokens=raw_tokens if custom_settings.preserve_quotes else tokens,
+ cmd_set=None)
+
# Text we need to remove from completions later
text_to_remove = ''
@@ -1655,40 +1719,9 @@ class Cmd(cmd.Cmd):
text = text_to_remove + text
begidx = actual_begidx
- # Check if a macro was entered
- if command in self.macros:
- compfunc = self.path_complete
-
- # Check if a command was entered
- elif command in self.get_all_commands():
- # Get the completer function for this command
- compfunc = getattr(self, constants.COMPLETER_FUNC_PREFIX + command, None)
-
- if compfunc is None:
- # There's no completer function, next see if the command uses argparse
- func = self.cmd_func(command)
- argparser = getattr(func, constants.CMD_ATTR_ARGPARSER, None)
-
- if func is not None and argparser is not None:
- import functools
- compfunc = functools.partial(self._complete_argparse_command,
- argparser=argparser,
- preserve_quotes=getattr(func, constants.CMD_ATTR_PRESERVE_QUOTES),
- cmd_set=cmd_set)
- else:
- compfunc = self.completedefault
-
- # Not a recognized macro or command
- else:
- # Check if this command should be run as a shell command
- if self.default_to_shell and command in utils.get_exes_in_path(command):
- compfunc = self.path_complete
- else:
- compfunc = self.completedefault
-
# Attempt tab completion for redirection first, and if that isn't occurring,
# call the completer function for the current command
- self.completion_matches = self._redirect_complete(text, line, begidx, endidx, compfunc)
+ self.completion_matches = self._redirect_complete(text, line, begidx, endidx, completer_func)
if self.completion_matches:
@@ -1738,16 +1771,12 @@ class Cmd(cmd.Cmd):
elif text_to_remove:
self.completion_matches = [match.replace(text_to_remove, '', 1) for match in self.completion_matches]
- # Check if we need to restore a shortcut in the tab completions
- # so it doesn't get erased from the command line
- if shortcut_to_restore:
- self.completion_matches = [shortcut_to_restore + match for match in self.completion_matches]
-
# If we have one result, then add a closing quote if needed and allowed
if len(self.completion_matches) == 1 and self.allow_closing_quote and unclosed_quote:
self.completion_matches[0] += unclosed_quote
- def complete(self, text: str, state: int) -> Optional[str]:
+ def complete(self, text: str, state: int,
+ custom_settings: Optional[utils.CustomCompletionSettings] = None) -> Optional[str]:
"""Override of cmd2's complete method which returns the next possible completion for 'text'
This completer function is called by readline as complete(text, state), for state in 0, 1, 2, …,
@@ -1759,6 +1788,7 @@ class Cmd(cmd.Cmd):
:param text: the current word that user is typing
:param state: non-negative integer
+ :param custom_settings: used when not tab completing the main command line
:return: the next possible completion for text or None
"""
# noinspection PyBroadException
@@ -1769,7 +1799,7 @@ class Cmd(cmd.Cmd):
# Check if we are completing a multiline command
if self._at_continuation_prompt:
# lstrip and prepend the previously typed portion of this multiline command
- lstripped_previous = self._multiline_in_progress.lstrip()
+ lstripped_previous = self._multiline_in_progress.lstrip().replace(constants.LINE_FEED, ' ')
line = lstripped_previous + readline.get_line_buffer()
# Increment the indexes to account for the prepended text
@@ -1788,9 +1818,9 @@ class Cmd(cmd.Cmd):
# Shortcuts are not word break characters when tab completing. Therefore shortcuts become part
# of the text variable if there isn't a word break, like a space, after it. We need to remove it
- # from text and update the indexes. This only applies if we are at the the beginning of the line.
+ # from text and update the indexes. This only applies if we are at the beginning of the command line.
shortcut_to_restore = ''
- if begidx == 0:
+ if begidx == 0 and custom_settings is None:
for (shortcut, _) in self.statement_parser.shortcuts:
if text.startswith(shortcut):
# Save the shortcut to restore later
@@ -1800,15 +1830,19 @@ class Cmd(cmd.Cmd):
text = text[len(shortcut_to_restore):]
begidx += len(shortcut_to_restore)
break
+ else:
+ # No shortcut was found. Complete the command token.
+ parser = DEFAULT_ARGUMENT_PARSER(add_help=False)
+ parser.add_argument('command', metavar="COMMAND", help="command, alias, or macro name",
+ choices=self._get_commands_aliases_and_macros_for_completion())
+ custom_settings = utils.CustomCompletionSettings(parser)
- # If begidx is greater than 0, then we are no longer completing the first token (command name)
- if begidx > 0:
- self._completion_for_command(text, line, begidx, endidx, shortcut_to_restore)
+ self._perform_completion(text, line, begidx, endidx, custom_settings)
- # 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)
+ # Check if we need to restore a shortcut in the tab completions
+ # so it doesn't get erased from the command line
+ if shortcut_to_restore:
+ self.completion_matches = [shortcut_to_restore + match for match in self.completion_matches]
# 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:
@@ -1841,20 +1875,6 @@ class Cmd(cmd.Cmd):
rl_force_redisplay()
return None
- def _complete_argparse_command(self, text: str, line: str, begidx: int, endidx: int, *,
- argparser: argparse.ArgumentParser,
- preserve_quotes: bool,
- cmd_set: Optional[CommandSet] = None) -> List[str]:
- """Completion function for argparse commands"""
- from .argparse_completer import ArgparseCompleter
- completer = ArgparseCompleter(argparser, self)
- tokens, raw_tokens = self.tokens_for_completion(line, begidx, endidx)
-
- # To have tab completion parsing match command line parsing behavior,
- # use preserve_quotes to determine if we parse the quoted or unquoted tokens.
- tokens_to_parse = raw_tokens if preserve_quotes else tokens
- return completer.complete_command(tokens_to_parse, text, line, begidx, endidx, cmd_set=cmd_set)
-
def in_script(self) -> bool:
"""Return whether a text script is running"""
return self._current_script_dir is not None
@@ -2507,36 +2527,115 @@ class Cmd(cmd.Cmd):
# Set apply_style to False so default_error's style is not overridden
self.perror(err_msg, apply_style=False)
- def read_input(self, prompt: str, *, allow_completion: bool = False) -> str:
+ def read_input(self, prompt: str, *,
+ history: Optional[List[str]] = None,
+ completion_mode: utils.CompletionMode = utils.CompletionMode.NONE,
+ preserve_quotes: bool = False,
+ choices: Iterable = None,
+ choices_provider: Optional[Callable] = None,
+ completer: Optional[Callable] = None,
+ parser: Optional[argparse.ArgumentParser] = None) -> str:
"""
- Read input from appropriate stdin value. Also allows you to disable tab completion while input is being read.
+ Read input from appropriate stdin value. Also supports tab completion and up-arrow history while
+ input is being entered.
:param prompt: prompt to display to user
- :param allow_completion: if True, then tab completion of commands is enabled. This generally should be
- set to False unless reading the command line. Defaults to False.
+ :param history: optional list of strings to use for up-arrow history. If completion_mode is
+ CompletionMode.COMMANDS and this is None, then cmd2's command list history will
+ be used. The passed in history will not be edited. It is the caller's responsibility
+ to add the returned input to history if desired. Defaults to None.
+ :param completion_mode: tells what type of tab completion to support. Tab completion only works when
+ self.use_rawinput is True and sys.stdin is a terminal. Defaults to
+ CompletionMode.NONE.
+
+ The following optional settings apply when completion_mode is CompletionMode.CUSTOM:
+
+ :param preserve_quotes: if True, then quoted tokens will keep their quotes when processed by
+ ArgparseCompleter. This is helpful in cases when you're tab completing
+ flag-like tokens (e.g. -o, --option) and you don't want them to be
+ treated as argparse flags when quoted. Set this to True if you plan
+ on passing the string to argparse with the tokens still quoted.
+
+ A maximum of one of these should be provided:
+
+ :param choices: iterable of accepted values for single argument
+ :param choices_provider: function that provides choices for single argument
+ :param completer: tab completion function that provides choices for single argument
+ :param parser: an argument parser which supports the tab completion of multiple arguments
+
:return: the line read from stdin with all trailing new lines removed
:raises: any exceptions raised by input() and stdin.readline()
"""
- completion_disabled = False
- orig_completer = None
+ readline_configured = False
+ saved_completer = None # type: Optional[Callable]
+ saved_history = None # type: Optional[List[str]]
+
+ def configure_readline():
+ """Configure readline tab completion and history"""
+ nonlocal readline_configured
+ nonlocal saved_completer
+ nonlocal saved_history
+ nonlocal parser
+
+ if readline_configured: # pragma: no cover
+ return
- def disable_completion():
- """Turn off completion while entering input"""
- nonlocal orig_completer
- nonlocal completion_disabled
+ # Configure tab completion
+ if self._completion_supported():
+ saved_completer = readline.get_completer()
+
+ # Disable completion
+ if completion_mode == utils.CompletionMode.NONE:
+ # noinspection PyUnusedLocal
+ def complete_none(text: str, state: int): # pragma: no cover
+ return None
+ complete_func = complete_none
- if self._completion_supported() and not completion_disabled:
- orig_completer = readline.get_completer()
- readline.set_completer(lambda *args, **kwargs: None)
- completion_disabled = True
+ # Complete commands
+ elif completion_mode == utils.CompletionMode.COMMANDS:
+ complete_func = self.complete
+
+ # Set custom completion settings
+ else:
+ if parser is None:
+ parser = DEFAULT_ARGUMENT_PARSER(add_help=False)
+ parser.add_argument('arg', suppress_tab_hint=True, choices=choices,
+ choices_provider=choices_provider, completer=completer)
- def enable_completion():
- """Restore tab completion when finished entering input"""
- nonlocal completion_disabled
+ custom_settings = utils.CustomCompletionSettings(parser, preserve_quotes=preserve_quotes)
+ complete_func = functools.partial(self.complete, custom_settings=custom_settings)
- if self._completion_supported() and completion_disabled:
- readline.set_completer(orig_completer)
- completion_disabled = False
+ readline.set_completer(complete_func)
+
+ # Overwrite history if not completing commands or new history was provided
+ if completion_mode != utils.CompletionMode.COMMANDS or history is not None:
+ saved_history = []
+ for i in range(1, readline.get_current_history_length() + 1):
+ # noinspection PyArgumentList
+ saved_history.append(readline.get_history_item(i))
+
+ readline.clear_history()
+ if history is not None:
+ for item in history:
+ readline.add_history(item)
+
+ readline_configured = True
+
+ def restore_readline():
+ """Restore readline tab completion and history"""
+ nonlocal readline_configured
+ if not readline_configured: # pragma: no cover
+ return
+
+ if self._completion_supported():
+ readline.set_completer(saved_completer)
+
+ if saved_history is not None:
+ readline.clear_history()
+ for item in saved_history:
+ readline.add_history(item)
+
+ readline_configured = False
# Check we are reading from sys.stdin
if self.use_rawinput:
@@ -2546,15 +2645,11 @@ class Cmd(cmd.Cmd):
safe_prompt = rl_make_safe_prompt(prompt)
with self.sigint_protection:
- # Check if tab completion should be disabled
- if not allow_completion:
- disable_completion()
+ configure_readline()
line = input(safe_prompt)
finally:
with self.sigint_protection:
- # Check if we need to re-enable tab completion
- if not allow_completion:
- enable_completion()
+ restore_readline()
else:
line = input()
if self.echo:
@@ -2598,7 +2693,7 @@ class Cmd(cmd.Cmd):
self.terminal_lock.release()
except RuntimeError:
pass
- return self.read_input(prompt, allow_completion=True)
+ return self.read_input(prompt, completion_mode=utils.CompletionMode.COMMANDS)
except EOFError:
return 'eof'
finally:
@@ -2607,7 +2702,7 @@ class Cmd(cmd.Cmd):
def _set_up_cmd2_readline(self) -> _SavedReadlineSettings:
"""
- Set up readline with cmd2-specific settings
+ Called at beginning of command loop to set up readline with cmd2-specific settings
:return: Class containing saved readline settings
"""
@@ -2642,7 +2737,7 @@ class Cmd(cmd.Cmd):
def _restore_readline(self, readline_settings: _SavedReadlineSettings):
"""
- Restore saved readline settings
+ Called at end of command loop to restore saved readline settings
:param readline_settings: the readline settings to restore
"""
@@ -2739,9 +2834,9 @@ class Cmd(cmd.Cmd):
'overwritten')
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:
@@ -2784,7 +2879,7 @@ class Cmd(cmd.Cmd):
alias_delete_parser = DEFAULT_ARGUMENT_PARSER(description=alias_delete_description)
alias_delete_parser.add_argument('-a', '--all', action='store_true', help="delete all aliases")
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')
@as_subcommand_to('alias', 'delete', alias_delete_parser, help=alias_delete_help)
def _alias_delete(self, args: argparse.Namespace) -> None:
@@ -2815,7 +2910,7 @@ class Cmd(cmd.Cmd):
"Use this option when saving to a startup script that\n"
"should silently create aliases.")
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:
@@ -2901,9 +2996,9 @@ class Cmd(cmd.Cmd):
'overwritten')
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:
@@ -2992,7 +3087,7 @@ class Cmd(cmd.Cmd):
macro_delete_parser = DEFAULT_ARGUMENT_PARSER(description=macro_delete_description)
macro_delete_parser.add_argument('-a', '--all', action='store_true', help="delete all macros")
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')
@as_subcommand_to('macro', 'delete', macro_delete_parser, help=macro_delete_help)
def _macro_delete(self, args: argparse.Namespace) -> None:
@@ -3023,7 +3118,7 @@ class Cmd(cmd.Cmd):
"Use this option when saving to a startup script that\n"
"should silently create macros.")
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:
@@ -3049,7 +3144,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]:
@@ -3066,21 +3161,18 @@ class Cmd(cmd.Cmd):
if func is None or argparser is None:
return []
- # Combine the command and its subcommand tokens for the ArgparseCompleter
- tokens = [command] + arg_tokens['subcommands']
-
from .argparse_completer import ArgparseCompleter
completer = ArgparseCompleter(argparser, self)
- return completer.complete_subcommand_help(tokens, text, line, begidx, endidx)
+ return completer.complete_subcommand_help(text, line, begidx, endidx, arg_tokens['subcommands'])
help_parser = DEFAULT_ARGUMENT_PARSER(description="List available commands or provide "
"detailed help for a specific command")
help_parser.add_argument('-v', '--verbose', action='store_true',
help="print a list of all commands with descriptions of each")
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)
# Get rid of cmd's complete_help() functions so ArgparseCompleter will complete the help command
if getattr(cmd.Cmd, 'complete_help', None) is not None:
@@ -3102,10 +3194,9 @@ class Cmd(cmd.Cmd):
if func is not None and argparser is not None:
from .argparse_completer import ArgparseCompleter
completer = ArgparseCompleter(argparser, self)
- tokens = [args.command] + args.subcommands
# Set end to blank so the help output matches how it looks when "command -h" is used
- self.poutput(completer.format_help(tokens), end='')
+ self.poutput(completer.format_help(args.subcommands), end='')
# If there is no help information then print an error
elif help_func is None and (func is None or not func.__doc__):
@@ -3310,10 +3401,6 @@ class Cmd(cmd.Cmd):
if not response:
continue
- if rl_type != RlType.NONE:
- hlen = readline.get_current_history_length()
- if hlen >= 1:
- readline.remove_history_item(hlen - 1)
try:
choice = int(response)
if choice < 1:
@@ -3340,17 +3427,15 @@ 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)
# Use raw_tokens since quotes have been preserved
_, raw_tokens = self.tokens_for_completion(line, begidx, endidx)
- return completer.complete_command(raw_tokens, text, line, begidx, endidx)
+ return completer.complete(text, line, begidx, endidx, raw_tokens[1:])
# When tab completing value, we recreate the set command parser with a value argument specific to
# the settable being edited. To make this easier, define a parent parser with all the common elements.
@@ -3361,12 +3446,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, suppress_tab_hint=True)
+ completer=complete_set_value, suppress_tab_hint=True)
# 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)
@@ -3427,9 +3512,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)
@@ -3720,9 +3805,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]:
@@ -3816,10 +3901,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')
@@ -4126,7 +4211,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:
@@ -4169,8 +4254,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]:
@@ -4482,8 +4567,6 @@ class Cmd(cmd.Cmd):
command being disabled.
ex: message_to_print = "{} is currently disabled".format(COMMAND_NAME)
"""
- import functools
-
# If the commands is already disabled, then return
if command in self.disabled_commands:
return
diff --git a/cmd2/constants.py b/cmd2/constants.py
index 552c1a74..9f29be86 100644
--- a/cmd2/constants.py
+++ b/cmd2/constants.py
@@ -57,4 +57,3 @@ PARSER_ATTR_COMMANDSET = 'command_set'
# custom attributes added to argparse Namespaces
NS_ATTR_SUBCMD_HANDLER = '__subcmd_handler__'
-NS_ATTR_STATEMENT = '__statement__'
diff --git a/cmd2/decorators.py b/cmd2/decorators.py
index 4ee61754..6bc57b90 100644
--- a/cmd2/decorators.py
+++ b/cmd2/decorators.py
@@ -299,9 +299,6 @@ def with_argparser(parser: argparse.ArgumentParser, *,
except SystemExit:
raise Cmd2ArgparseError
else:
- # Add statement to Namespace as __statement__ (this is deprecated and will be removed in 2.0)
- setattr(ns, constants.NS_ATTR_STATEMENT, statement)
-
# Add wrapped statement to Namespace as cmd2_statement
setattr(ns, 'cmd2_statement', Cmd2AttributeWrapper(statement))
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/rl_utils.py b/cmd2/rl_utils.py
index 099d76b7..9163efd8 100644
--- a/cmd2/rl_utils.py
+++ b/cmd2/rl_utils.py
@@ -2,8 +2,8 @@
"""
Imports the proper readline for the platform and provides utility functions for it
"""
+import enum
import sys
-from enum import Enum
# Prefer statically linked gnureadline if available (for macOS compatibility due to issues with libedit)
try:
@@ -19,7 +19,7 @@ except ImportError:
pass
-class RlType(Enum):
+class RlType(enum.Enum):
"""Readline library types we recognize"""
GNU = 1
PYREADLINE = 2
diff --git a/cmd2/table_creator.py b/cmd2/table_creator.py
index 7a5c826c..fc2398f2 100644
--- a/cmd2/table_creator.py
+++ b/cmd2/table_creator.py
@@ -6,10 +6,10 @@ The general use case is to inherit from TableCreator to create a table class wit
There are already implemented and ready-to-use examples of this below TableCreator's code.
"""
import copy
+import enum
import functools
import io
from collections import deque
-from enum import Enum
from typing import Any, Optional, Sequence, Tuple, Union
from wcwidth import wcwidth
@@ -39,14 +39,14 @@ EMPTY = ''
SPACE = ' '
-class HorizontalAlignment(Enum):
+class HorizontalAlignment(enum.Enum):
"""Horizontal alignment of text in a cell"""
LEFT = 1
CENTER = 2
RIGHT = 3
-class VerticalAlignment(Enum):
+class VerticalAlignment(enum.Enum):
"""Vertical alignment of text in a cell"""
TOP = 1
MIDDLE = 2
diff --git a/cmd2/utils.py b/cmd2/utils.py
index b6dadf1c..cd716083 100644
--- a/cmd2/utils.py
+++ b/cmd2/utils.py
@@ -1,8 +1,10 @@
# coding=utf-8
"""Shared utility functions"""
+import argparse
import collections
import collections.abc as collections_abc
+import enum
import functools
import glob
import inspect
@@ -13,7 +15,6 @@ import sys
import threading
import unicodedata
-from enum import Enum
from typing import Any, Callable, Dict, IO, Iterable, List, Optional, TextIO, Type, Union
from . import constants
@@ -75,40 +76,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
@@ -130,17 +104,8 @@ 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)
-
- Note:
- For choices_method and completer_method, 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.
+ :param choices_provider: function that provides choices for this argument
+ :param completer: tab completion function that provides choices for this argument
"""
if val_type == bool:
val_type = str_to_bool
@@ -151,10 +116,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]],
@@ -695,23 +658,7 @@ 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):
+class TextAlignment(enum.Enum):
"""Horizontal text alignment"""
LEFT = 1
CENTER = 2
@@ -1081,3 +1028,37 @@ def get_defining_class(meth) -> Type:
if isinstance(cls, type):
return cls
return getattr(meth, '__objclass__', None) # handle special descriptor objects
+
+
+class CompletionMode(enum.Enum):
+ """Enum for what type of tab completion to perform in cmd2.Cmd.read_input()"""
+ # Tab completion will be disabled during read_input() call
+ # Use of custom up-arrow history supported
+ NONE = 1
+
+ # read_input() will tab complete cmd2 commands and their arguments
+ # cmd2's command line history will be used for up arrow if history is not provided.
+ # Otherwise use of custom up-arrow history supported.
+ COMMANDS = 2
+
+ # read_input() will tab complete based on one of its following parameters:
+ # choices, choices_provider, completer, parser
+ # Use of custom up-arrow history supported
+ CUSTOM = 3
+
+
+class CustomCompletionSettings:
+ """Used by cmd2.Cmd.complete() to tab complete strings other than command arguments"""
+ def __init__(self, parser: argparse.ArgumentParser, *, preserve_quotes: bool = False):
+ """
+ Initializer
+
+ :param parser: arg parser defining format of string being tab completed
+ :param preserve_quotes: if True, then quoted tokens will keep their quotes when processed by
+ ArgparseCompleter. This is helpful in cases when you're tab completing
+ flag-like tokens (e.g. -o, --option) and you don't want them to be
+ treated as argparse flags when quoted. Set this to True if you plan
+ on passing the string to argparse with the tokens still quoted.
+ """
+ self.parser = parser
+ self.preserve_quotes = preserve_quotes
diff --git a/docs/api/exceptions.rst b/docs/api/exceptions.rst
index db23eb0a..98afa97a 100644
--- a/docs/api/exceptions.rst
+++ b/docs/api/exceptions.rst
@@ -12,3 +12,6 @@ Custom cmd2 exceptions
.. autoclass:: cmd2.exceptions.CommandSetRegistrationError
:members:
+
+.. autoclass:: cmd2.exceptions.CompletionError
+ :members:
diff --git a/docs/api/utils.rst b/docs/api/utils.rst
index d9166401..9276587f 100644
--- a/docs/api/utils.rst
+++ b/docs/api/utils.rst
@@ -39,10 +39,28 @@ IO Handling
Tab Completion
--------------
-.. autoclass:: cmd2.utils.CompletionError
- :members:
+.. autoclass:: cmd2.utils.CompletionMode
+
+ .. attribute:: NONE
+
+ Tab completion will be disabled during read_input() call. Use of custom
+ up-arrow history supported.
+
+ .. attribute:: COMMANDS
+
+ read_input() will tab complete cmd2 commands and their arguments.
+ cmd2's command line history will be used for up arrow if history is not
+ provided. Otherwise use of custom up-arrow history supported.
-.. autofunction:: cmd2.utils.basic_complete
+ .. attribute:: CUSTOM
+
+ read_input() will tab complete based on one of its following parameters
+ (choices, choices_provider, completer, parser). Use of custom up-arrow
+ history supported
+
+.. autoclass:: cmd2.utils.CustomCompletionSettings
+
+ .. automethod:: __init__
Text Alignment
diff --git a/docs/features/argument_processing.rst b/docs/features/argument_processing.rst
index 06f48f82..9abd9c65 100644
--- a/docs/features/argument_processing.rst
+++ b/docs/features/argument_processing.rst
@@ -392,11 +392,7 @@ argparse arguments.
- ``cmd2_statement`` - ``cmd2.Cmd2AttributeWrapper`` object containing
``cmd2.Statement`` object that was created when parsing the command line.
-- ``__statement__`` - ``cmd2.Statement`` object that was created when parsing
- the command line. (This is deprecated and will be removed in 2.0.0.) Use
- ``cmd2_statement`` instead.
-
-- ``__subcmd_handler__`` - used by cmd2 to identify the handler for a
- subcommand created with ``@cmd2.as_subcommand_to`` decorator.
- ``cmd2_handler`` - ``cmd2.Cmd2AttributeWrapper`` object containing
a subcommand handler function or ``None`` if one was not set.
+- ``__subcmd_handler__`` - used by cmd2 to identify the handler for a
+ subcommand created with ``@cmd2.as_subcommand_to`` decorator.
diff --git a/docs/features/completion.rst b/docs/features/completion.rst
index 77a51136..3894a4eb 100644
--- a/docs/features/completion.rst
+++ b/docs/features/completion.rst
@@ -38,7 +38,7 @@ Included Tab Completion Functions
---------------------------------
``cmd2`` provides the following tab completion functions
-- :attr:`cmd2.utils.basic_complete` - helper method for tab completion against
+- :attr:`cmd2.Cmd.basic_complete` - helper method for tab completion against
a list
- :attr:`cmd2.Cmd.path_complete` - helper method provides flexible tab
completion of file system paths
@@ -79,7 +79,7 @@ to be reported to the user. These include the following example cases:
is invalid
- Tab completion hints
-``cmd2`` provides the :class:`cmd2.utils.CompletionError` exception class for
+``cmd2`` provides the :class:`cmd2.exceptions.CompletionError` exception class for
this capability. If an error occurs in which it is more desirable to display
a message than a stack trace, then raise a ``CompletionError``. By default, the
message displays in red like an error. However, ``CompletionError`` has a
@@ -89,24 +89,25 @@ completion hints.
.. _argparse-based:
+
Tab Completion Using argparse Decorators
----------------------------------------
When using one the argparse-based :ref:`api/decorators:cmd2.decorators`,
``cmd2`` provides automatic tab completion of flag names.
-Tab completion of argument values can be configured by using one of five
+Tab completion of argument values can be configured by using one of three
parameters to :meth:`argparse.ArgumentParser.add_argument`
- ``choices``
-- ``choices_function`` or ``choices_method``
-- ``completer_function`` or ``completer_method``
+- ``choices_provider``
+- ``completer``
See the arg_decorators_ or colors_ example for a demonstration of how to
use the ``choices`` parameter. See the argparse_completion_ example for a
-demonstration of how to use the ``choices_function`` and ``choices_method``
-parameters. See the arg_decorators_ or argparse_completion_ example for a
-demonstration of how to use the ``completer_method`` parameter.
+demonstration of how to use the ``choices_provider`` parameter. See the
+arg_decorators_ or argparse_completion_ example for a demonstration
+of how to use the ``completer`` parameter.
When tab completing flags or argument values for a ``cmd2`` command using
one of these decorators, ``cmd2`` keeps track of state so that once a flag has
@@ -126,12 +127,23 @@ When tab completing things like a unique ID from a database, it can often be
beneficial to provide the user with some extra context about the item being
completed, such as a description. To facilitate this, ``cmd2`` defines the
:class:`cmd2.argparse_custom.CompletionItem` class which can be returned from
-any of the 4 completion functions: ``choices_function``, ``choices_method``,
-``completion_function``, or ``completion_method``.
+any of the 3 completion parameters: ``choices``, ``choices_provider``, and
+``completer``.
See the argparse_completion_ example or the implementation of the built-in
:meth:`~cmd2.Cmd.do_set` command for demonstration of how this is used.
+
+Custom Completion with ``read_input()``
+--------------------------------------------------
+
+``cmd2`` provides :attr:`cmd2.Cmd.read_input` as an alternative to Python's
+``input()`` function. ``read_input`` supports configurable tab completion and
+up-arrow history at the prompt. See read_input_ example for a demonstration.
+
+.. _read_input: https://github.com/python-cmd2/cmd2/blob/master/examples/read_input.py
+
+
For More Information
--------------------
diff --git a/examples/arg_decorators.py b/examples/arg_decorators.py
index a085341d..9ffcd8ce 100755
--- a/examples/arg_decorators.py
+++ b/examples/arg_decorators.py
@@ -18,7 +18,7 @@ class ArgparsingApp(cmd2.Cmd):
help='add comma for thousands separator')
fsize_parser.add_argument('-u', '--unit', choices=['MB', 'KB'], help='unit to display size in')
fsize_parser.add_argument('file_path', help='path of file',
- completer_method=cmd2.Cmd.path_complete)
+ completer=cmd2.Cmd.path_complete)
@cmd2.with_argparser(fsize_parser)
def do_fsize(self, args: argparse.Namespace) -> None:
diff --git a/examples/argparse_completion.py b/examples/argparse_completion.py
index e44533b3..c2cb31f6 100644
--- a/examples/argparse_completion.py
+++ b/examples/argparse_completion.py
@@ -6,62 +6,19 @@ A simple example demonstrating how to integrate tab completion with argparse-bas
import argparse
from typing import Dict, List
-from cmd2 import Cmd, Cmd2ArgumentParser, CompletionItem, with_argparser
-from cmd2.utils import CompletionError, basic_complete
+from cmd2 import Cmd, Cmd2ArgumentParser, CompletionError, CompletionItem, with_argparser
# Data source for argparse.choices
food_item_strs = ['Pizza', 'Ham', 'Ham Sandwich', 'Potato']
-def choices_function() -> List[str]:
- """Choices functions are useful when the choice list is dynamically generated (e.g. from data in a database)"""
- return ['a', 'dynamic', 'list', 'goes', 'here']
-
-
-def completer_function(text: str, line: str, begidx: int, endidx: int) -> List[str]:
- """
- A tab completion function not dependent on instance data. 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.
- """
- match_against = ['a', 'dynamic', 'list', 'goes', 'here']
- return basic_complete(text, line, begidx, endidx, match_against)
-
-
-def choices_completion_item() -> List[CompletionItem]:
- """Return CompletionItem instead of strings. These give more context to what's being tab completed."""
- items = \
- {
- 1: "My item",
- 2: "Another item",
- 3: "Yet another item"
- }
- return [CompletionItem(item_id, description) for item_id, description in items.items()]
-
-
-def choices_arg_tokens(arg_tokens: Dict[str, List[str]]) -> List[str]:
- """
- If a choices or completer function/method takes a value called arg_tokens, then it will be
- passed a dictionary that maps the command line tokens up through the one being completed
- to their argparse argument name. All values of the arg_tokens dictionary are lists, even if
- a particular argument expects only 1 token.
- """
- # Check if choices_function flag has appeared
- values = ['choices_function', 'flag']
- if 'choices_function' in arg_tokens:
- values.append('is {}'.format(arg_tokens['choices_function'][0]))
- else:
- values.append('not supplied')
- return values
-
-
class ArgparseCompletion(Cmd):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.sport_item_strs = ['Bat', 'Basket', 'Basketball', 'Football', 'Space Ball']
- def choices_method(self) -> List[str]:
- """Choices methods are useful when the choice list is based on instance data of your application"""
+ def choices_provider(self) -> List[str]:
+ """A choices provider is useful when the choice list is based on instance data of your application"""
return self.sport_item_strs
def choices_completion_error(self) -> List[str]:
@@ -76,6 +33,33 @@ class ArgparseCompletion(Cmd):
return self.sport_item_strs
raise CompletionError("debug must be true")
+ # noinspection PyMethodMayBeStatic
+ def choices_completion_item(self) -> List[CompletionItem]:
+ """Return CompletionItem instead of strings. These give more context to what's being tab completed."""
+ items = \
+ {
+ 1: "My item",
+ 2: "Another item",
+ 3: "Yet another item"
+ }
+ return [CompletionItem(item_id, description) for item_id, description in items.items()]
+
+ # noinspection PyMethodMayBeStatic
+ def choices_arg_tokens(self, arg_tokens: Dict[str, List[str]]) -> List[str]:
+ """
+ If a choices or completer function/method takes a value called arg_tokens, then it will be
+ passed a dictionary that maps the command line tokens up through the one being completed
+ to their argparse argument name. All values of the arg_tokens dictionary are lists, even if
+ a particular argument expects only 1 token.
+ """
+ # Check if choices_provider flag has appeared
+ values = ['choices_provider', 'flag']
+ if 'choices_provider' in arg_tokens:
+ values.append('is {}'.format(arg_tokens['choices_provider'][0]))
+ else:
+ values.append('not supplied')
+ return values
+
# Parser for example command
example_parser = Cmd2ArgumentParser(description="Command demonstrating tab completion with argparse\n"
"Notice even the flags of this command tab complete")
@@ -85,29 +69,25 @@ class ArgparseCompletion(Cmd):
example_parser.add_argument('--choices', choices=food_item_strs, metavar="CHOICE",
help="tab complete using choices")
- # Tab complete from choices provided by a choices function and choices method
- example_parser.add_argument('--choices_function', choices_function=choices_function,
- help="tab complete using a choices_function")
- example_parser.add_argument('--choices_method', choices_method=choices_method,
- help="tab complete using a choices_method")
+ # Tab complete from choices provided by a choices_provider
+ example_parser.add_argument('--choices_provider', choices_provider=choices_provider,
+ help="tab complete using a choices_provider")
- # Tab complete using a completer function and completer method
- example_parser.add_argument('--completer_function', completer_function=completer_function,
- help="tab complete using a completer_function")
- example_parser.add_argument('--completer_method', completer_method=Cmd.path_complete,
- help="tab complete using a completer_method")
+ # Tab complete using a completer
+ example_parser.add_argument('--completer', completer=Cmd.path_complete,
+ help="tab complete using a completer")
# Demonstrate raising a CompletionError while tab completing
- example_parser.add_argument('--completion_error', choices_method=choices_completion_error,
+ example_parser.add_argument('--completion_error', choices_provider=choices_completion_error,
help="raise a CompletionError while tab completing if debug is False")
# Demonstrate returning CompletionItems instead of strings
- example_parser.add_argument('--completion_item', choices_function=choices_completion_item, metavar="ITEM_ID",
+ example_parser.add_argument('--completion_item', choices_provider=choices_completion_item, metavar="ITEM_ID",
descriptive_header="Description",
help="demonstrate use of CompletionItems")
# Demonstrate use of arg_tokens dictionary
- example_parser.add_argument('--arg_tokens', choices_function=choices_arg_tokens,
+ example_parser.add_argument('--arg_tokens', choices_provider=choices_arg_tokens,
help="demonstrate use of arg_tokens dictionary")
@with_argparser(example_parser)
diff --git a/examples/async_printing.py b/examples/async_printing.py
index a136d8e2..29af2d64 100755
--- a/examples/async_printing.py
+++ b/examples/async_printing.py
@@ -35,7 +35,7 @@ class AlerterApp(cmd2.Cmd):
self.prompt = "(APR)> "
# The thread that will asynchronously alert the user of events
- self._stop_thread = False
+ self._stop_event = threading.Event()
self._alerter_thread = threading.Thread()
self._alert_count = 0
self._next_alert_time = 0
@@ -50,7 +50,7 @@ class AlerterApp(cmd2.Cmd):
# Therefore this is the best place to start the alerter thread since there is no risk of it alerting
# before the prompt is displayed. You can also start it via a command if its not something that should
# be running during the entire application. See do_start_alerts().
- self._stop_thread = False
+ self._stop_event.clear()
self._alerter_thread = threading.Thread(name='alerter', target=self._alerter_thread_func)
self._alerter_thread.start()
@@ -61,7 +61,7 @@ class AlerterApp(cmd2.Cmd):
# After this function returns, cmdloop() releases self.terminal_lock which could make the alerter
# thread think the prompt is on screen. Therefore this is the best place to stop the alerter thread.
# You can also stop it via a command. See do_stop_alerts().
- self._stop_thread = True
+ self._stop_event.set()
if self._alerter_thread.is_alive():
self._alerter_thread.join()
@@ -70,13 +70,13 @@ class AlerterApp(cmd2.Cmd):
if self._alerter_thread.is_alive():
print("The alert thread is already started")
else:
- self._stop_thread = False
+ self._stop_event.clear()
self._alerter_thread = threading.Thread(name='alerter', target=self._alerter_thread_func)
self._alerter_thread.start()
def do_stop_alerts(self, _):
""" Stops the alerter thread """
- self._stop_thread = True
+ self._stop_event.set()
if self._alerter_thread.is_alive():
self._alerter_thread.join()
else:
@@ -166,7 +166,7 @@ class AlerterApp(cmd2.Cmd):
self._alert_count = 0
self._next_alert_time = 0
- while not self._stop_thread:
+ while not self._stop_event.is_set():
# Always acquire terminal_lock before printing alerts or updating the prompt
# To keep the app responsive, do not block on this call
if self.terminal_lock.acquire(blocking=False):
@@ -191,7 +191,7 @@ class AlerterApp(cmd2.Cmd):
# Don't forget to release the lock
self.terminal_lock.release()
- time.sleep(0.5)
+ self._stop_event.wait(0.5)
if __name__ == '__main__':
diff --git a/examples/modular_commands/commandset_basic.py b/examples/modular_commands/commandset_basic.py
index 2ceda439..9c94e01e 100644
--- a/examples/modular_commands/commandset_basic.py
+++ b/examples/modular_commands/commandset_basic.py
@@ -4,8 +4,7 @@ A simple example demonstrating a loadable command set
"""
from typing import List
-from cmd2 import Cmd, CommandSet, Statement, with_category, with_default_category
-from cmd2.utils import CompletionError
+from cmd2 import Cmd, CommandSet, CompletionError, Statement, with_category, with_default_category
@with_default_category('Basic Completion')
diff --git a/examples/modular_commands/commandset_complex.py b/examples/modular_commands/commandset_complex.py
index 579c0677..94f8b5f4 100644
--- a/examples/modular_commands/commandset_complex.py
+++ b/examples/modular_commands/commandset_complex.py
@@ -8,7 +8,6 @@ import argparse
from typing import List
import cmd2
-from cmd2 import utils
@cmd2.with_default_category('Fruits')
@@ -42,7 +41,7 @@ class CommandSetA(cmd2.CommandSet):
self._cmd.poutput(', '.join(['{}']*len(args)).format(*args))
def complete_durian(self, text: str, line: str, begidx: int, endidx: int) -> List[str]:
- return utils.basic_complete(text, line, begidx, endidx, ['stinks', 'smells', 'disgusting'])
+ return self._cmd.basic_complete(text, line, begidx, endidx, ['stinks', 'smells', 'disgusting'])
elderberry_parser = cmd2.Cmd2ArgumentParser()
elderberry_parser.add_argument('arg1')
diff --git a/examples/modular_commands_main.py b/examples/modular_commands_main.py
index b698e00f..1b0ec64d 100644
--- a/examples/modular_commands_main.py
+++ b/examples/modular_commands_main.py
@@ -5,58 +5,13 @@ A complex example demonstrating a variety of methods to load CommandSets using a
with examples of how to integrate tab completion with argparse-based commands.
"""
import argparse
-from typing import Dict, Iterable, List, Optional
+from typing import Iterable, List, Optional
-from cmd2 import Cmd, Cmd2ArgumentParser, CommandSet, CompletionItem, with_argparser
-from cmd2.utils import CompletionError, basic_complete
from modular_commands.commandset_basic import BasicCompletionCommandSet # noqa: F401
from modular_commands.commandset_complex import CommandSetA # noqa: F401
from modular_commands.commandset_custominit import CustomInitCommandSet # noqa: F401
-# Data source for argparse.choices
-food_item_strs = ['Pizza', 'Ham', 'Ham Sandwich', 'Potato']
-
-
-def choices_function() -> List[str]:
- """Choices functions are useful when the choice list is dynamically generated (e.g. from data in a database)"""
- return ['a', 'dynamic', 'list', 'goes', 'here']
-
-
-def completer_function(text: str, line: str, begidx: int, endidx: int) -> List[str]:
- """
- A tab completion function not dependent on instance data. 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.
- """
- match_against = ['a', 'dynamic', 'list', 'goes', 'here']
- return basic_complete(text, line, begidx, endidx, match_against)
-
-
-def choices_completion_item() -> List[CompletionItem]:
- """Return CompletionItem instead of strings. These give more context to what's being tab completed."""
- items = \
- {
- 1: "My item",
- 2: "Another item",
- 3: "Yet another item"
- }
- return [CompletionItem(item_id, description) for item_id, description in items.items()]
-
-
-def choices_arg_tokens(arg_tokens: Dict[str, List[str]]) -> List[str]:
- """
- If a choices or completer function/method takes a value called arg_tokens, then it will be
- passed a dictionary that maps the command line tokens up through the one being completed
- to their argparse argument name. All values of the arg_tokens dictionary are lists, even if
- a particular argument expects only 1 token.
- """
- # Check if choices_function flag has appeared
- values = ['choices_function', 'flag']
- if 'choices_function' in arg_tokens:
- values.append('is {}'.format(arg_tokens['choices_function'][0]))
- else:
- values.append('not supplied')
- return values
+from cmd2 import Cmd, Cmd2ArgumentParser, CommandSet, with_argparser
class WithCommandSets(Cmd):
@@ -64,55 +19,26 @@ class WithCommandSets(Cmd):
super().__init__(command_sets=command_sets)
self.sport_item_strs = ['Bat', 'Basket', 'Basketball', 'Football', 'Space Ball']
- def choices_method(self) -> List[str]:
- """Choices methods are useful when the choice list is based on instance data of your application"""
+ def choices_provider(self) -> List[str]:
+ """A choices provider is useful when the choice list is based on instance data of your application"""
return self.sport_item_strs
- def choices_completion_error(self) -> List[str]:
- """
- CompletionErrors can be raised if an error occurs while tab completing.
-
- 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
- """
- if self.debug:
- return self.sport_item_strs
- raise CompletionError("debug must be true")
-
# Parser for example command
example_parser = Cmd2ArgumentParser(description="Command demonstrating tab completion with argparse\n"
"Notice even the flags of this command tab complete")
# Tab complete from a list using argparse choices. Set metavar if you don't
# want the entire choices list showing in the usage text for this command.
- example_parser.add_argument('--choices', choices=food_item_strs, metavar="CHOICE",
+ example_parser.add_argument('--choices', choices=['some', 'choices', 'here'], metavar="CHOICE",
help="tab complete using choices")
- # Tab complete from choices provided by a choices function and choices method
- example_parser.add_argument('--choices_function', choices_function=choices_function,
- help="tab complete using a choices_function")
- example_parser.add_argument('--choices_method', choices_method=choices_method,
- help="tab complete using a choices_method")
-
- # Tab complete using a completer function and completer method
- example_parser.add_argument('--completer_function', completer_function=completer_function,
- help="tab complete using a completer_function")
- example_parser.add_argument('--completer_method', completer_method=Cmd.path_complete,
- help="tab complete using a completer_method")
-
- # Demonstrate raising a CompletionError while tab completing
- example_parser.add_argument('--completion_error', choices_method=choices_completion_error,
- help="raise a CompletionError while tab completing if debug is False")
-
- # Demonstrate returning CompletionItems instead of strings
- example_parser.add_argument('--completion_item', choices_function=choices_completion_item, metavar="ITEM_ID",
- descriptive_header="Description",
- help="demonstrate use of CompletionItems")
+ # Tab complete from choices provided by a choices provider
+ example_parser.add_argument('--choices_provider', choices_provider=choices_provider,
+ help="tab complete using a choices_provider")
- # Demonstrate use of arg_tokens dictionary
- example_parser.add_argument('--arg_tokens', choices_function=choices_arg_tokens,
- help="demonstrate use of arg_tokens dictionary")
+ # Tab complete using a completer
+ example_parser.add_argument('--completer', completer=Cmd.path_complete,
+ help="tab complete using a completer")
@with_argparser(example_parser)
def do_example(self, _: argparse.Namespace) -> None:
diff --git a/examples/read_input.py b/examples/read_input.py
new file mode 100644
index 00000000..e772a106
--- /dev/null
+++ b/examples/read_input.py
@@ -0,0 +1,112 @@
+#!/usr/bin/env python
+# coding=utf-8
+"""
+A simple example demonstrating the various ways to call cmd2.Cmd.read_input() for input history and tab completion
+"""
+from typing import List
+
+import cmd2
+
+EXAMPLE_COMMANDS = "Example Commands"
+
+
+class ReadInputApp(cmd2.Cmd):
+ def __init__(self, *args, **kwargs) -> None:
+ super().__init__(*args, **kwargs)
+ self.prompt = "\n" + self.prompt
+ self.custom_history = ['history 1', 'history 2']
+
+ @cmd2.with_category(EXAMPLE_COMMANDS)
+ def do_basic(self, _) -> None:
+ """Call read_input with no history or tab completion"""
+ self.poutput("Tab completion and up-arrow history is off")
+ try:
+ self.read_input("> ")
+ except EOFError:
+ pass
+
+ @cmd2.with_category(EXAMPLE_COMMANDS)
+ def do_basic_with_history(self, _) -> None:
+ """Call read_input with custom history and no tab completion"""
+ self.poutput("Tab completion is off but using custom history")
+ try:
+ input_str = self.read_input("> ", history=self.custom_history)
+ except EOFError:
+ pass
+ else:
+ self.custom_history.append(input_str)
+
+ @cmd2.with_category(EXAMPLE_COMMANDS)
+ def do_commands(self, _) -> None:
+ """Call read_input the same way cmd2 prompt does to read commands"""
+ self.poutput("Tab completing and up-arrow history configured for commands")
+ try:
+ self.read_input("> ", completion_mode=cmd2.CompletionMode.COMMANDS)
+ except EOFError:
+ pass
+
+ @cmd2.with_category(EXAMPLE_COMMANDS)
+ def do_custom_choices(self, _) -> None:
+ """Call read_input to use custom history and choices"""
+ self.poutput("Tab completing with static choices list and using custom history")
+ try:
+ input_str = self.read_input("> ", history=self.custom_history, completion_mode=cmd2.CompletionMode.CUSTOM,
+ choices=['choice_1', 'choice_2', 'choice_3'])
+ except EOFError:
+ pass
+ else:
+ self.custom_history.append(input_str)
+
+ # noinspection PyMethodMayBeStatic
+ def choices_provider(self) -> List[str]:
+ """Example choices provider function"""
+ return ["from_provider_1", "from_provider_2", "from_provider_3"]
+
+ @cmd2.with_category(EXAMPLE_COMMANDS)
+ def do_custom_choices_provider(self, _) -> None:
+ """Call read_input to use custom history and choices provider function"""
+ self.poutput("Tab completing with choices from provider function and using custom history")
+ try:
+ input_str = self.read_input("> ", history=self.custom_history, completion_mode=cmd2.CompletionMode.CUSTOM,
+ choices_provider=ReadInputApp.choices_provider)
+ except EOFError:
+ pass
+ else:
+ self.custom_history.append(input_str)
+
+ @cmd2.with_category(EXAMPLE_COMMANDS)
+ def do_custom_completer(self, _) -> None:
+ """all read_input to use custom history and completer function"""
+ self.poutput("Tab completing paths and using custom history")
+ try:
+ input_str = self.read_input("> ", history=self.custom_history, completion_mode=cmd2.CompletionMode.CUSTOM,
+ completer=cmd2.Cmd.path_complete)
+ self.custom_history.append(input_str)
+ except EOFError:
+ pass
+
+ @cmd2.with_category(EXAMPLE_COMMANDS)
+ def do_custom_parser(self, _) -> None:
+ """Call read_input to use a custom history and an argument parser"""
+ parser = cmd2.Cmd2ArgumentParser(prog='', description="An example parser")
+ parser.add_argument('-o', '--option', help="an optional arg")
+ parser.add_argument('arg_1', help="a choice for this arg", metavar='arg_1',
+ choices=['my_choice', 'your_choice'])
+ parser.add_argument('arg_2', help="path of something", completer=cmd2.Cmd.path_complete)
+
+ self.poutput("Tab completing with argument parser and using custom history")
+ self.poutput(parser.format_usage())
+
+ try:
+ input_str = self.read_input("> ", history=self.custom_history, completion_mode=cmd2.CompletionMode.CUSTOM,
+ parser=parser)
+ except EOFError:
+ pass
+ else:
+ self.custom_history.append(input_str)
+
+
+if __name__ == '__main__':
+ import sys
+ app = ReadInputApp()
+ sys.exit(app.cmdloop())
diff --git a/tests/test_argparse_completer.py b/tests/test_argparse_completer.py
index dd86163b..5c579b5c 100644
--- a/tests/test_argparse_completer.py
+++ b/tests/test_argparse_completer.py
@@ -10,49 +10,22 @@ from typing import List
import pytest
import cmd2
-from cmd2 import Cmd2ArgumentParser, CompletionItem, with_argparser
-from cmd2.utils import CompletionError, StdSim, basic_complete
+from cmd2 import Cmd2ArgumentParser, CompletionError, CompletionItem, with_argparser
+from cmd2.utils import StdSim
from .conftest import complete_tester, run_cmd
-# Lists used in our tests (there is a mix of sorted and unsorted on purpose)
-non_negative_int_choices = [1, 2, 3, 0, 22]
-int_choices = [-1, 1, -2, 2, 0, -12]
-static_choices_list = ['static', 'choices', 'stop', 'here']
-choices_from_function = ['choices', 'function', 'chatty', 'smith']
-choices_from_method = ['choices', 'method', 'most', 'improved']
+# Data and functions for testing standalone choice_provider and completer
+standalone_choices = ['standalone', 'provider']
+standalone_completions = ['standalone', 'completer']
-set_value_choices = ['set', 'value', 'choices']
-one_or_more_choices = ['one', 'or', 'more', 'choices']
-optional_choices = ['a', 'few', 'optional', 'choices']
-range_choices = ['some', 'range', 'choices']
-remainder_choices = ['remainder', 'choices']
-positional_choices = ['the', 'positional', 'choices']
+# noinspection PyUnusedLocal
+def standalone_choice_provider(cli: cmd2.Cmd) -> List[str]:
+ return standalone_choices
-completions_from_function = ['completions', 'function', 'fairly', 'complete']
-completions_from_method = ['completions', 'method', 'missed', 'spot']
-
-def choices_function() -> List[str]:
- """Function that provides choices"""
- return choices_from_function
-
-
-def completer_function(text: str, line: str, begidx: int, endidx: int) -> List[str]:
- """Tab completion function"""
- return basic_complete(text, line, begidx, endidx, completions_from_function)
-
-
-def choices_takes_arg_tokens(arg_tokens: argparse.Namespace) -> List[str]:
- """Choices function that receives arg_tokens from ArgparseCompleter"""
- return [arg_tokens['parent_arg'][0], arg_tokens['subcommand'][0]]
-
-
-def completer_takes_arg_tokens(text: str, line: str, begidx: int, endidx: int,
- arg_tokens: argparse.Namespace) -> List[str]:
- """Completer function that receives arg_tokens from ArgparseCompleter"""
- match_against = [arg_tokens['parent_arg'][0], arg_tokens['subcommand'][0]]
- return basic_complete(text, line, begidx, endidx, match_against)
+def standalone_completer(cli: cmd2.Cmd, text: str, line: str, begidx: int, endidx: int) -> List[str]:
+ return cli.basic_complete(text, line, begidx, endidx, standalone_completions)
# noinspection PyMethodMayBeStatic,PyUnusedLocal,PyProtectedMember
@@ -108,15 +81,21 @@ class ArgparseCompleterTester(cmd2.Cmd):
pass
############################################################################################################
- # Begin code related to testing choices, choices_function, and choices_method parameters
+ # Begin code related to testing choices and choices_provider parameters
############################################################################################################
STR_METAVAR = "HEADLESS"
TUPLE_METAVAR = ('arg1', 'others')
CUSTOM_DESC_HEADER = "Custom Header"
- def choices_method(self) -> List[str]:
+ # Lists used in our tests (there is a mix of sorted and unsorted on purpose)
+ non_negative_int_choices = [1, 2, 3, 0, 22]
+ int_choices = [-1, 1, -2, 2, 0, -12]
+ static_choices_list = ['static', 'choices', 'stop', 'here']
+ choices_from_provider = ['choices', 'provider', 'probably', 'improved']
+
+ def choices_provider(self) -> List[str]:
"""Method that provides choices"""
- return choices_from_method
+ return self.choices_from_provider
def completion_item_method(self) -> List[CompletionItem]:
"""Choices method that returns CompletionItems"""
@@ -131,17 +110,15 @@ class ArgparseCompleterTester(cmd2.Cmd):
# Flag args for choices command. Include string and non-string arg types.
choices_parser.add_argument("-l", "--list", help="a flag populated with a choices list",
choices=static_choices_list)
- choices_parser.add_argument("-f", "--function", help="a flag populated with a choices function",
- choices_function=choices_function)
- choices_parser.add_argument("-m", "--method", help="a flag populated with a choices method",
- choices_method=choices_method)
+ choices_parser.add_argument("-p", "--provider", help="a flag populated with a choices provider",
+ choices_provider=choices_provider)
choices_parser.add_argument('-d', "--desc_header", help='this arg has a descriptive header',
- choices_method=completion_item_method,
+ choices_provider=completion_item_method,
descriptive_header=CUSTOM_DESC_HEADER)
choices_parser.add_argument('-n', "--no_header", help='this arg has no descriptive header',
- choices_method=completion_item_method, metavar=STR_METAVAR)
+ choices_provider=completion_item_method, metavar=STR_METAVAR)
choices_parser.add_argument('-t', "--tuple_metavar", help='this arg has tuple for a metavar',
- choices_method=completion_item_method, metavar=TUPLE_METAVAR,
+ choices_provider=completion_item_method, metavar=TUPLE_METAVAR,
nargs=argparse.ONE_OR_MORE)
choices_parser.add_argument('-i', '--int', type=int, help='a flag with an int type',
choices=int_choices)
@@ -149,10 +126,8 @@ class ArgparseCompleterTester(cmd2.Cmd):
# Positional args for choices command
choices_parser.add_argument("list_pos", help="a positional populated with a choices list",
choices=static_choices_list)
- choices_parser.add_argument("function_pos", help="a positional populated with a choices function",
- choices_function=choices_function)
- choices_parser.add_argument("method_pos", help="a positional populated with a choices method",
- choices_method=choices_method)
+ choices_parser.add_argument("method_pos", help="a positional populated with a choices provider",
+ choices_provider=choices_provider)
choices_parser.add_argument('non_negative_int', type=int, help='a positional with non-negative int choices',
choices=non_negative_int_choices)
choices_parser.add_argument('empty_choices', help='a positional with empty choices',
@@ -163,25 +138,32 @@ class ArgparseCompleterTester(cmd2.Cmd):
pass
############################################################################################################
- # Begin code related to testing completer_function and completer_method parameters
+ # Begin code related to testing completer parameter
############################################################################################################
- def completer_method(self, text: str, line: str, begidx: int, endidx: int) -> List[str]:
- """Tab completion method"""
- return basic_complete(text, line, begidx, endidx, completions_from_method)
+ completions_for_flag = ['completions', 'flag', 'fairly', 'complete']
+ completions_for_pos_1 = ['completions', 'positional_1', 'probably', 'missed', 'spot']
+ completions_for_pos_2 = ['completions', 'positional_2', 'probably', 'missed', 'me']
+
+ def flag_completer(self, text: str, line: str, begidx: int, endidx: int) -> List[str]:
+ return self.basic_complete(text, line, begidx, endidx, self.completions_for_flag)
+
+ def pos_1_completer(self, text: str, line: str, begidx: int, endidx: int) -> List[str]:
+ return self.basic_complete(text, line, begidx, endidx, self.completions_for_pos_1)
+
+ def pos_2_completer(self, text: str, line: str, begidx: int, endidx: int) -> List[str]:
+ return self.basic_complete(text, line, begidx, endidx, self.completions_for_pos_2)
completer_parser = Cmd2ArgumentParser()
# Flag args for completer command
- completer_parser.add_argument("-f", "--function", help="a flag using a completer function",
- completer_function=completer_function)
- completer_parser.add_argument("-m", "--method", help="a flag using a completer method",
- completer_method=completer_method)
+ completer_parser.add_argument("-c", "--completer", help="a flag using a completer",
+ completer=flag_completer)
# Positional args for completer command
- completer_parser.add_argument("function_pos", help="a positional using a completer function",
- completer_function=completer_function)
- completer_parser.add_argument("method_pos", help="a positional using a completer method",
- completer_method=completer_method)
+ completer_parser.add_argument("pos_1", help="a positional using a completer method",
+ completer=pos_1_completer)
+ completer_parser.add_argument("pos_2", help="a positional using a completer method",
+ completer=pos_2_completer)
@with_argparser(completer_parser)
def do_completer(self, args: argparse.Namespace) -> None:
@@ -190,6 +172,13 @@ class ArgparseCompleterTester(cmd2.Cmd):
############################################################################################################
# Begin code related to nargs
############################################################################################################
+ set_value_choices = ['set', 'value', 'choices']
+ one_or_more_choices = ['one', 'or', 'more', 'choices']
+ optional_choices = ['a', 'few', 'optional', 'choices']
+ range_choices = ['some', 'range', 'choices']
+ remainder_choices = ['remainder', 'choices']
+ positional_choices = ['the', 'positional', 'choices']
+
nargs_parser = Cmd2ArgumentParser()
# Flag args for nargs command
@@ -241,10 +230,10 @@ class ArgparseCompleterTester(cmd2.Cmd):
raise CompletionError('choice broke something')
comp_error_parser = Cmd2ArgumentParser()
- comp_error_parser.add_argument('completer', help='positional arg',
- completer_method=completer_raise_error)
+ comp_error_parser.add_argument('completer_pos', help='positional arg',
+ completer=completer_raise_error)
comp_error_parser.add_argument('--choice', help='flag arg',
- choices_method=choice_raise_error)
+ choices_provider=choice_raise_error)
@with_argparser(comp_error_parser)
def do_raise_completion_error(self, args: argparse.Namespace) -> None:
@@ -253,6 +242,16 @@ class ArgparseCompleterTester(cmd2.Cmd):
############################################################################################################
# Begin code related to receiving arg_tokens
############################################################################################################
+ def choices_takes_arg_tokens(self, arg_tokens: argparse.Namespace) -> List[str]:
+ """Choices function that receives arg_tokens from ArgparseCompleter"""
+ return [arg_tokens['parent_arg'][0], arg_tokens['subcommand'][0]]
+
+ def completer_takes_arg_tokens(self, text: str, line: str, begidx: int, endidx: int,
+ arg_tokens: argparse.Namespace) -> List[str]:
+ """Completer function that receives arg_tokens from ArgparseCompleter"""
+ match_against = [arg_tokens['parent_arg'][0], arg_tokens['subcommand'][0]]
+ return self.basic_complete(text, line, begidx, endidx, match_against)
+
arg_tokens_parser = Cmd2ArgumentParser()
arg_tokens_parser.add_argument('parent_arg', help='arg from a parent parser')
@@ -260,8 +259,8 @@ class ArgparseCompleterTester(cmd2.Cmd):
arg_tokens_subparser = arg_tokens_parser.add_subparsers(dest='subcommand')
arg_tokens_subcmd_parser = arg_tokens_subparser.add_parser('subcmd')
- arg_tokens_subcmd_parser.add_argument('choices_pos', choices_function=choices_takes_arg_tokens)
- arg_tokens_subcmd_parser.add_argument('completer_pos', completer_function=completer_takes_arg_tokens)
+ arg_tokens_subcmd_parser.add_argument('choices_pos', choices_provider=choices_takes_arg_tokens)
+ arg_tokens_subcmd_parser.add_argument('completer_pos', completer=completer_takes_arg_tokens)
# Used to override parent_arg in arg_tokens_parser
arg_tokens_subcmd_parser.add_argument('--parent_arg')
@@ -286,6 +285,17 @@ class ArgparseCompleterTester(cmd2.Cmd):
def do_mutex(self, args: argparse.Namespace) -> None:
pass
+ ############################################################################################################
+ # Begin code related to standalone functions
+ ############################################################################################################
+ standalone_parser = Cmd2ArgumentParser()
+ standalone_parser.add_argument('--provider', help='standalone provider', choices_provider=standalone_choice_provider)
+ standalone_parser.add_argument('--completer', help='standalone completer', completer=standalone_completer)
+
+ @with_argparser(standalone_parser)
+ def do_standalone(self, args: argparse.Namespace) -> None:
+ pass
+
@pytest.fixture
def ac_app():
@@ -432,13 +442,11 @@ def test_autcomp_flag_completion(ac_app, command_and_args, text, completion_matc
@pytest.mark.parametrize('flag, text, completions', [
- ('-l', '', static_choices_list),
+ ('-l', '', ArgparseCompleterTester.static_choices_list),
('--list', 's', ['static', 'stop']),
- ('-f', '', choices_from_function),
- ('--function', 'ch', ['choices', 'chatty']),
- ('-m', '', choices_from_method),
- ('--method', 'm', ['method', 'most']),
- ('-i', '', int_choices),
+ ('-p', '', ArgparseCompleterTester.choices_from_provider),
+ ('--provider', 'pr', ['provider', 'probably']),
+ ('-i', '', ArgparseCompleterTester.int_choices),
('--int', '1', ['1 ']),
('--int', '-', [-1, -2, -12]),
('--int', '-1', [-1, -12])
@@ -465,15 +473,13 @@ def test_autocomp_flag_choices_completion(ac_app, flag, text, completions):
@pytest.mark.parametrize('pos, text, completions', [
- (1, '', static_choices_list),
+ (1, '', ArgparseCompleterTester.static_choices_list),
(1, 's', ['static', 'stop']),
- (2, '', choices_from_function),
- (2, 'ch', ['choices', 'chatty']),
- (3, '', choices_from_method),
- (3, 'm', ['method', 'most']),
- (4, '', non_negative_int_choices),
- (4, '2', [2, 22]),
- (5, '', []),
+ (2, '', ArgparseCompleterTester.choices_from_provider),
+ (2, 'pr', ['provider', 'probably']),
+ (3, '', ArgparseCompleterTester.non_negative_int_choices),
+ (3, '2', [2, 22]),
+ (4, '', []),
])
def test_autocomp_positional_choices_completion(ac_app, pos, text, completions):
# Generate line were preceding positionals are already filled
@@ -519,10 +525,8 @@ def test_flag_sorting(ac_app):
@pytest.mark.parametrize('flag, text, completions', [
- ('-f', '', completions_from_function),
- ('--function', 'f', ['function', 'fairly']),
- ('-m', '', completions_from_method),
- ('--method', 'm', ['method', 'missed'])
+ ('-c', '', ArgparseCompleterTester.completions_for_flag),
+ ('--completer', 'f', ['flag', 'fairly'])
])
def test_autocomp_flag_completers(ac_app, flag, text, completions):
line = 'completer {} {}'.format(flag, text)
@@ -539,10 +543,10 @@ def test_autocomp_flag_completers(ac_app, flag, text, completions):
@pytest.mark.parametrize('pos, text, completions', [
- (1, '', completions_from_function),
- (1, 'c', ['completions', 'complete']),
- (2, '', completions_from_method),
- (2, 'm', ['method', 'missed'])
+ (1, '', ArgparseCompleterTester.completions_for_pos_1),
+ (1, 'p', ['positional_1', 'probably']),
+ (2, '', ArgparseCompleterTester.completions_for_pos_2),
+ (2, 'm', ['missed', 'me']),
])
def test_autocomp_positional_completers(ac_app, pos, text, completions):
# Generate line were preceding positionals are already filled
@@ -565,27 +569,27 @@ def test_autocomp_blank_token(ac_app):
blank = ''
- # Blank flag arg
+ # Blank flag arg will be consumed. Therefore we expect to be completing the first positional.
text = ''
- line = 'completer -m {} {}'.format(blank, text)
+ line = 'completer -c {} {}'.format(blank, text)
endidx = len(line)
begidx = endidx - len(text)
completer = ArgparseCompleter(ac_app.completer_parser, ac_app)
- tokens = ['completer', '-f', blank, text]
- completions = completer.complete_command(tokens, text, line, begidx, endidx)
- assert completions == completions_from_function
+ tokens = ['-c', blank, text]
+ completions = completer.complete(text, line, begidx, endidx, tokens)
+ assert sorted(completions) == sorted(ArgparseCompleterTester.completions_for_pos_1)
- # Blank positional arg
+ # Blank arg for first positional will be consumed. Therefore we expect to be completing the second positional.
text = ''
line = 'completer {} {}'.format(blank, text)
endidx = len(line)
begidx = endidx - len(text)
completer = ArgparseCompleter(ac_app.completer_parser, ac_app)
- tokens = ['completer', blank, text]
- completions = completer.complete_command(tokens, text, line, begidx, endidx)
- assert completions == completions_from_method
+ tokens = [blank, text]
+ completions = completer.complete(text, line, begidx, endidx, tokens)
+ assert sorted(completions) == sorted(ArgparseCompleterTester.completions_for_pos_2)
@pytest.mark.parametrize('num_aliases, show_description', [
@@ -619,54 +623,54 @@ def test_completion_items(ac_app, num_aliases, show_description):
@pytest.mark.parametrize('args, completions', [
# Flag with nargs = 2
- ('--set_value', set_value_choices),
+ ('--set_value', ArgparseCompleterTester.set_value_choices),
('--set_value set', ['value', 'choices']),
# Both args are filled. At positional arg now.
- ('--set_value set value', positional_choices),
+ ('--set_value set value', ArgparseCompleterTester.positional_choices),
# Using the flag again will reset the choices available
- ('--set_value set value --set_value', set_value_choices),
+ ('--set_value set value --set_value', ArgparseCompleterTester.set_value_choices),
# Flag with nargs = ONE_OR_MORE
- ('--one_or_more', one_or_more_choices),
+ ('--one_or_more', ArgparseCompleterTester.one_or_more_choices),
('--one_or_more one', ['or', 'more', 'choices']),
# Flag with nargs = OPTIONAL
- ('--optional', optional_choices),
+ ('--optional', ArgparseCompleterTester.optional_choices),
# Only one arg allowed for an OPTIONAL. At positional now.
- ('--optional optional', positional_choices),
+ ('--optional optional', ArgparseCompleterTester.positional_choices),
# Flag with nargs range (1, 2)
- ('--range', range_choices),
+ ('--range', ArgparseCompleterTester.range_choices),
('--range some', ['range', 'choices']),
# Already used 2 args so at positional
- ('--range some range', positional_choices),
+ ('--range some range', ArgparseCompleterTester.positional_choices),
# Flag with nargs = REMAINDER
- ('--remainder', remainder_choices),
+ ('--remainder', ArgparseCompleterTester.remainder_choices),
('--remainder remainder ', ['choices ']),
# No more flags can appear after a REMAINDER flag)
('--remainder choices --set_value', ['remainder ']),
# Double dash ends the current flag
- ('--range choice --', positional_choices),
+ ('--range choice --', ArgparseCompleterTester.positional_choices),
# Double dash ends a REMAINDER flag
- ('--remainder remainder --', positional_choices),
+ ('--remainder remainder --', ArgparseCompleterTester.positional_choices),
# No more flags after a double dash
- ('-- --one_or_more ', positional_choices),
+ ('-- --one_or_more ', ArgparseCompleterTester.positional_choices),
# Consume positional
- ('', positional_choices),
+ ('', ArgparseCompleterTester.positional_choices),
('positional', ['the', 'choices']),
# Intermixed flag and positional
- ('positional --set_value', set_value_choices),
+ ('positional --set_value', ArgparseCompleterTester.set_value_choices),
('positional --set_value set', ['choices', 'value']),
# Intermixed flag and positional with flag finishing
@@ -674,12 +678,12 @@ def test_completion_items(ac_app, num_aliases, show_description):
('positional --range choice --', ['the', 'choices']),
# REMAINDER positional
- ('the positional', remainder_choices),
+ ('the positional', ArgparseCompleterTester.remainder_choices),
('the positional remainder', ['choices ']),
('the positional remainder choices', []),
# REMAINDER positional. Flags don't work in REMAINDER
- ('the positional --set_value', remainder_choices),
+ ('the positional --set_value', ArgparseCompleterTester.remainder_choices),
('the positional remainder --set_value', ['choices '])
])
def test_autcomp_nargs(ac_app, args, completions):
@@ -997,7 +1001,7 @@ def test_complete_command_no_tokens(ac_app):
parser = Cmd2ArgumentParser()
ac = ArgparseCompleter(parser, ac_app)
- completions = ac.complete_command(tokens=[], text='', line='', begidx=0, endidx=0)
+ completions = ac.complete(text='', line='', begidx=0, endidx=0, tokens=[])
assert not completions
@@ -1007,5 +1011,20 @@ def test_complete_command_help_no_tokens(ac_app):
parser = Cmd2ArgumentParser()
ac = ArgparseCompleter(parser, ac_app)
- completions = ac.complete_subcommand_help(tokens=[], text='', line='', begidx=0, endidx=0)
+ completions = ac.complete_subcommand_help(text='', line='', begidx=0, endidx=0, tokens=[])
assert not completions
+
+
+@pytest.mark.parametrize('flag, completions', [
+ ('--provider', standalone_choices),
+ ('--completer', standalone_completions)
+])
+def test_complete_standalone(ac_app, flag, completions):
+ text = ''
+ line = 'standalone {} {}'.format(flag, text)
+ endidx = len(line)
+ begidx = endidx - len(text)
+
+ first_match = complete_tester(text, line, begidx, endidx, ac_app)
+ assert first_match is not None
+ assert ac_app.completion_matches == sorted(completions, key=ac_app.default_sort_key)
diff --git a/tests/test_argparse_custom.py b/tests/test_argparse_custom.py
index e2b3bb97..3d41fffd 100644
--- a/tests/test_argparse_custom.py
+++ b/tests/test_argparse_custom.py
@@ -41,13 +41,9 @@ def fake_func():
@pytest.mark.parametrize('kwargs, is_valid', [
- ({'choices_function': fake_func}, True),
- ({'choices_method': fake_func}, True),
- ({'completer_function': fake_func}, True),
- ({'completer_method': fake_func}, True),
- ({'choices_function': fake_func, 'choices_method': fake_func}, False),
- ({'choices_method': fake_func, 'completer_function': fake_func}, False),
- ({'completer_function': fake_func, 'completer_method': fake_func}, False),
+ ({'choices_provider': fake_func}, True),
+ ({'completer': fake_func}, True),
+ ({'choices_provider': fake_func, 'completer': fake_func}, False),
])
def test_apcustom_choices_callable_count(kwargs, is_valid):
parser = Cmd2ArgumentParser()
@@ -60,10 +56,8 @@ def test_apcustom_choices_callable_count(kwargs, is_valid):
@pytest.mark.parametrize('kwargs', [
- ({'choices_function': fake_func}),
- ({'choices_method': fake_func}),
- ({'completer_function': fake_func}),
- ({'completer_method': fake_func})
+ ({'choices_provider': fake_func}),
+ ({'completer': fake_func})
])
def test_apcustom_no_choices_callables_alongside_choices(kwargs):
with pytest.raises(TypeError) as excinfo:
@@ -73,10 +67,8 @@ def test_apcustom_no_choices_callables_alongside_choices(kwargs):
@pytest.mark.parametrize('kwargs', [
- ({'choices_function': fake_func}),
- ({'choices_method': fake_func}),
- ({'completer_function': fake_func}),
- ({'completer_method': fake_func})
+ ({'choices_provider': fake_func}),
+ ({'completer': fake_func})
])
def test_apcustom_no_choices_callables_when_nargs_is_0(kwargs):
with pytest.raises(TypeError) as excinfo:
diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py
index 1e4f4844..d913b4fc 100755
--- a/tests/test_cmd2.py
+++ b/tests/test_cmd2.py
@@ -1437,6 +1437,42 @@ def test_read_input_rawinput_true(capsys, monkeypatch):
line = app.read_input(prompt_str)
assert line == input_str
+ # Run custom history code
+ import readline
+ readline.add_history('old_history')
+ custom_history = ['cmd1', 'cmd2']
+ line = app.read_input(prompt_str, history=custom_history, completion_mode=cmd2.CompletionMode.NONE)
+ assert line == input_str
+ readline.clear_history()
+
+ # Run all completion modes
+ line = app.read_input(prompt_str, completion_mode=cmd2.CompletionMode.NONE)
+ assert line == input_str
+
+ line = app.read_input(prompt_str, completion_mode=cmd2.CompletionMode.COMMANDS)
+ assert line == input_str
+
+ # custom choices
+ custom_choices = ['choice1', 'choice2']
+ line = app.read_input(prompt_str, completion_mode=cmd2.CompletionMode.CUSTOM,
+ choices=custom_choices)
+ assert line == input_str
+
+ # custom choices_provider
+ line = app.read_input(prompt_str, completion_mode=cmd2.CompletionMode.CUSTOM,
+ choices_provider=cmd2.Cmd.get_all_commands)
+ assert line == input_str
+
+ # custom completer
+ line = app.read_input(prompt_str, completion_mode=cmd2.CompletionMode.CUSTOM,
+ completer=cmd2.Cmd.path_complete)
+ assert line == input_str
+
+ # custom parser
+ line = app.read_input(prompt_str, completion_mode=cmd2.CompletionMode.CUSTOM,
+ parser=cmd2.Cmd2ArgumentParser())
+ assert line == input_str
+
# isatty is False
with mock.patch('sys.stdin.isatty', mock.MagicMock(name='isatty', return_value=False)):
# echo True
diff --git a/tests/test_completion.py b/tests/test_completion.py
index 48a055d0..48f93a5a 100755
--- a/tests/test_completion.py
+++ b/tests/test_completion.py
@@ -22,7 +22,6 @@ import pytest
import cmd2
from cmd2 import utils
from examples.subcommands import SubcommandsExample
-
from .conftest import complete_tester, normalize, run_cmd
# List of strings used with completion functions
@@ -65,13 +64,13 @@ class CompletionsExample(cmd2.Cmd):
cmd2.Cmd.__init__(self, multiline_commands=['test_multiline'])
self.foo = 'bar'
self.add_settable(utils.Settable('foo', str, description="a settable param",
- completer_method=CompletionsExample.complete_foo_val))
+ completer=CompletionsExample.complete_foo_val))
def do_test_basic(self, args):
pass
def complete_test_basic(self, text, line, begidx, endidx):
- return utils.basic_complete(text, line, begidx, endidx, food_item_strs)
+ return self.basic_complete(text, line, begidx, endidx, food_item_strs)
def do_test_delimited(self, args):
pass
@@ -84,7 +83,7 @@ class CompletionsExample(cmd2.Cmd):
def complete_test_sort_key(self, text, line, begidx, endidx):
num_strs = ['2', '11', '1']
- return utils.basic_complete(text, line, begidx, endidx, num_strs)
+ return self.basic_complete(text, line, begidx, endidx, num_strs)
def do_test_raise_exception(self, args):
pass
@@ -96,7 +95,7 @@ class CompletionsExample(cmd2.Cmd):
pass
def complete_test_multiline(self, text, line, begidx, endidx):
- return utils.basic_complete(text, line, begidx, endidx, sport_item_strs)
+ return self.basic_complete(text, line, begidx, endidx, sport_item_strs)
def do_test_no_completer(self, args):
"""Completing this should result in completedefault() being called"""
@@ -541,7 +540,7 @@ def test_basic_completion_single(cmd2_app):
endidx = len(line)
begidx = endidx - len(text)
- assert utils.basic_complete(text, line, begidx, endidx, food_item_strs) == ['Pizza']
+ assert cmd2_app.basic_complete(text, line, begidx, endidx, food_item_strs) == ['Pizza']
def test_basic_completion_multiple(cmd2_app):
text = ''
@@ -549,7 +548,7 @@ def test_basic_completion_multiple(cmd2_app):
endidx = len(line)
begidx = endidx - len(text)
- matches = sorted(utils.basic_complete(text, line, begidx, endidx, food_item_strs))
+ matches = sorted(cmd2_app.basic_complete(text, line, begidx, endidx, food_item_strs))
assert matches == sorted(food_item_strs)
def test_basic_completion_nomatch(cmd2_app):
@@ -558,7 +557,7 @@ def test_basic_completion_nomatch(cmd2_app):
endidx = len(line)
begidx = endidx - len(text)
- assert utils.basic_complete(text, line, begidx, endidx, food_item_strs) == []
+ assert cmd2_app.basic_complete(text, line, begidx, endidx, food_item_strs) == []
def test_delimiter_completion(cmd2_app):
text = '/home/'
@@ -858,9 +857,9 @@ def test_no_completer(cmd2_app):
first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
assert first_match is not None and cmd2_app.completion_matches == expected
-def test_quote_as_command(cmd2_app):
+def test_wordbreak_in_command(cmd2_app):
text = ''
- line = '" {}'.format(text)
+ line = '"{}'.format(text)
endidx = len(line)
begidx = endidx - len(text)
diff --git a/tests/test_history.py b/tests/test_history.py
index 6fa16ad8..86c52592 100755
--- a/tests/test_history.py
+++ b/tests/test_history.py
@@ -27,6 +27,7 @@ except ImportError:
#
def test_readline_remove_history_item(base_app):
from cmd2.rl_utils import readline
+ readline.clear_history()
assert readline.get_current_history_length() == 0
readline.add_history('this is a test')
assert readline.get_current_history_length() == 1
diff --git a/tests_isolated/test_commandset/test_commandset.py b/tests_isolated/test_commandset/test_commandset.py
index 21cce8bf..db1065b7 100644
--- a/tests_isolated/test_commandset/test_commandset.py
+++ b/tests_isolated/test_commandset/test_commandset.py
@@ -67,7 +67,7 @@ class CommandSetA(CommandSetBase):
self._cmd.last_result = {'args': args}
def complete_durian(self, text: str, line: str, begidx: int, endidx: int) -> List[str]:
- return utils.basic_complete(text, line, begidx, endidx, ['stinks', 'smells', 'disgusting'])
+ return self._cmd.basic_complete(text, line, begidx, endidx, ['stinks', 'smells', 'disgusting'])
elderberry_parser = cmd2.Cmd2ArgumentParser()
elderberry_parser.add_argument('arg1')
@@ -439,7 +439,7 @@ class LoadableVegetables(cmd2.CommandSet):
return ['quartered', 'diced']
bokchoy_parser = cmd2.Cmd2ArgumentParser()
- bokchoy_parser.add_argument('style', completer_method=complete_style_arg)
+ bokchoy_parser.add_argument('style', completer=complete_style_arg)
@cmd2.as_subcommand_to('cut', 'bokchoy', bokchoy_parser)
def cut_bokchoy(self, ns: argparse.Namespace):
@@ -644,7 +644,7 @@ class AppWithSubCommands(cmd2.Cmd):
return ['quartered', 'diced']
bokchoy_parser = cmd2.Cmd2ArgumentParser()
- bokchoy_parser.add_argument('style', completer_method=complete_style_arg)
+ bokchoy_parser.add_argument('style', completer=complete_style_arg)
@cmd2.as_subcommand_to('cut', 'bokchoy', bokchoy_parser)
def cut_bokchoy(self, _: argparse.Namespace):
@@ -695,12 +695,12 @@ class WithCompleterCommandSet(cmd2.CommandSet):
def complete_states(self, text: str, line: str, begidx: int, endidx: int) -> List[str]:
assert self is complete_states_expected_self
- return utils.basic_complete(text, line, begidx, endidx, self.states)
+ return self._cmd.basic_complete(text, line, begidx, endidx, self.states)
class SubclassCommandSetCase1(WithCompleterCommandSet):
parser = cmd2.Cmd2ArgumentParser()
- parser.add_argument('state', type=str, completer_method=WithCompleterCommandSet.complete_states)
+ parser.add_argument('state', type=str, completer=WithCompleterCommandSet.complete_states)
@cmd2.with_argparser(parser)
def do_case1(self, ns: argparse.Namespace):
@@ -709,7 +709,7 @@ class SubclassCommandSetCase1(WithCompleterCommandSet):
class SubclassCommandSetErrorCase2(WithCompleterCommandSet):
parser = cmd2.Cmd2ArgumentParser()
- parser.add_argument('state', type=str, completer_method=WithCompleterCommandSet.complete_states)
+ parser.add_argument('state', type=str, completer=WithCompleterCommandSet.complete_states)
@cmd2.with_argparser(parser)
def do_error2(self, ns: argparse.Namespace):
@@ -722,7 +722,7 @@ class SubclassCommandSetCase2(cmd2.CommandSet):
super(SubclassCommandSetCase2, self).__init__()
parser = cmd2.Cmd2ArgumentParser()
- parser.add_argument('state', type=str, completer_method=WithCompleterCommandSet.complete_states)
+ parser.add_argument('state', type=str, completer=WithCompleterCommandSet.complete_states)
@cmd2.with_argparser(parser)
def do_case2(self, ns: argparse.Namespace):
@@ -732,7 +732,7 @@ class SubclassCommandSetCase2(cmd2.CommandSet):
def test_cross_commandset_completer(command_sets_manual):
global complete_states_expected_self
# This tests the different ways to locate the matching CommandSet when completing an argparse argument.
- # Exercises the `_complete_for_arg` function of `ArgparseCompleter` in `argparse_completer.py`
+ # Exercises the `_complete_arg` function of `ArgparseCompleter` in `argparse_completer.py`
####################################################################################################################
# This exercises Case 1
@@ -853,7 +853,7 @@ class CommandSetWithPathComplete(cmd2.CommandSet):
super(CommandSetWithPathComplete, self).__init__()
parser = argparse.ArgumentParser()
- parser.add_argument('path', nargs='+', help='paths', completer_method=cmd2.Cmd.path_complete)
+ parser.add_argument('path', nargs='+', help='paths', completer=cmd2.Cmd.path_complete)
@cmd2.with_argparser(parser)
def do_path(self, app: cmd2.Cmd, args):