summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md4
-rw-r--r--cmd2/cmd2.py17
-rw-r--r--cmd2/decorators.py42
-rw-r--r--tests/test_argparse.py37
4 files changed, 78 insertions, 22 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 01180001..03b88d03 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,7 +3,9 @@
* Made `ipy` consistent with `py` in the following ways
* `ipy` returns whether any of the commands run in it returned True to stop command loop
* `Cmd.in_pyscript()` returns True while in `ipy`.
- * Starting `ipy` when `Cmd.in_pyscript()` is already True is not allowed.
+ * Starting `ipy` when `Cmd.in_pyscript()` is already True is not allowed.
+ * `with_argument_list`, `with_argparser`, and `with_argparser_and_unknown_args` wrappers now pass
+ `kwargs` through to their wrapped command function.
## 1.0.2 (April 06, 2020)
* Bug Fixes
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index 317b707f..d14b4d99 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -3169,17 +3169,16 @@ class Cmd(cmd.Cmd):
py_parser.add_argument('command', nargs=argparse.OPTIONAL, help="command to run")
py_parser.add_argument('remainder', nargs=argparse.REMAINDER, help="remainder of command")
- # This is a hidden flag for telling do_py to run a pyscript. It is intended only to be used by run_pyscript
- # after it sets up sys.argv for the script being run. When this flag is present, it takes precedence over all
- # other arguments.
- py_parser.add_argument('--pyscript', help=argparse.SUPPRESS)
-
# Preserve quotes since we are passing these strings to Python
@with_argparser(py_parser, preserve_quotes=True)
- def do_py(self, args: argparse.Namespace) -> Optional[bool]:
+ def do_py(self, args: argparse.Namespace, *, pyscript: Optional[str] = None) -> Optional[bool]:
"""
Enter an interactive Python shell
+ :param args: Namespace of args on the command line
+ :param pyscript: optional path to a pyscript file to run. This is intended only to be used by run_pyscript
+ after it sets up sys.argv for the script. If populated, this takes precedence over all
+ other arguments. (Defaults to None)
:return: True if running of commands should stop
"""
def py_quit():
@@ -3211,9 +3210,9 @@ class Cmd(cmd.Cmd):
localvars['self'] = self
# Handle case where we were called by run_pyscript
- if args.pyscript:
+ if pyscript is not None:
# Read the script file
- expanded_filename = os.path.expanduser(utils.strip_quotes(args.pyscript))
+ expanded_filename = os.path.expanduser(pyscript)
try:
with open(expanded_filename) as f:
@@ -3320,7 +3319,7 @@ class Cmd(cmd.Cmd):
sys.argv = [args.script_path] + args.script_arguments
# noinspection PyTypeChecker
- py_return = self.do_py('--pyscript {}'.format(utils.quote_string(args.script_path)))
+ py_return = self.do_py('', pyscript=args.script_path)
finally:
# Restore command line arguments to original state
diff --git a/cmd2/decorators.py b/cmd2/decorators.py
index deac4701..dc196032 100644
--- a/cmd2/decorators.py
+++ b/cmd2/decorators.py
@@ -1,7 +1,7 @@
# coding=utf-8
"""Decorators for ``cmd2`` commands"""
import argparse
-from typing import Callable, List, Optional, Union
+from typing import Any, Callable, Dict, List, Optional, Union
from . import constants
from .exceptions import Cmd2ArgparseError
@@ -53,12 +53,20 @@ def with_argument_list(*args: List[Callable], preserve_quotes: bool = False) ->
def arg_decorator(func: Callable):
@functools.wraps(func)
- def cmd_wrapper(cmd2_app, statement: Union[Statement, str]):
+ def cmd_wrapper(cmd2_app, statement: Union[Statement, str], **kwargs: Dict[str, Any]) -> Optional[bool]:
+ """
+ Command function wrapper which translates command line into an argument list and calls actual command function
+
+ :param cmd2_app: CLI instance passed as self parameter to command function
+ :param statement: command line string or already generated Statement
+ :param kwargs: any keyword arguments being passed to command function
+ :return: return value of command function
+ """
_, parsed_arglist = cmd2_app.statement_parser.get_command_arg_list(command_name,
statement,
preserve_quotes)
- return func(cmd2_app, parsed_arglist)
+ return func(cmd2_app, parsed_arglist, **kwargs)
command_name = func.__name__[len(constants.COMMAND_FUNC_PREFIX):]
cmd_wrapper.__doc__ = func.__doc__
@@ -132,7 +140,17 @@ def with_argparser_and_unknown_args(parser: argparse.ArgumentParser, *,
def arg_decorator(func: Callable):
@functools.wraps(func)
- def cmd_wrapper(cmd2_app, statement: Union[Statement, str]):
+ def cmd_wrapper(cmd2_app, statement: Union[Statement, str], **kwargs: Dict[str, Any]) -> Optional[bool]:
+ """
+ Command function wrapper which translates command line into argparse Namespace and calls actual
+ command function
+
+ :param cmd2_app: CLI instance passed as self parameter to command function
+ :param statement: command line string or already generated Statement
+ :param kwargs: any keyword arguments being passed to command function
+ :return: return value of command function
+ :raises: Cmd2ArgparseError if argparse has error parsing command line
+ """
statement, parsed_arglist = cmd2_app.statement_parser.get_command_arg_list(command_name,
statement,
preserve_quotes)
@@ -148,7 +166,7 @@ def with_argparser_and_unknown_args(parser: argparse.ArgumentParser, *,
raise Cmd2ArgparseError
else:
setattr(args, '__statement__', statement)
- return func(cmd2_app, args, unknown)
+ return func(cmd2_app, args, unknown, **kwargs)
# argparser defaults the program name to sys.argv[0], but we want it to be the name of our command
command_name = func.__name__[len(constants.COMMAND_FUNC_PREFIX):]
@@ -204,7 +222,17 @@ def with_argparser(parser: argparse.ArgumentParser, *,
def arg_decorator(func: Callable):
@functools.wraps(func)
- def cmd_wrapper(cmd2_app, statement: Union[Statement, str]):
+ def cmd_wrapper(cmd2_app, statement: Union[Statement, str], **kwargs: Dict[str, Any]) -> Optional[bool]:
+ """
+ Command function wrapper which translates command line into argparse Namespace and calls actual
+ command function
+
+ :param cmd2_app: CLI instance passed as self parameter to command function
+ :param statement: command line string or already generated Statement
+ :param kwargs: any keyword arguments being passed to command function
+ :return: return value of command function
+ :raises: Cmd2ArgparseError if argparse has error parsing command line
+ """
statement, parsed_arglist = cmd2_app.statement_parser.get_command_arg_list(command_name,
statement,
preserve_quotes)
@@ -220,7 +248,7 @@ def with_argparser(parser: argparse.ArgumentParser, *,
raise Cmd2ArgparseError
else:
setattr(args, '__statement__', statement)
- return func(cmd2_app, args)
+ return func(cmd2_app, args, **kwargs)
# argparser defaults the program name to sys.argv[0], but we want it to be the name of our command
command_name = func.__name__[len(constants.COMMAND_FUNC_PREFIX):]
diff --git a/tests/test_argparse.py b/tests/test_argparse.py
index 21ec17e8..9510e8f2 100644
--- a/tests/test_argparse.py
+++ b/tests/test_argparse.py
@@ -4,11 +4,11 @@
Cmd2 testing for argument parsing
"""
import argparse
+from typing import Optional
+
import pytest
import cmd2
-from cmd2.utils import StdSim
-
from .conftest import run_cmd
# Prefer statically linked gnureadline if available (for macOS compatibility due to issues with libedit)
@@ -41,7 +41,7 @@ class ArgparseApp(cmd2.Cmd):
say_parser.add_argument('words', nargs='+', help='words to say')
@cmd2.with_argparser(say_parser)
- def do_say(self, args):
+ def do_say(self, args, *, keyword_arg: Optional[str] = None):
"""Repeat what you tell me to."""
words = []
for word in args.words:
@@ -57,6 +57,9 @@ class ArgparseApp(cmd2.Cmd):
self.stdout.write(' '.join(words))
self.stdout.write('\n')
+ if keyword_arg is not None:
+ print(keyword_arg)
+
tag_parser = argparse.ArgumentParser(description='create a html tag')
tag_parser.add_argument('tag', help='tag')
tag_parser.add_argument('content', nargs='+', help='content to surround with tag')
@@ -71,12 +74,15 @@ class ArgparseApp(cmd2.Cmd):
self.stdout.write('{}'.format(args.custom_stuff))
@cmd2.with_argument_list
- def do_arglist(self, arglist):
+ def do_arglist(self, arglist, *, keyword_arg: Optional[str] = None):
if isinstance(arglist, list):
self.stdout.write('True')
else:
self.stdout.write('False')
+ if keyword_arg is not None:
+ print(keyword_arg)
+
@cmd2.with_argument_list(preserve_quotes=True)
def do_preservelist(self, arglist):
self.stdout.write('{}'.format(arglist))
@@ -86,7 +92,7 @@ class ArgparseApp(cmd2.Cmd):
known_parser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE')
known_parser.add_argument('-r', '--repeat', type=int, help='output [n] times')
@cmd2.with_argparser_and_unknown_args(known_parser)
- def do_speak(self, args, extra):
+ def do_speak(self, args, extra, *, keyword_arg: Optional[str] = None):
"""Repeat what you tell me to."""
words = []
for word in extra:
@@ -102,6 +108,9 @@ class ArgparseApp(cmd2.Cmd):
self.stdout.write(' '.join(words))
self.stdout.write('\n')
+ if keyword_arg is not None:
+ print(keyword_arg)
+
@cmd2.with_argparser_and_unknown_args(argparse.ArgumentParser(), preserve_quotes=True)
def do_test_argparse_with_list_quotes(self, args, extra):
self.stdout.write('{}'.format(' '.join(extra)))
@@ -129,6 +138,12 @@ def test_argparse_remove_quotes(argparse_app):
out, err = run_cmd(argparse_app, 'say "hello there"')
assert out == ['hello there']
+def test_argparser_kwargs(argparse_app, capsys):
+ """Test with_argparser wrapper passes through kwargs to command function"""
+ argparse_app.do_say('word', keyword_arg="foo")
+ out, err = capsys.readouterr()
+ assert out == "foo\n"
+
def test_argparse_preserve_quotes(argparse_app):
out, err = run_cmd(argparse_app, 'tag mytag "hello"')
assert out[0] == '<mytag>"hello"</mytag>'
@@ -161,6 +176,12 @@ def test_argparser_correct_args_with_quotes_and_midline_options(argparse_app):
out, err = run_cmd(argparse_app, "speak 'This is a' -s test of the emergency broadcast system!")
assert out == ['THIS IS A TEST OF THE EMERGENCY BROADCAST SYSTEM!']
+def test_argparser_and_unknown_args_kwargs(argparse_app, capsys):
+ """Test with_argparser_and_unknown_args wrapper passes through kwargs to command function"""
+ argparse_app.do_speak('', keyword_arg="foo")
+ out, err = capsys.readouterr()
+ assert out == "foo\n"
+
def test_argparse_quoted_arguments_multiple(argparse_app):
out, err = run_cmd(argparse_app, 'say "hello there" "rick & morty"')
assert out == ['hello there rick & morty']
@@ -186,6 +207,12 @@ def test_arglist(argparse_app):
out, err = run_cmd(argparse_app, 'arglist "we should" get these')
assert out[0] == 'True'
+def test_arglist_kwargs(argparse_app, capsys):
+ """Test with_argument_list wrapper passes through kwargs to command function"""
+ argparse_app.do_arglist('arg', keyword_arg="foo")
+ out, err = capsys.readouterr()
+ assert out == "foo\n"
+
def test_preservelist(argparse_app):
out, err = run_cmd(argparse_app, 'preservelist foo "bar baz"')
assert out[0] == "['foo', '\"bar baz\"']"