summaryrefslogtreecommitdiff
path: root/cmd2/cmd2.py
diff options
context:
space:
mode:
authorTodd Leonhardt <todd.leonhardt@gmail.com>2021-02-19 21:35:13 -0500
committerTodd Leonhardt <todd.leonhardt@gmail.com>2021-02-19 21:35:13 -0500
commit3e180a810e9c4b9d251c135667d1d150b0bbd0dd (patch)
tree03e49d5da86d40efa9118eccfd8bd4bbf3dcf86b /cmd2/cmd2.py
parent4c70bdb03d34c43f833bf77c441452cd402d0715 (diff)
parent06aaf962689840631325c70ea7e9056d176c7f67 (diff)
downloadcmd2-git-3e180a810e9c4b9d251c135667d1d150b0bbd0dd.tar.gz
Merge branch 'master' into black
# Conflicts: # cmd2/__init__.py # cmd2/argparse_completer.py # cmd2/argparse_custom.py # cmd2/cmd2.py # cmd2/decorators.py # cmd2/exceptions.py # cmd2/utils.py # examples/arg_decorators.py # examples/argparse_completion.py # examples/modular_commands_main.py # tests/test_argparse_completer.py # tests/test_argparse_custom.py # tests/test_cmd2.py # tests/test_completion.py # tests/test_history.py
Diffstat (limited to 'cmd2/cmd2.py')
-rw-r--r--cmd2/cmd2.py603
1 files changed, 287 insertions, 316 deletions
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index a08c5d42..88fdcd87 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
@@ -91,6 +92,7 @@ from .decorators import (
from .exceptions import (
Cmd2ShlexError,
CommandSetRegistrationError,
+ CompletionError,
EmbeddedConsoleExit,
EmptyStatement,
RedirectionError,
@@ -117,7 +119,6 @@ from .rl_utils import (
vt100_support,
)
from .utils import (
- CompletionError,
Settable,
get_defining_class,
)
@@ -1173,7 +1174,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 [], []
@@ -1192,9 +1193,23 @@ class Cmd(cmd.Cmd):
return tokens, raw_tokens
- def delimiter_complete(
- self, text: str, line: str, begidx: int, endidx: int, match_against: Iterable, delimiter: str
- ) -> List[str]:
+ # 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]:
"""
Performs tab completion against a list but each match is split on a delimiter and only
the portion of the match being tab completed is shown as the completion suggestions.
@@ -1227,7 +1242,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:
@@ -1281,7 +1296,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 = []
@@ -1295,7 +1310,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):
@@ -1330,7 +1345,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 = []
@@ -1346,7 +1361,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):
@@ -1541,7 +1556,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
@@ -1722,48 +1737,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 = ''
@@ -1791,43 +1854,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:
@@ -1877,16 +1906,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, …,
@@ -1898,6 +1923,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
@@ -1908,7 +1934,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
@@ -1927,9 +1953,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
@@ -1939,15 +1965,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:
@@ -1980,30 +2010,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
@@ -2668,36 +2674,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
+
+ # 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)
+
+ custom_settings = utils.CustomCompletionSettings(parser, preserve_quotes=preserve_quotes)
+ complete_func = functools.partial(self.complete, custom_settings=custom_settings)
+
+ readline.set_completer(complete_func)
- if self._completion_supported() and not completion_disabled:
- orig_completer = readline.get_completer()
- readline.set_completer(lambda *args, **kwargs: None)
- completion_disabled = True
+ # 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))
- def enable_completion():
- """Restore tab completion when finished entering input"""
- nonlocal completion_disabled
+ readline.clear_history()
+ if history is not None:
+ for item in history:
+ readline.add_history(item)
- if self._completion_supported() and completion_disabled:
- readline.set_completer(orig_completer)
- completion_disabled = False
+ 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:
@@ -2707,15 +2792,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:
@@ -2759,7 +2840,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:
@@ -2768,7 +2849,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
"""
@@ -2803,7 +2884,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
"""
@@ -2894,16 +2975,11 @@ class Cmd(cmd.Cmd):
)
alias_create_parser = DEFAULT_ARGUMENT_PARSER(description=alias_create_description, epilog=alias_create_epilog)
- alias_create_parser.add_argument(
- '-s', '--silent', action='store_true', help='do not print message confirming alias was created or\n' '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
- )
- alias_create_parser.add_argument(
- 'command_args', nargs=argparse.REMAINDER, help='arguments to pass to command', completer_method=path_complete
- )
+ alias_create_parser.add_argument('command', help='what the alias resolves to',
+ 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=path_complete)
@as_subcommand_to('alias', 'create', alias_create_parser, help=alias_create_description.lower())
def _alias_create(self, args: argparse.Namespace) -> None:
@@ -2933,9 +3009,8 @@ class Cmd(cmd.Cmd):
value += ' ' + ' '.join(args.command_args)
# Set the alias
- if not args.silent:
- result = "overwritten" if args.name in self.aliases else "created"
- self.poutput("Alias '{}' {}".format(args.name, result))
+ result = "overwritten" if args.name in self.aliases else "created"
+ self.poutput("Alias '{}' {}".format(args.name, result))
self.aliases[args.name] = value
@@ -2945,13 +3020,8 @@ 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',
- )
+ alias_delete_parser.add_argument('names', nargs=argparse.ZERO_OR_MORE, help='alias(es) to delete',
+ 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:
@@ -2979,29 +3049,12 @@ class Cmd(cmd.Cmd):
)
alias_list_parser = DEFAULT_ARGUMENT_PARSER(description=alias_list_description)
- alias_list_parser.add_argument(
- '-w',
- '--with_silent',
- action='store_true',
- help="include --silent flag with listed aliases\n"
- "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',
- )
+ alias_list_parser.add_argument('names', nargs=argparse.ZERO_OR_MORE, help='alias(es) to list',
+ 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:
"""List some or all aliases as 'alias create' commands"""
- create_cmd = "alias create"
- if args.with_silent:
- create_cmd += " --silent"
-
tokens_to_quote = constants.REDIRECTION_TOKENS
tokens_to_quote.extend(self.statement_parser.terminators)
@@ -3026,7 +3079,7 @@ class Cmd(cmd.Cmd):
if args:
val += ' ' + ' '.join(args)
- self.poutput("{} {} {}".format(create_cmd, name, val))
+ self.poutput("alias create {} {}".format(name, val))
for name in not_found:
self.perror("Alias '{}' not found".format(name))
@@ -3092,16 +3145,11 @@ class Cmd(cmd.Cmd):
)
macro_create_parser = DEFAULT_ARGUMENT_PARSER(description=macro_create_description, epilog=macro_create_epilog)
- macro_create_parser.add_argument(
- '-s', '--silent', action='store_true', help='do not print message confirming macro was created or\n' '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
- )
- macro_create_parser.add_argument(
- 'command_args', nargs=argparse.REMAINDER, help='arguments to pass to command', completer_method=path_complete
- )
+ macro_create_parser.add_argument('command', help='what the macro resolves to',
+ 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=path_complete)
@as_subcommand_to('macro', 'create', macro_create_parser, help=macro_create_help)
def _macro_create(self, args: argparse.Namespace) -> None:
@@ -3176,9 +3224,8 @@ class Cmd(cmd.Cmd):
break
# Set the macro
- if not args.silent:
- result = "overwritten" if args.name in self.macros else "created"
- self.poutput("Macro '{}' {}".format(args.name, result))
+ result = "overwritten" if args.name in self.macros else "created"
+ self.poutput("Macro '{}' {}".format(args.name, result))
self.macros[args.name] = Macro(name=args.name, value=value, minimum_arg_count=max_arg_num, arg_list=arg_list)
@@ -3187,13 +3234,8 @@ class Cmd(cmd.Cmd):
macro_delete_description = "Delete specified macros or all macros if --all is used"
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',
- )
+ macro_delete_parser.add_argument('names', nargs=argparse.ZERO_OR_MORE, help='macro(s) to delete',
+ 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:
@@ -3221,29 +3263,12 @@ class Cmd(cmd.Cmd):
)
macro_list_parser = DEFAULT_ARGUMENT_PARSER(description=macro_list_description)
- macro_list_parser.add_argument(
- '-w',
- '--with_silent',
- action='store_true',
- help="include --silent flag with listed macros\n"
- "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',
- )
+ macro_list_parser.add_argument('names', nargs=argparse.ZERO_OR_MORE, help='macro(s) to list',
+ 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:
"""List some or all macros as 'macro create' commands"""
- create_cmd = "macro create"
- if args.with_silent:
- create_cmd += " --silent"
-
tokens_to_quote = constants.REDIRECTION_TOKENS
tokens_to_quote.extend(self.statement_parser.terminators)
@@ -3268,7 +3293,7 @@ class Cmd(cmd.Cmd):
if args:
val += ' ' + ' '.join(args)
- self.poutput("{} {} {}".format(create_cmd, name, val))
+ self.poutput("macro create {} {}".format(name, val))
for name in not_found:
self.perror("Macro '{}' not found".format(name))
@@ -3280,7 +3305,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]]
@@ -3298,31 +3323,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,
- )
-
+ 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
- )
- help_parser.add_argument(
- 'subcommands',
- nargs=argparse.REMAINDER,
- help="subcommand(s) to retrieve help for",
- completer_method=complete_help_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=complete_help_command)
+ help_parser.add_argument('subcommands', nargs=argparse.REMAINDER, help="subcommand(s) to retrieve help for",
+ 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:
@@ -3347,10 +3359,9 @@ class Cmd(cmd.Cmd):
)
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 a help func delegate to do_help
elif help_func is not None:
@@ -3556,10 +3567,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:
@@ -3584,26 +3591,17 @@ class Cmd(cmd.Cmd):
# Settables with choices list the values of those choices instead of the arg name
# in help text and this shows in tab completion hints. Set metavar to avoid this.
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,
- )
-
- from .argparse_completer import (
- ArgparseCompleter,
- )
+ settable_parser.add_argument(arg_name, metavar=arg_name, help=settable.description,
+ choices=settable.choices,
+ 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.
@@ -3613,26 +3611,15 @@ class Cmd(cmd.Cmd):
"Call with just param to view that parameter's value."
)
set_parser_parent = DEFAULT_ARGUMENT_PARSER(description=set_description, add_help=False)
- 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',
- )
+ 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_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,
- )
+ set_parser.add_argument('value', nargs=argparse.OPTIONAL, help='new value for settable',
+ 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)
@@ -3692,10 +3679,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_args', nargs=argparse.REMAINDER, help='arguments to pass to command', completer_method=path_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=path_complete)
# Preserve quotes since we are passing these strings to the shell
@with_argparser(shell_parser, preserve_quotes=True)
@@ -3992,10 +3978,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_arguments', nargs=argparse.REMAINDER, help='arguments to pass to script', 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=path_complete)
@with_argparser(run_pyscript_parser)
def do_run_pyscript(self, args: argparse.Namespace) -> Optional[bool]:
@@ -4091,21 +4076,14 @@ class Cmd(cmd.Cmd):
history_parser = DEFAULT_ARGUMENT_PARSER(description=history_description)
history_action_group = history_parser.add_mutually_exclusive_group()
history_action_group.add_argument('-r', '--run', action='store_true', help='run selected history items')
- history_action_group.add_argument('-e', '--edit', action='store_true', 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,
- )
- 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,
- )
+ history_action_group.add_argument('-e', '--edit', action='store_true',
+ 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=path_complete)
+ history_action_group.add_argument('-t', '--transcript', metavar='TRANSCRIPT_FILE',
+ help='output commands and results to a transcript file,\nimplies -s',
+ 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')
@@ -4436,9 +4414,8 @@ 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
- )
+ edit_parser.add_argument('file_path', nargs=argparse.OPTIONAL,
+ 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:
@@ -4481,14 +4458,10 @@ 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)
+ run_script_parser.add_argument('-t', '--transcript', metavar='TRANSCRIPT_FILE',
+ help='record the output of the script as a transcript file',
+ 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]:
@@ -4808,8 +4781,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