Skip to content

Commit fb3486f

Browse files
authored
Merge pull request #604 from python-cmd2/tweaks
Made it so default_to_shell results in do_shell being called so that output can be captured
2 parents c3a7380 + a73a3c4 commit fb3486f

File tree

3 files changed

+41
-60
lines changed

3 files changed

+41
-60
lines changed

cmd2/cmd2.py

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,7 @@ def perror(self, err: Union[str, Exception], traceback_war: bool=True, err_color
632632
err_msg = err_color + err_msg + Fore.RESET
633633
self.decolorized_write(sys.stderr, err_msg)
634634

635-
if traceback_war:
635+
if traceback_war and not self.debug:
636636
war = "To enable full traceback, run the following command: 'set debug true'\n"
637637
war = war_color + war + Fore.RESET
638638
self.decolorized_write(sys.stderr, war)
@@ -1893,6 +1893,8 @@ def _redirect_output(self, statement: Statement) -> None:
18931893
try:
18941894
self.pipe_proc = subprocess.Popen(statement.pipe_to, stdin=subproc_stdin)
18951895
except Exception as ex:
1896+
self.perror('Not piping because - {}'.format(ex), traceback_war=False)
1897+
18961898
# Restore stdout to what it was and close the pipe
18971899
self.stdout.close()
18981900
subproc_stdin.close()
@@ -1901,8 +1903,6 @@ def _redirect_output(self, statement: Statement) -> None:
19011903
self.kept_state = None
19021904
self.redirecting = False
19031905

1904-
# Re-raise the exception
1905-
raise ex
19061906
elif statement.output:
19071907
import tempfile
19081908
if (not statement.output_to) and (not self.can_clip):
@@ -1920,7 +1920,7 @@ def _redirect_output(self, statement: Statement) -> None:
19201920
try:
19211921
sys.stdout = self.stdout = open(statement.output_to, mode)
19221922
except OSError as ex:
1923-
self.perror('Not Redirecting because - {}'.format(ex), traceback_war=False)
1923+
self.perror('Not redirecting because - {}'.format(ex), traceback_war=False)
19241924
self.redirecting = False
19251925
else:
19261926
# going to a paste buffer
@@ -2007,8 +2007,10 @@ def onecmd(self, statement: Union[Statement, str]) -> bool:
20072007
stop = func(statement)
20082008

20092009
else:
2010-
self.default(statement)
2011-
stop = False
2010+
stop = self.default(statement)
2011+
2012+
if stop is None:
2013+
stop = False
20122014

20132015
return stop
20142016

@@ -2056,19 +2058,18 @@ def _run_macro(self, statement: Statement) -> bool:
20562058
# Run the resolved command
20572059
return self.onecmd_plus_hooks(resolved)
20582060

2059-
def default(self, statement: Statement) -> None:
2061+
def default(self, statement: Statement) -> Optional[bool]:
20602062
"""Executed when the command given isn't a recognized command implemented by a do_* method.
20612063
20622064
:param statement: Statement object with parsed input
20632065
"""
20642066
if self.default_to_shell:
2065-
result = os.system(statement.command_and_args)
2066-
# If os.system() succeeded, then don't print warning about unknown command
2067-
if not result:
2068-
return
2067+
if 'shell' not in self.exclude_from_history:
2068+
self.history.append(statement.raw)
20692069

2070-
# Print out a message stating this is an unknown command
2071-
self.poutput('*** Unknown syntax: {}\n'.format(statement.command_and_args))
2070+
return self.do_shell(statement.command_and_args)
2071+
else:
2072+
self.poutput('*** {} is not a recognized command, alias, or macro\n'.format(statement.command))
20722073

20732074
def pseudo_raw_input(self, prompt: str) -> str:
20742075
"""Began life as a copy of cmd's cmdloop; like raw_input but
@@ -2803,7 +2804,7 @@ def show(self, args: argparse.Namespace, parameter: str='') -> None:
28032804
:param args: argparse parsed arguments from the set command
28042805
:param parameter: optional search parameter
28052806
"""
2806-
param = parameter.strip().lower()
2807+
param = utils.norm_fold(parameter.strip())
28072808
result = {}
28082809
maxlen = 0
28092810

@@ -2844,7 +2845,7 @@ def do_set(self, args: argparse.Namespace) -> None:
28442845
# Check if param was passed in
28452846
if not args.param:
28462847
return self.show(args)
2847-
param = args.param.strip().lower()
2848+
param = utils.norm_fold(args.param.strip())
28482849

28492850
# Check if value was passed in
28502851
if not args.value:
@@ -3156,15 +3157,19 @@ def load_ipy(app):
31563157
history_parser_group.add_argument('-e', '--edit', action='store_true',
31573158
help='edit and then run selected history items')
31583159
history_parser_group.add_argument('-s', '--script', action='store_true', help='output commands in script format')
3159-
history_parser_group.add_argument('-o', '--output-file', metavar='FILE', help='output commands to a script file')
3160-
history_parser_group.add_argument('-t', '--transcript', help='output commands and results to a transcript file')
3160+
setattr(history_parser_group.add_argument('-o', '--output-file', metavar='FILE',
3161+
help='output commands to a script file'),
3162+
ACTION_ARG_CHOICES, ('path_complete',))
3163+
setattr(history_parser_group.add_argument('-t', '--transcript',
3164+
help='output commands and results to a transcript file'),
3165+
ACTION_ARG_CHOICES, ('path_complete',))
31613166
history_parser_group.add_argument('-c', '--clear', action="store_true", help='clear all history')
3162-
_history_arg_help = """empty all history items
3163-
a one history item by number
3164-
a..b, a:b, a:, ..b items by indices (inclusive)
3165-
[string] items containing string
3166-
/regex/ items matching regular expression"""
3167-
history_parser.add_argument('arg', nargs='?', help=_history_arg_help)
3167+
history_arg_help = ("empty all history items\n"
3168+
"a one history item by number\n"
3169+
"a..b, a:b, a:, ..b items by indices (inclusive)\n"
3170+
"string items containing string\n"
3171+
"/regex/ items matching regular expression")
3172+
history_parser.add_argument('arg', nargs='?', help=history_arg_help)
31683173

31693174
@with_argparser(history_parser)
31703175
def do_history(self, args: argparse.Namespace) -> None:
@@ -3851,7 +3856,6 @@ def get(self, getme: Optional[Union[int, str]]=None) -> List[HistoryItem]:
38513856
end = int(end)
38523857
return self[start:end]
38533858

3854-
# noinspection PyUnresolvedReferences
38553859
getme = getme.strip()
38563860

38573861
if getme.startswith(r'/') and getme.endswith(r'/'):
@@ -3871,7 +3875,7 @@ def isin(hi):
38713875
:param hi: HistoryItem
38723876
:return: bool - True if search matches
38733877
"""
3874-
return getme.lower() in hi.lowercase
3878+
return utils.norm_fold(getme) in utils.norm_fold(hi)
38753879
return [itm for itm in self if isin(itm)]
38763880

38773881

tests/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
arg empty all history items
6060
a one history item by number
6161
a..b, a:b, a:, ..b items by indices (inclusive)
62-
[string] items containing string
62+
string items containing string
6363
/regex/ items matching regular expression
6464
6565
optional arguments:

tests/test_cmd2.py

Lines changed: 11 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ def test_pyscript_requires_an_argument(base_app, capsys):
298298

299299
def test_base_error(base_app):
300300
out = run_cmd(base_app, 'meow')
301-
assert out == ["*** Unknown syntax: meow"]
301+
assert "is not a recognized command" in out[0]
302302

303303

304304
@pytest.fixture
@@ -803,8 +803,7 @@ def test_pipe_to_shell_error(base_app, capsys):
803803
run_cmd(base_app, 'help | foobarbaz.this_does_not_exist')
804804
out, err = capsys.readouterr()
805805
assert not out
806-
expected_error = 'FileNotFoundError'
807-
assert err.startswith("EXCEPTION of type '{}' occurred with message:".format(expected_error))
806+
assert err.startswith("ERROR: Not piping because")
808807

809808

810809
@pytest.mark.skipif(not clipboard.can_clip,
@@ -1042,12 +1041,12 @@ def hook_failure():
10421041

10431042
def test_precmd_hook_success(base_app):
10441043
out = base_app.onecmd_plus_hooks('help')
1045-
assert out is None
1044+
assert out is False
10461045

10471046

10481047
def test_precmd_hook_failure(hook_failure):
10491048
out = hook_failure.onecmd_plus_hooks('help')
1050-
assert out == True
1049+
assert out is True
10511050

10521051

10531052
class SayApp(cmd2.Cmd):
@@ -1098,40 +1097,18 @@ def __init__(self, *args, **kwargs):
10981097
super().__init__(*args, **kwargs)
10991098
self.default_to_shell = True
11001099

1101-
@pytest.fixture
1102-
def shell_app():
1103-
app = ShellApp()
1104-
app.stdout = utils.StdSim(app.stdout)
1105-
return app
1106-
1107-
def test_default_to_shell_unknown(shell_app):
1108-
unknown_command = 'zyxcw23'
1109-
out = run_cmd(shell_app, unknown_command)
1110-
assert out == ["*** Unknown syntax: {}".format(unknown_command)]
1111-
1112-
def test_default_to_shell_good(capsys):
1113-
app = cmd2.Cmd()
1114-
app.default_to_shell = True
1100+
def test_default_to_shell(base_app, monkeypatch):
11151101
if sys.platform.startswith('win'):
11161102
line = 'dir'
11171103
else:
11181104
line = 'ls'
1119-
statement = app.statement_parser.parse(line)
1120-
retval = app.default(statement)
1121-
assert not retval
1122-
out, err = capsys.readouterr()
1123-
assert out == ''
1124-
1125-
def test_default_to_shell_failure(capsys):
1126-
app = cmd2.Cmd()
1127-
app.default_to_shell = True
1128-
line = 'ls does_not_exist.xyz'
1129-
statement = app.statement_parser.parse(line)
1130-
retval = app.default(statement)
1131-
assert not retval
1132-
out, err = capsys.readouterr()
1133-
assert out == "*** Unknown syntax: {}\n".format(line)
11341105

1106+
base_app.default_to_shell = True
1107+
m = mock.Mock()
1108+
monkeypatch.setattr("{}.Popen".format('subprocess'), m)
1109+
out = run_cmd(base_app, line)
1110+
assert out == []
1111+
assert m.called
11351112

11361113
def test_ansi_prompt_not_esacped(base_app):
11371114
from cmd2.rl_utils import rl_make_safe_prompt

0 commit comments

Comments
 (0)