summaryrefslogtreecommitdiff
path: root/cmd2/cmd2.py
diff options
context:
space:
mode:
Diffstat (limited to 'cmd2/cmd2.py')
-rw-r--r--cmd2/cmd2.py411
1 files changed, 247 insertions, 164 deletions
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