Skip to content

Commit 16d8e96

Browse files
committed
Reworked get() on History class
1 parent d6c6cf3 commit 16d8e96

File tree

2 files changed

+87
-42
lines changed

2 files changed

+87
-42
lines changed

cmd2/history.py

Lines changed: 57 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,17 @@ def pr(self, script=False, expanded=False, verbose=False) -> str:
6060

6161

6262
class History(list):
63-
""" A list of HistoryItems that knows how to respond to user requests. """
63+
"""A list of HistoryItems that knows how to respond to user requests.
64+
65+
Here are some key methods:
66+
67+
select() - parse user input and return a list of relevant history items
68+
str_search() - return a list of history items which contain the given string
69+
regex_search() - return a list of history items which match a given regex
70+
get() - return a single element of the list, using 1 based indexing
71+
span() - given a 1-based slice, return the appropriate list of history items
72+
73+
"""
6474

6575
# noinspection PyMethodMayBeStatic
6676
def _zero_based_index(self, onebased: int) -> int:
@@ -80,10 +90,30 @@ def _to_index(self, raw: str) -> Optional[int]:
8090
spanpattern = re.compile(r'^\s*(?P<start>-?\d+)?\s*(?P<separator>:|(\.{2,}))?\s*(?P<end>-?\d+)?\s*$')
8191

8292
def span(self, raw: str) -> List[HistoryItem]:
83-
"""Parses the input string search for a span pattern and if if found, returns a slice from the History list.
93+
"""Parses the input string and return a slice from the History list.
94+
95+
:param raw: string potentially containing a span
96+
:return: a list of HistoryItems
97+
98+
This method can accommodate input in any of these forms:
99+
100+
a
101+
-a
102+
a..b or a:b
103+
a.. or a:
104+
..a or :a
105+
-a.. or -a:
106+
..-a or :-a
107+
108+
Different from native python indexing and slicing of arrays, this method
109+
uses 1-based array numbering. Users who are not programmers can't grok
110+
0 based numbering. Programmers can grok either. Which reminds me, there
111+
are only two hard problems in programming:
112+
113+
- naming
114+
- cache invalidation
115+
- off by one errors
84116
85-
:param raw: string potentially containing a span of the forms a..b, a:b, a:, ..b
86-
:return: slice from the History list
87117
"""
88118
if raw.lower() in ('*', '-', 'all'):
89119
raw = ':'
@@ -116,37 +146,30 @@ def append(self, new: Statement) -> None:
116146
list.append(self, new)
117147
new.idx = len(self)
118148

119-
def get(self, getme: Optional[Union[int, str]]=None) -> List[HistoryItem]:
120-
"""Get an item or items from the History list using 1-based indexing.
149+
def get(self, index: Union[int, str]) -> HistoryItem:
150+
"""Get item from the History list using 1-based indexing.
121151
122-
:param getme: optional item(s) to get (either an integer index or string to search for)
123-
:return: list of HistoryItems matching the retrieval criteria
152+
:param index: optional item to get (index as either integer or string)
153+
:return: a single HistoryItem
124154
"""
125-
if not getme:
126-
return self
127-
try:
128-
getme = int(getme)
129-
if getme < 0:
130-
return self[:(-1 * getme)]
131-
else:
132-
return [self[getme - 1]]
133-
except IndexError:
134-
return []
135-
except ValueError:
136-
range_result = self.rangePattern.search(getme)
137-
if range_result:
138-
start = range_result.group('start') or None
139-
end = range_result.group('start') or None
140-
if start:
141-
start = int(start) - 1
142-
if end:
143-
end = int(end)
144-
return self[start:end]
145-
146-
getme = getme.strip()
147-
148-
if getme.startswith(r'/') and getme.endswith(r'/'):
149-
finder = re.compile(getme[1:-1], re.DOTALL | re.MULTILINE | re.IGNORECASE)
155+
index = int(index)
156+
if index == 0:
157+
raise IndexError
158+
elif index < 0:
159+
return self[index]
160+
else:
161+
return self[index - 1]
162+
163+
164+
165+
def str_search(self, search: str) -> List[HistoryItem]:
166+
pass
167+
168+
def regex_search(self, regex: str) -> List[HistoryItem]:
169+
regex = regex.strip()
170+
171+
if regex.startswith(r'/') and regex.endswith(r'/'):
172+
finder = re.compile(regex[1:-1], re.DOTALL | re.MULTILINE | re.IGNORECASE)
150173

151174
def isin(hi):
152175
"""Listcomp filter function for doing a regular expression search of History.
@@ -162,6 +185,6 @@ def isin(hi):
162185
:param hi: HistoryItem
163186
:return: bool - True if search matches
164187
"""
165-
srch = utils.norm_fold(getme)
188+
srch = utils.norm_fold(regex)
166189
return srch in utils.norm_fold(hi) or srch in utils.norm_fold(hi.expanded)
167190
return [itm for itm in self if isin(itm)]

tests/test_history.py

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,36 @@ def test_history_class_span(hist):
7575
assert h.span('*') == h
7676

7777
def test_history_class_get(hist):
78-
h = hist
79-
assert h == ['first', 'second', 'third', 'fourth']
80-
assert h.get('') == h
81-
assert h.get('-2') == h[:-2]
82-
assert h.get('5') == []
83-
assert h.get('2-3') == ['second'] # Exclusive of end
84-
assert h.get('ir') == ['first', 'third'] # Normal string search for all elements containing "ir"
85-
assert h.get('/i.*d/') == ['third'] # Regex string search "i", then anything, then "d"
78+
assert hist.get('1') == 'first'
79+
assert hist.get(3) == 'third'
80+
assert hist.get('-2') == hist[-2]
81+
assert hist.get(-1) == 'fourth'
82+
83+
with pytest.raises(IndexError):
84+
hist.get(0)
85+
with pytest.raises(IndexError):
86+
hist.get('0')
87+
88+
with pytest.raises(IndexError):
89+
hist.get('5')
90+
with pytest.raises(ValueError):
91+
hist.get('2-3')
92+
with pytest.raises(ValueError):
93+
hist.get('1..2')
94+
with pytest.raises(ValueError):
95+
hist.get('3:4')
96+
with pytest.raises(ValueError):
97+
hist.get('fred')
98+
with pytest.raises(ValueError):
99+
hist.get('')
100+
with pytest.raises(TypeError):
101+
hist.get(None)
102+
103+
def test_history_str_search(hist):
104+
assert hist.get('ir') == ['first', 'third']
105+
106+
def test_history_regex_search(hist):
107+
assert hist.get('/i.*d/') == ['third']
86108

87109
def test_base_history(base_app):
88110
run_cmd(base_app, 'help')

0 commit comments

Comments
 (0)