From 47f8652fa467b2d140b1097b3167f968b0188451 Mon Sep 17 00:00:00 2001 From: Eric Lin Date: Thu, 27 Aug 2020 17:07:43 -0400 Subject: [PATCH] the with_argparse() decorator was incorrectly using a parsed statement object to search for the original function arguments. Switched to search for the original statement value instead --- CHANGELOG.md | 5 +++++ cmd2/decorators.py | 8 ++++---- tests/test_cmd2.py | 17 +++++++++++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index acb29c936..12478a29d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.3.7 (August 27, 2020) +* Bug Fixes + * Fixes an issue introduced in 1.3.0 with processing command strings containing terminator/separator + character(s) that are manually passed to a command that uses argparse. + ## 1.3.6 (August 27, 2020) * Breaking changes * The functions cmd2 adds to Namespaces (`get_statement()` and `get_handler()`) are now diff --git a/cmd2/decorators.py b/cmd2/decorators.py index c26891023..4ee61754d 100644 --- a/cmd2/decorators.py +++ b/cmd2/decorators.py @@ -35,7 +35,7 @@ def cat_decorator(func): return cat_decorator ########################## -# The _parse_positionals and _swap_args decorators allow for additional positional args to be preserved +# The _parse_positionals and _arg_swap functions allow for additional positional args to be preserved # in cmd2 command functions/callables. As long as the 2-ple of arguments we expect to be there can be # found we can swap out the statement with each decorator's specific parameters ########################## @@ -276,9 +276,9 @@ def cmd_wrapper(*args: Any, **kwargs: Dict[str, Any]) -> Optional[bool]: :return: return value of command function :raises: Cmd2ArgparseError if argparse has error parsing command line """ - cmd2_app, statement = _parse_positionals(args) + cmd2_app, statement_arg = _parse_positionals(args) statement, parsed_arglist = cmd2_app.statement_parser.get_command_arg_list(command_name, - statement, + statement_arg, preserve_quotes) if ns_provider is None: @@ -314,7 +314,7 @@ def cmd_wrapper(*args: Any, **kwargs: Dict[str, Any]) -> Optional[bool]: if hasattr(ns, constants.NS_ATTR_SUBCMD_HANDLER): delattr(ns, constants.NS_ATTR_SUBCMD_HANDLER) - args_list = _arg_swap(args, statement, *new_args) + args_list = _arg_swap(args, statement_arg, *new_args) return func(*args_list, **kwargs) # argparser defaults the program name to sys.argv[0], but we want it to be the name of our command diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 8688e124f..3b240e4ed 100755 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -217,6 +217,23 @@ def test_shell_last_result(base_app): run_cmd(base_app, 'shell fake') assert base_app.last_result is not None + +def test_shell_manual_call(base_app): + # Verifies crash from Issue #986 doesn't happen + cmds = [ + 'echo "hi"', + 'echo "there"', + 'echo "cmd2!"' + ] + cmd = ';'.join(cmds) + + base_app.do_shell(cmd) + + cmd = '&&'.join(cmds) + + base_app.do_shell(cmd) + + def test_base_py(base_app): # Make sure py can't edit Cmd.py_locals. It used to be that cmd2 was passing its py_locals # dictionary to the py environment instead of a shallow copy.