Skip to content

Commit b84798c

Browse files
committed
Negative ending history indices include the referenced command, instead of excluding it
1 parent a4ff1a4 commit b84798c

File tree

3 files changed

+32
-16
lines changed

3 files changed

+32
-16
lines changed

cmd2/history.py

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ def get(self, index: Union[int, str]) -> HistoryItem:
9797
"""
9898
index = int(index)
9999
if index == 0:
100-
raise IndexError
100+
raise IndexError('The first command in history is command 1.')
101101
elif index < 0:
102102
return self[index]
103103
else:
@@ -108,22 +108,27 @@ def get(self, index: Union[int, str]) -> HistoryItem:
108108
# ^\s* matches any whitespace at the beginning of the
109109
# input. This is here so you don't have to trim the input
110110
#
111-
# (?P<start>-?\d+)? create a capture group named 'start' which matches one
112-
# or more digits, optionally preceeded by a minus sign. This
113-
# group is optional so that we can match a string like '..2'
111+
# (?P<start>-?[1-9]{1}\d*)? create a capture group named 'start' which matches an
112+
# optional minus sign, followed by exactly one non-zero
113+
# digit, and as many other digits as you want. This group
114+
# is optional so that we can match an input string like '..2'.
115+
# This regex will match 1, -1, 10, -10, but not 0 or -0.
114116
#
115117
# (?P<separator>:|(\.{2,}))? create a capture group named 'separator' which matches either
116118
# a colon or two periods. This group is optional so we can
117119
# match a string like '3'
118120
#
119-
# (?P<end>-?\d+)? create a capture group named 'end' which matches one or more
120-
# digits, optionally preceeded by a minus sign. This group is
121-
# optional so that we can match a string like ':' or '5:'
121+
# (?P<end>-?[1-9]{1}\d*)? create a capture group named 'end' which matches an
122+
# optional minus sign, followed by exactly one non-zero
123+
# digit, and as many other digits as you want. This group is
124+
# optional so that we can match an input string like ':'
125+
# or '5:'. This regex will match 1, -1, 10, -10, but not
126+
# 0 or -0.
122127
#
123128
# \s*$ match any whitespace at the end of the input. This is here so
124129
# you don't have to trim the input
125130
#
126-
spanpattern = re.compile(r'^\s*(?P<start>-?\d+)?(?P<separator>:|(\.{2,}))?(?P<end>-?\d+)?\s*$')
131+
spanpattern = re.compile(r'^\s*(?P<start>-?[1-9]{1}\d*)?(?P<separator>:|(\.{2,}))?(?P<end>-?[1-9]{1}\d*)?\s*$')
127132

128133
def span(self, span: str) -> List[HistoryItem]:
129134
"""Return an index or slice of the History list,
@@ -156,7 +161,7 @@ def span(self, span: str) -> List[HistoryItem]:
156161
results = self.spanpattern.search(span)
157162
if not results:
158163
# our regex doesn't match the input, bail out
159-
raise ValueError
164+
raise ValueError('History indices must be positive or negative integers, and may not be zero.')
160165

161166
sep = results.group('separator')
162167
start = results.group('start')
@@ -165,6 +170,16 @@ def span(self, span: str) -> List[HistoryItem]:
165170
end = results.group('end')
166171
if end:
167172
end = int(end)
173+
# modify end so it's inclusive of the last element
174+
if end == -1:
175+
# -1 as the end means include the last command in the array, which in pythonic
176+
# terms means to not provide an ending index. If you put -1 as the ending index
177+
# python excludes the last item in the list.
178+
end = None
179+
elif end < -1:
180+
# if the ending is smaller than -1, make it one larger so it includes
181+
# the element (python native indices exclude the last referenced element)
182+
end += 1
168183

169184
if start is not None and end is not None:
170185
# we have both start and end, return a slice of history

docs/freefeatures.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,9 +293,10 @@ entered, and so forth::
293293

294294
You can use a similar mechanism to display a range of commands. Simply give two
295295
command numbers separated by ``..`` or ``:``, and you will see all commands
296-
between those two numbers::
296+
between, and including, those two numbers::
297297

298-
(Cmd) history 2:3
298+
(Cmd) history 1:3
299+
1 alias create one !echo one
299300
2 alias create two !echo two
300301
3 alias create three !echo three
301302

tests/test_history.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,14 @@ def test_history_class_span(hist):
7777

7878
assert hist.span('1..3') == ['first', 'second', 'third']
7979
assert hist.span('1:3') == ['first', 'second', 'third']
80-
assert hist.span('2:-1') == ['second', 'third']
80+
assert hist.span('2:-1') == ['second', 'third', 'fourth']
8181
assert hist.span('-3:4') == ['second', 'third','fourth']
82-
assert hist.span('-4:-2') == ['first', 'second']
82+
assert hist.span('-4:-2') == ['first', 'second', 'third']
8383

84-
assert hist.span(':-2') == ['first', 'second']
85-
assert hist.span('..-2') == ['first', 'second']
84+
assert hist.span(':-2') == ['first', 'second', 'third']
85+
assert hist.span('..-2') == ['first', 'second', 'third']
8686

87-
value_errors = ['fred', 'fred:joe', 'a..b', '2 ..', '1 : 3']
87+
value_errors = ['fred', 'fred:joe', 'a..b', '2 ..', '1 : 3', '1:0', '0:3']
8888
for tryit in value_errors:
8989
with pytest.raises(ValueError):
9090
hist.span(tryit)

0 commit comments

Comments
 (0)