From 400ab5743362aa89b81d484884dfbc5e0ef388bb Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Fri, 30 Nov 2018 18:55:37 -0500 Subject: Made it so default_to_shell results in do_shell being called so that output can be captured --- cmd2/cmd2.py | 65 ++++++++++++++++++++++++++++++------------------------ tests/conftest.py | 2 +- tests/test_cmd2.py | 45 +++++++++---------------------------- 3 files changed, 48 insertions(+), 64 deletions(-) diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 2a617a75..f0253c65 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -632,7 +632,7 @@ class Cmd(cmd.Cmd): err_msg = err_color + err_msg + Fore.RESET self.decolorized_write(sys.stderr, err_msg) - if traceback_war: + if traceback_war and not self.debug: war = "To enable full traceback, run the following command: 'set debug true'\n" war = war_color + war + Fore.RESET self.decolorized_write(sys.stderr, war) @@ -1893,6 +1893,8 @@ class Cmd(cmd.Cmd): try: self.pipe_proc = subprocess.Popen(statement.pipe_to, stdin=subproc_stdin) except Exception as ex: + self.perror('Not piping because - {}'.format(ex), traceback_war=False) + # Restore stdout to what it was and close the pipe self.stdout.close() subproc_stdin.close() @@ -1901,8 +1903,6 @@ class Cmd(cmd.Cmd): self.kept_state = None self.redirecting = False - # Re-raise the exception - raise ex elif statement.output: import tempfile if (not statement.output_to) and (not self.can_clip): @@ -1920,7 +1920,7 @@ class Cmd(cmd.Cmd): try: sys.stdout = self.stdout = open(statement.output_to, mode) except OSError as ex: - self.perror('Not Redirecting because - {}'.format(ex), traceback_war=False) + self.perror('Not redirecting because - {}'.format(ex), traceback_war=False) self.redirecting = False else: # going to a paste buffer @@ -1998,18 +1998,29 @@ class Cmd(cmd.Cmd): if statement.command in self.macros: stop = self._run_macro(statement) else: - func = self.cmd_func(statement.command) + command = statement.command + func = self.cmd_func(command) + func_arg = statement.args + + if not func and self.default_to_shell: + command = 'shell' + func = self.cmd_func(command) + func_arg = statement.command_and_args + if func: - # Since we have a valid command store it in the history - if statement.command not in self.exclude_from_history: + # Check if this command should be stored in the history + if command not in self.exclude_from_history: self.history.append(statement.raw) - stop = func(statement) + stop = func(func_arg) else: self.default(statement) stop = False + if stop is None: + stop = False + return stop def _run_macro(self, statement: Statement) -> bool: @@ -2057,18 +2068,11 @@ class Cmd(cmd.Cmd): return self.onecmd_plus_hooks(resolved) def default(self, statement: Statement) -> None: - """Executed when the command given isn't a recognized command implemented by a do_* method. + """Called on an input line when the command prefix is not recognized. :param statement: Statement object with parsed input """ - if self.default_to_shell: - result = os.system(statement.command_and_args) - # If os.system() succeeded, then don't print warning about unknown command - if not result: - return - - # Print out a message stating this is an unknown command - self.poutput('*** Unknown syntax: {}\n'.format(statement.command_and_args)) + self.poutput('*** {} is not a recognized command, alias, or macro\n'.format(statement.command)) def pseudo_raw_input(self, prompt: str) -> str: """Began life as a copy of cmd's cmdloop; like raw_input but @@ -2803,7 +2807,7 @@ class Cmd(cmd.Cmd): :param args: argparse parsed arguments from the set command :param parameter: optional search parameter """ - param = parameter.strip().lower() + param = utils.norm_fold(parameter.strip()) result = {} maxlen = 0 @@ -2844,7 +2848,7 @@ class Cmd(cmd.Cmd): # Check if param was passed in if not args.param: return self.show(args) - param = args.param.strip().lower() + param = utils.norm_fold(args.param.strip()) # Check if value was passed in if not args.value: @@ -3156,15 +3160,19 @@ class Cmd(cmd.Cmd): history_parser_group.add_argument('-e', '--edit', action='store_true', help='edit and then run selected history items') history_parser_group.add_argument('-s', '--script', action='store_true', help='output commands in script format') - history_parser_group.add_argument('-o', '--output-file', metavar='FILE', help='output commands to a script file') - history_parser_group.add_argument('-t', '--transcript', help='output commands and results to a transcript file') + setattr(history_parser_group.add_argument('-o', '--output-file', metavar='FILE', + help='output commands to a script file'), + ACTION_ARG_CHOICES, ('path_complete',)) + setattr(history_parser_group.add_argument('-t', '--transcript', + help='output commands and results to a transcript file'), + ACTION_ARG_CHOICES, ('path_complete',)) history_parser_group.add_argument('-c', '--clear', action="store_true", help='clear all history') - _history_arg_help = """empty all history items -a one history item by number -a..b, a:b, a:, ..b items by indices (inclusive) -[string] items containing string -/regex/ items matching regular expression""" - history_parser.add_argument('arg', nargs='?', help=_history_arg_help) + history_arg_help = ("empty all history items\n" + "a one history item by number\n" + "a..b, a:b, a:, ..b items by indices (inclusive)\n" + "string items containing string\n" + "/regex/ items matching regular expression") + history_parser.add_argument('arg', nargs='?', help=history_arg_help) @with_argparser(history_parser) def do_history(self, args: argparse.Namespace) -> None: @@ -3851,7 +3859,6 @@ class History(list): end = int(end) return self[start:end] - # noinspection PyUnresolvedReferences getme = getme.strip() if getme.startswith(r'/') and getme.endswith(r'/'): @@ -3871,7 +3878,7 @@ class History(list): :param hi: HistoryItem :return: bool - True if search matches """ - return getme.lower() in hi.lowercase + return utils.norm_fold(getme) in utils.norm_fold(hi) return [itm for itm in self if isin(itm)] diff --git a/tests/conftest.py b/tests/conftest.py index da7e8b08..ac6a1896 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -59,7 +59,7 @@ positional arguments: arg empty all history items a one history item by number a..b, a:b, a:, ..b items by indices (inclusive) - [string] items containing string + string items containing string /regex/ items matching regular expression optional arguments: diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 9bb85ffe..59c6bb60 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -298,7 +298,7 @@ def test_pyscript_requires_an_argument(base_app, capsys): def test_base_error(base_app): out = run_cmd(base_app, 'meow') - assert out == ["*** Unknown syntax: meow"] + assert "is not a recognized command" in out[0] @pytest.fixture @@ -803,8 +803,7 @@ def test_pipe_to_shell_error(base_app, capsys): run_cmd(base_app, 'help | foobarbaz.this_does_not_exist') out, err = capsys.readouterr() assert not out - expected_error = 'FileNotFoundError' - assert err.startswith("EXCEPTION of type '{}' occurred with message:".format(expected_error)) + assert err.startswith("ERROR: Not piping because") @pytest.mark.skipif(not clipboard.can_clip, @@ -1042,12 +1041,12 @@ def hook_failure(): def test_precmd_hook_success(base_app): out = base_app.onecmd_plus_hooks('help') - assert out is None + assert out is False def test_precmd_hook_failure(hook_failure): out = hook_failure.onecmd_plus_hooks('help') - assert out == True + assert out is True class SayApp(cmd2.Cmd): @@ -1098,40 +1097,18 @@ class ShellApp(cmd2.Cmd): super().__init__(*args, **kwargs) self.default_to_shell = True -@pytest.fixture -def shell_app(): - app = ShellApp() - app.stdout = utils.StdSim(app.stdout) - return app - -def test_default_to_shell_unknown(shell_app): - unknown_command = 'zyxcw23' - out = run_cmd(shell_app, unknown_command) - assert out == ["*** Unknown syntax: {}".format(unknown_command)] - -def test_default_to_shell_good(capsys): - app = cmd2.Cmd() - app.default_to_shell = True +def test_default_to_shell(base_app, monkeypatch): if sys.platform.startswith('win'): line = 'dir' else: line = 'ls' - statement = app.statement_parser.parse(line) - retval = app.default(statement) - assert not retval - out, err = capsys.readouterr() - assert out == '' - -def test_default_to_shell_failure(capsys): - app = cmd2.Cmd() - app.default_to_shell = True - line = 'ls does_not_exist.xyz' - statement = app.statement_parser.parse(line) - retval = app.default(statement) - assert not retval - out, err = capsys.readouterr() - assert out == "*** Unknown syntax: {}\n".format(line) + base_app.default_to_shell = True + m = mock.Mock() + monkeypatch.setattr("{}.Popen".format('subprocess'), m) + out = run_cmd(base_app, line) + assert out == [] + assert m.called def test_ansi_prompt_not_esacped(base_app): from cmd2.rl_utils import rl_make_safe_prompt -- cgit v1.2.1 From a73a3c4020dbf02ec00ceb77c5f3677d8bc94d3c Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Mon, 3 Dec 2018 12:15:43 -0500 Subject: Moved default_to_shell logic back to default() --- cmd2/cmd2.py | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index f0253c65..012b6f6b 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -1998,25 +1998,16 @@ class Cmd(cmd.Cmd): if statement.command in self.macros: stop = self._run_macro(statement) else: - command = statement.command - func = self.cmd_func(command) - func_arg = statement.args - - if not func and self.default_to_shell: - command = 'shell' - func = self.cmd_func(command) - func_arg = statement.command_and_args - + func = self.cmd_func(statement.command) if func: - # Check if this command should be stored in the history - if command not in self.exclude_from_history: + # Since we have a valid command store it in the history + if statement.command not in self.exclude_from_history: self.history.append(statement.raw) - stop = func(func_arg) + stop = func(statement) else: - self.default(statement) - stop = False + stop = self.default(statement) if stop is None: stop = False @@ -2067,12 +2058,18 @@ class Cmd(cmd.Cmd): # Run the resolved command return self.onecmd_plus_hooks(resolved) - def default(self, statement: Statement) -> None: - """Called on an input line when the command prefix is not recognized. + def default(self, statement: Statement) -> Optional[bool]: + """Executed when the command given isn't a recognized command implemented by a do_* method. :param statement: Statement object with parsed input """ - self.poutput('*** {} is not a recognized command, alias, or macro\n'.format(statement.command)) + if self.default_to_shell: + if 'shell' not in self.exclude_from_history: + self.history.append(statement.raw) + + return self.do_shell(statement.command_and_args) + else: + self.poutput('*** {} is not a recognized command, alias, or macro\n'.format(statement.command)) def pseudo_raw_input(self, prompt: str) -> str: """Began life as a copy of cmd's cmdloop; like raw_input but -- cgit v1.2.1