summaryrefslogtreecommitdiff
path: root/cmd2/cmd2.py
diff options
context:
space:
mode:
authorTodd Leonhardt <todd.leonhardt@gmail.com>2019-09-17 22:12:41 -0400
committerTodd Leonhardt <todd.leonhardt@gmail.com>2019-09-17 22:12:41 -0400
commitefadff391032482b139524c96dfc4130fc631f9c (patch)
tree1f5a35d1c65a8c68f8a5eaa4bd78c3cf5d487fa5 /cmd2/cmd2.py
parent74857c34c00323c881f1669ae95788dcff6a48fa (diff)
parent1176c0cc99975044d2fcec88b3f0903b8453194f (diff)
downloadcmd2-git-efadff391032482b139524c96dfc4130fc631f9c.tar.gz
Merge branch 'master' into doc_additions
Diffstat (limited to 'cmd2/cmd2.py')
-rwxr-xr-xcmd2/cmd2.py109
1 files changed, 49 insertions, 60 deletions
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index a0a49a51..87f8684d 100755
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -119,6 +119,9 @@ CMD_ATTR_HELP_CATEGORY = 'help_category'
# The argparse parser for the command
CMD_ATTR_ARGPARSER = 'argparser'
+# Whether or not tokens are unquoted before sending to argparse
+CMD_ATTR_PRESERVE_QUOTES = 'preserve_quotes'
+
def categorize(func: Union[Callable, Iterable[Callable]], category: str) -> None:
"""Categorize a function.
@@ -225,8 +228,9 @@ def with_argparser_and_unknown_args(argparser: argparse.ArgumentParser, *,
# Set the command's help text as argparser.description (which can be None)
cmd_wrapper.__doc__ = argparser.description
- # Mark this function as having an argparse ArgumentParser
+ # Set some custom attributes for this command
setattr(cmd_wrapper, CMD_ATTR_ARGPARSER, argparser)
+ setattr(cmd_wrapper, CMD_ATTR_PRESERVE_QUOTES, preserve_quotes)
return cmd_wrapper
@@ -283,8 +287,9 @@ def with_argparser(argparser: argparse.ArgumentParser, *,
# Set the command's help text as argparser.description (which can be None)
cmd_wrapper.__doc__ = argparser.description
- # Mark this function as having an argparse ArgumentParser
+ # Set some custom attributes for this command
setattr(cmd_wrapper, CMD_ATTR_ARGPARSER, argparser)
+ setattr(cmd_wrapper, CMD_ATTR_PRESERVE_QUOTES, preserve_quotes)
return cmd_wrapper
@@ -1431,7 +1436,8 @@ class Cmd(cmd.Cmd):
if func is not None and argparser is not None:
import functools
compfunc = functools.partial(self._autocomplete_default,
- argparser=argparser)
+ argparser=argparser,
+ preserve_quotes=getattr(func, CMD_ATTR_PRESERVE_QUOTES))
else:
compfunc = self.completedefault
@@ -1588,13 +1594,21 @@ class Cmd(cmd.Cmd):
self.pexcept(e)
return None
- def _autocomplete_default(self, text: str, line: str, begidx: int, endidx: int,
- argparser: argparse.ArgumentParser) -> List[str]:
+ def _autocomplete_default(self, text: str, line: str, begidx: int, endidx: int, *,
+ argparser: argparse.ArgumentParser, preserve_quotes: bool) -> List[str]:
"""Default completion function for argparse commands"""
from .argparse_completer import AutoCompleter
completer = AutoCompleter(argparser, self)
- tokens, _ = self.tokens_for_completion(line, begidx, endidx)
- return completer.complete_command(tokens, text, line, begidx, endidx)
+ 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)
+
+ def get_names(self):
+ """Return an alphabetized list of names comprising the attributes of the cmd2 class instance."""
+ return dir(self)
def get_all_commands(self) -> List[str]:
"""Return a list of all commands"""
@@ -2384,7 +2398,8 @@ class Cmd(cmd.Cmd):
alias_parser = Cmd2ArgumentParser(description=alias_description, epilog=alias_epilog, prog='alias')
# Add subcommands to alias
- alias_subparsers = alias_parser.add_subparsers()
+ alias_subparsers = alias_parser.add_subparsers(dest='subcommand')
+ alias_subparsers.required = True
# alias -> create
alias_create_help = "create or overwrite an alias"
@@ -2439,13 +2454,9 @@ class Cmd(cmd.Cmd):
@with_argparser(alias_parser, preserve_quotes=True)
def do_alias(self, args: argparse.Namespace) -> None:
"""Manage aliases"""
- func = getattr(args, 'func', None)
- if func is not None:
- # Call whatever subcommand function was selected
- func(self, args)
- else:
- # noinspection PyTypeChecker
- self.do_help('alias')
+ # Call whatever subcommand function was selected
+ func = getattr(args, 'func')
+ func(self, args)
# ----- Macro subcommand functions -----
@@ -2564,7 +2575,8 @@ class Cmd(cmd.Cmd):
macro_parser = Cmd2ArgumentParser(description=macro_description, epilog=macro_epilog, prog='macro')
# Add subcommands to macro
- macro_subparsers = macro_parser.add_subparsers()
+ macro_subparsers = macro_parser.add_subparsers(dest='subcommand')
+ macro_subparsers.required = True
# macro -> create
macro_create_help = "create or overwrite a macro"
@@ -2641,13 +2653,9 @@ class Cmd(cmd.Cmd):
@with_argparser(macro_parser, preserve_quotes=True)
def do_macro(self, args: argparse.Namespace) -> None:
"""Manage macros"""
- func = getattr(args, 'func', None)
- if func is not None:
- # Call whatever subcommand function was selected
- func(self, args)
- else:
- # noinspection PyTypeChecker
- self.do_help('macro')
+ # Call whatever subcommand function was selected
+ func = getattr(args, 'func')
+ func(self, args)
def complete_help_command(self, text: str, line: str, begidx: int, endidx: int) -> List[str]:
"""Completes the command argument of help"""
@@ -2658,49 +2666,34 @@ class Cmd(cmd.Cmd):
strs_to_match = list(topics | visible_commands)
return utils.basic_complete(text, line, begidx, endidx, strs_to_match)
- def complete_help_subcommand(self, text: str, line: str, begidx: int, endidx: int) -> List[str]:
- """Completes the subcommand argument of help"""
-
- # Get all tokens through the one being completed
- tokens, _ = self.tokens_for_completion(line, begidx, endidx)
-
- if not tokens:
- return []
-
- # Must have at least 3 args for 'help command subcommand'
- if len(tokens) < 3:
- return []
+ def complete_help_subcommands(self, text: str, line: str, begidx: int, endidx: int,
+ arg_tokens: Dict[str, List[str]]) -> List[str]:
+ """Completes the subcommands argument of help"""
- # Find where the command is by skipping past any flags
- cmd_index = 1
- for cur_token in tokens[cmd_index:]:
- if not cur_token.startswith('-'):
- break
- cmd_index += 1
-
- if cmd_index >= len(tokens):
+ # Make sure we have a command whose subcommands we will complete
+ command = arg_tokens['command'][0]
+ if not command:
return []
- command = tokens[cmd_index]
- matches = []
-
# Check if this command uses argparse
func = self.cmd_func(command)
argparser = getattr(func, CMD_ATTR_ARGPARSER, None)
+ if func is None or argparser is None:
+ return []
- if func is not None and argparser is not None:
- from .argparse_completer import AutoCompleter
- completer = AutoCompleter(argparser, self)
- matches = completer.complete_command_help(tokens[cmd_index:], text, line, begidx, endidx)
+ # Combine the command and its subcommand tokens for the AutoCompleter
+ tokens = [command] + arg_tokens['subcommands']
- return matches
+ from .argparse_completer import AutoCompleter
+ completer = AutoCompleter(argparser, self)
+ return completer.complete_subcommand_help(tokens, text, line, begidx, endidx)
help_parser = Cmd2ArgumentParser(description="List available commands or provide "
"detailed help for a specific command")
help_parser.add_argument('command', nargs=argparse.OPTIONAL, help="command to retrieve help for",
completer_method=complete_help_command)
- help_parser.add_argument('subcommand', nargs=argparse.REMAINDER, help="subcommand to retrieve help for",
- completer_method=complete_help_subcommand)
+ help_parser.add_argument('subcommands', nargs=argparse.REMAINDER, help="subcommand(s) to retrieve help for",
+ completer_method=complete_help_subcommands)
help_parser.add_argument('-v', '--verbose', action='store_true',
help="print a list of all commands with descriptions of each")
@@ -2724,7 +2717,7 @@ class Cmd(cmd.Cmd):
if func is not None and argparser is not None:
from .argparse_completer import AutoCompleter
completer = AutoCompleter(argparser, self)
- tokens = [args.command] + args.subcommand
+ 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='')
@@ -2959,14 +2952,11 @@ class Cmd(cmd.Cmd):
choice = int(response)
if choice < 1:
raise IndexError
- result = fulloptions[choice - 1][0]
- break
+ return fulloptions[choice - 1][0]
except (ValueError, IndexError):
self.poutput("{!r} isn't a valid choice. Pick a number between 1 and {}:".format(
response, len(fulloptions)))
- return result
-
def _get_read_only_settings(self) -> str:
"""Return a summary report of read-only settings which the user cannot modify at runtime.
@@ -4121,8 +4111,7 @@ class Cmd(cmd.Cmd):
if getattr(func, CMD_ATTR_HELP_CATEGORY, None) == category:
self.disable_command(cmd_name, message_to_print)
- # noinspection PyUnusedLocal
- def _report_disabled_command_usage(self, *args, message_to_print: str, **kwargs) -> None:
+ def _report_disabled_command_usage(self, *_args, message_to_print: str, **_kwargs) -> None:
"""
Report when a disabled command has been run or had help called on it
:param args: not used