Skip to content

Commit b16b912

Browse files
authored
Merge pull request #228 from python-cmd2/edit_index_bug
Fixed a bug when edit is passed an integer outside the range of history indices
2 parents 7388e0d + 1c60c8a commit b16b912

File tree

2 files changed

+81
-15
lines changed

2 files changed

+81
-15
lines changed

cmd2.py

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,7 +1026,7 @@ def pseudo_raw_input(self, prompt):
10261026
else:
10271027
line = sm.input()
10281028
if self.echo:
1029-
sys.stdout.write('{}{}\n'.format(safe_prompt,line))
1029+
sys.stdout.write('{}{}\n'.format(safe_prompt, line))
10301030
except EOFError:
10311031
line = 'eof'
10321032
else:
@@ -1669,10 +1669,29 @@ def do_edit(self, arg, opts=None):
16691669
filename = None
16701670
if arg and arg[0]:
16711671
try:
1672-
history_item = self._last_matching(int(arg[0]))
1672+
# Try to convert argument to an integer
1673+
history_idx = int(arg[0])
16731674
except ValueError:
1675+
# Argument passed is not convertible to an integer, so treat it as a file path
16741676
filename = arg[0]
16751677
history_item = ''
1678+
else:
1679+
# Argument passed IS convertible to an integer, so treat it as a history index
1680+
1681+
# Save off original index for pringing
1682+
orig_indx = history_idx
1683+
1684+
# Convert negative index into equivalent positive one
1685+
if history_idx < 0:
1686+
history_idx += len(self.history) + 1
1687+
1688+
# Make sure the index is actually within the history
1689+
if 1 <= history_idx <= len(self.history):
1690+
history_item = self._last_matching(history_idx)
1691+
else:
1692+
self.perror('index {!r} does not exist within the history'.format(orig_indx), traceback_war=False)
1693+
return
1694+
16761695
else:
16771696
try:
16781697
history_item = self.history[-1]
@@ -1817,7 +1836,7 @@ def do_load(self, file_path):
18171836
# command queue. Add an "end of script (eos)" command to cleanup the
18181837
# self._script_dir list when done. Specify file encoding in Python
18191838
# 3, but Python 2 doesn't allow that argument to open().
1820-
kwargs = {'encoding' : 'utf-8'} if six.PY3 else {}
1839+
kwargs = {'encoding': 'utf-8'} if six.PY3 else {}
18211840
with open(expanded_path, **kwargs) as target:
18221841
self.cmdqueue = target.read().splitlines() + ['eos'] + self.cmdqueue
18231842
except IOError as e:
@@ -2368,8 +2387,6 @@ def _test_transcript(self, fname, transcript):
23682387

23692388
def _transform_transcript_expected(self, s):
23702389
"""parse the string with slashed regexes into a valid regex"""
2371-
slash = '/'
2372-
backslash = '\\'
23732390
regex = ''
23742391
start = 0
23752392

@@ -2400,7 +2417,8 @@ def _transform_transcript_expected(self, s):
24002417
break
24012418
return regex
24022419

2403-
def _escaped_find(self, regex, s, start, in_regex):
2420+
@staticmethod
2421+
def _escaped_find(regex, s, start, in_regex):
24042422
"""
24052423
Find the next slash in {s} after {start} that is not preceded by a backslash.
24062424
@@ -2446,7 +2464,7 @@ def _escaped_find(self, regex, s, start, in_regex):
24462464
else:
24472465
# slash is not escaped, this is what we are looking for
24482466
break
2449-
return (regex, pos, start)
2467+
return regex, pos, start
24502468

24512469
def tearDown(self):
24522470
if self.cmdapp:

tests/test_cmd2.py

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -812,7 +812,29 @@ def test_edit_file_with_spaces(base_app, request, monkeypatch):
812812
# We think we have an editor, so should expect a system call
813813
m.assert_called_once_with('"{}" "{}"'.format(base_app.editor, filename))
814814

815-
def test_edit_number(base_app, monkeypatch):
815+
def test_edit_blank(base_app, monkeypatch):
816+
# Set a fake editor just to make sure we have one. We aren't really going to call it due to the mock
817+
base_app.editor = 'fooedit'
818+
819+
# Mock out the os.system call so we don't actually open an editor
820+
m = mock.MagicMock(name='system')
821+
monkeypatch.setattr("os.system", m)
822+
823+
# Run help command just so we have a command in history
824+
run_cmd(base_app, 'help')
825+
826+
run_cmd(base_app, 'edit')
827+
828+
# We have an editor, so should expect a system call
829+
m.assert_called_once()
830+
831+
def test_edit_empty_history(base_app, capsys):
832+
run_cmd(base_app, 'edit')
833+
out, err = capsys.readouterr()
834+
assert out == ''
835+
assert err == 'ERROR: edit must be called with argument if history is empty\n'
836+
837+
def test_edit_valid_positive_number(base_app, monkeypatch):
816838
# Set a fake editor just to make sure we have one. We aren't really going to call it due to the mock
817839
base_app.editor = 'fooedit'
818840

@@ -828,7 +850,7 @@ def test_edit_number(base_app, monkeypatch):
828850
# We have an editor, so should expect a system call
829851
m.assert_called_once()
830852

831-
def test_edit_blank(base_app, monkeypatch):
853+
def test_edit_valid_negative_number(base_app, monkeypatch):
832854
# Set a fake editor just to make sure we have one. We aren't really going to call it due to the mock
833855
base_app.editor = 'fooedit'
834856

@@ -839,16 +861,42 @@ def test_edit_blank(base_app, monkeypatch):
839861
# Run help command just so we have a command in history
840862
run_cmd(base_app, 'help')
841863

842-
run_cmd(base_app, 'edit')
864+
run_cmd(base_app, 'edit "-1"')
843865

844866
# We have an editor, so should expect a system call
845867
m.assert_called_once()
846868

847-
def test_edit_empty_history(base_app, capsys):
848-
run_cmd(base_app, 'edit')
849-
out, err = capsys.readouterr()
850-
assert out == ''
851-
assert err == 'ERROR: edit must be called with argument if history is empty\n'
869+
def test_edit_invalid_positive_number(base_app, monkeypatch):
870+
# Set a fake editor just to make sure we have one. We aren't really going to call it due to the mock
871+
base_app.editor = 'fooedit'
872+
873+
# Mock out the os.system call so we don't actually open an editor
874+
m = mock.MagicMock(name='system')
875+
monkeypatch.setattr("os.system", m)
876+
877+
# Run help command just so we have a command in history
878+
run_cmd(base_app, 'help')
879+
880+
run_cmd(base_app, 'edit 23')
881+
882+
# History index is invalid, so should expect a system call
883+
m.assert_not_called()
884+
885+
def test_edit_invalid_negative_number(base_app, monkeypatch):
886+
# Set a fake editor just to make sure we have one. We aren't really going to call it due to the mock
887+
base_app.editor = 'fooedit'
888+
889+
# Mock out the os.system call so we don't actually open an editor
890+
m = mock.MagicMock(name='system')
891+
monkeypatch.setattr("os.system", m)
892+
893+
# Run help command just so we have a command in history
894+
run_cmd(base_app, 'help')
895+
896+
run_cmd(base_app, 'edit "-23"')
897+
898+
# History index is invalid, so should expect a system call
899+
m.assert_not_called()
852900

853901

854902
def test_base_py_interactive(base_app):

0 commit comments

Comments
 (0)