Skip to content

Commit 69308f4

Browse files
committed
Refactor HistoryItem to not subclass str
1 parent ba6c5ed commit 69308f4

File tree

3 files changed

+127
-60
lines changed

3 files changed

+127
-60
lines changed

cmd2/cmd2.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3410,9 +3410,9 @@ def do_history(self, args: argparse.Namespace) -> None:
34103410
traceback_war=False)
34113411
else:
34123412
for runme in history:
3413-
self.pfeedback(runme)
3413+
self.pfeedback(runme.statement.raw)
34143414
if runme:
3415-
self.onecmd_plus_hooks(runme)
3415+
self.onecmd_plus_hooks(runme.statement.raw)
34163416
elif args.edit:
34173417
import tempfile
34183418
fd, fname = tempfile.mkstemp(suffix='.txt', text=True)
@@ -3432,11 +3432,11 @@ def do_history(self, args: argparse.Namespace) -> None:
34323432
elif args.output_file:
34333433
try:
34343434
with open(os.path.expanduser(args.output_file), 'w') as fobj:
3435-
for command in history:
3436-
if command.statement.multiline_command:
3437-
fobj.write('{}\n'.format(command.expanded.rstrip()))
3435+
for item in history:
3436+
if item.statement.multiline_command:
3437+
fobj.write('{}\n'.format(item.statement.expanded_command_line.rstrip()))
34383438
else:
3439-
fobj.write('{}\n'.format(command))
3439+
fobj.write('{}\n'.format(item.statement.raw))
34403440
plural = 's' if len(history) > 1 else ''
34413441
self.pfeedback('{} command{} saved to {}'.format(len(history), plural, args.output_file))
34423442
except Exception as e:
@@ -3547,6 +3547,8 @@ def _generate_transcript(self, history: List[Union[HistoryItem, str]], transcrip
35473547
# the command from the output
35483548
first = True
35493549
command = ''
3550+
if isinstance(history_item, HistoryItem):
3551+
history_item = history_item.statement.raw
35503552
for line in history_item.splitlines():
35513553
if first:
35523554
command += '{}{}\n'.format(self.prompt, line)

cmd2/history.py

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,25 @@
1111
from .parsing import Statement
1212

1313

14-
class HistoryItem(str):
14+
class HistoryItem():
1515
"""Class used to represent one command in the History list"""
16-
listformat = ' {:>4} {}\n'
17-
ex_listformat = ' {:>4}x {}\n'
16+
_listformat = ' {:>4} {}\n'
17+
_ex_listformat = ' {:>4}x {}\n'
1818

19-
def __new__(cls, statement: Statement):
20-
"""Create a new instance of HistoryItem
19+
# def __new__(cls, statement: Statement):
20+
# """Create a new instance of HistoryItem
2121

22-
We must override __new__ because we are subclassing `str` which is
23-
immutable and takes a different number of arguments as Statement.
24-
"""
25-
hi = super().__new__(cls, statement.raw)
26-
hi.statement = statement
27-
hi.idx = None
28-
return hi
22+
# We must override __new__ because we are subclassing `str` which is
23+
# immutable and takes a different number of arguments as Statement.
24+
# """
25+
# hi = super().__new__(cls, statement.raw)
26+
# hi.statement = statement
27+
# hi.idx = None
28+
# return hi
29+
30+
def __init__(self, statement: Statement, idx: int):
31+
self.statement = statement
32+
self.idx = idx
2933

3034
@property
3135
def expanded(self) -> str:
@@ -39,23 +43,24 @@ def pr(self, script=False, expanded=False, verbose=False) -> str:
3943
4044
:return: pretty print string version of a HistoryItem
4145
"""
46+
expanded_command = self.statement.expanded_command_line
4247
if verbose:
43-
ret_str = self.listformat.format(self.idx, str(self).rstrip())
44-
if self != self.expanded:
45-
ret_str += self.ex_listformat.format(self.idx, self.expanded.rstrip())
48+
ret_str = self._listformat.format(self.idx, self.statement.raw)
49+
if self.statement.raw != expanded_command.rstrip():
50+
ret_str += self._ex_listformat.format(self.idx, expanded_command)
4651
else:
4752
if script:
4853
# display without entry numbers
4954
if expanded or self.statement.multiline_command:
50-
ret_str = self.expanded.rstrip()
55+
ret_str = expanded_command.rstrip()
5156
else:
52-
ret_str = str(self)
57+
ret_str = self.statement.raw
5358
else:
5459
# display a numbered list
5560
if expanded or self.statement.multiline_command:
56-
ret_str = self.listformat.format(self.idx, self.expanded.rstrip())
61+
ret_str = self._listformat.format(self.idx, expanded_command.rstrip())
5762
else:
58-
ret_str = self.listformat.format(self.idx, str(self).rstrip())
63+
ret_str = self._listformat.format(self.idx, self.statement.raw.rstrip())
5964
return ret_str
6065

6166

@@ -85,9 +90,8 @@ def append(self, new: Statement) -> None:
8590
8691
:param new: command line to convert to HistoryItem and add to the end of the History list
8792
"""
88-
new = HistoryItem(new)
89-
list.append(self, new)
90-
new.idx = len(self)
93+
history_item = HistoryItem(new, len(self)+1)
94+
list.append(self, history_item)
9195

9296
def get(self, index: Union[int, str]) -> HistoryItem:
9397
"""Get item from the History list using 1-based indexing.
@@ -206,7 +210,7 @@ def str_search(self, search: str) -> List[HistoryItem]:
206210
def isin(history_item):
207211
"""filter function for string search of history"""
208212
sloppy = utils.norm_fold(search)
209-
return sloppy in utils.norm_fold(history_item) or sloppy in utils.norm_fold(history_item.expanded)
213+
return sloppy in utils.norm_fold(history_item.statement.raw) or sloppy in utils.norm_fold(history_item.statement.expanded_command_line)
210214
return [item for item in self if isin(item)]
211215

212216
def regex_search(self, regex: str) -> List[HistoryItem]:
@@ -222,7 +226,7 @@ def regex_search(self, regex: str) -> List[HistoryItem]:
222226

223227
def isin(hi):
224228
"""filter function for doing a regular expression search of history"""
225-
return finder.search(hi) or finder.search(hi.expanded)
229+
return finder.search(hi.statement.raw) or finder.search(hi.statement.expanded_command_line)
226230
return [itm for itm in self if isin(itm)]
227231

228232
def truncate(self, max_length:int) -> None:

tests/test_history.py

Lines changed: 91 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -55,44 +55,93 @@ def test_exclude_from_history(base_app, monkeypatch):
5555
def hist():
5656
from cmd2.parsing import Statement
5757
from cmd2.cmd2 import History, HistoryItem
58-
h = History([HistoryItem(Statement('', raw='first')),
59-
HistoryItem(Statement('', raw='second')),
60-
HistoryItem(Statement('', raw='third')),
61-
HistoryItem(Statement('', raw='fourth'))])
58+
h = History([HistoryItem(Statement('', raw='first'), 1),
59+
HistoryItem(Statement('', raw='second'), 2),
60+
HistoryItem(Statement('', raw='third'), 3),
61+
HistoryItem(Statement('', raw='fourth'),4)])
6262
return h
6363

6464
def test_history_class_span(hist):
6565
for tryit in ['*', ':', '-', 'all', 'ALL']:
6666
assert hist.span(tryit) == hist
6767

68-
assert hist.span('3') == ['third']
69-
assert hist.span('-1') == ['fourth']
70-
71-
assert hist.span('2..') == ['second', 'third', 'fourth']
72-
assert hist.span('2:') == ['second', 'third', 'fourth']
73-
74-
assert hist.span('-2..') == ['third', 'fourth']
75-
assert hist.span('-2:') == ['third', 'fourth']
76-
77-
assert hist.span('1..3') == ['first', 'second', 'third']
78-
assert hist.span('1:3') == ['first', 'second', 'third']
79-
assert hist.span('2:-1') == ['second', 'third', 'fourth']
80-
assert hist.span('-3:4') == ['second', 'third','fourth']
81-
assert hist.span('-4:-2') == ['first', 'second', 'third']
82-
83-
assert hist.span(':-2') == ['first', 'second', 'third']
84-
assert hist.span('..-2') == ['first', 'second', 'third']
68+
assert hist.span('3')[0].statement.raw == 'third'
69+
assert hist.span('-1')[0].statement.raw == 'fourth'
70+
71+
span = hist.span('2..')
72+
assert len(span) == 3
73+
assert span[0].statement.raw == 'second'
74+
assert span[1].statement.raw == 'third'
75+
assert span[2].statement.raw == 'fourth'
76+
77+
span = hist.span('2:')
78+
assert len(span) == 3
79+
assert span[0].statement.raw == 'second'
80+
assert span[1].statement.raw == 'third'
81+
assert span[2].statement.raw == 'fourth'
82+
83+
span = hist.span('-2..')
84+
assert len(span) == 2
85+
assert span[0].statement.raw == 'third'
86+
assert span[1].statement.raw == 'fourth'
87+
88+
span = hist.span('-2:')
89+
assert len(span) == 2
90+
assert span[0].statement.raw == 'third'
91+
assert span[1].statement.raw == 'fourth'
92+
93+
span = hist.span('1..3')
94+
assert len(span) == 3
95+
assert span[0].statement.raw == 'first'
96+
assert span[1].statement.raw == 'second'
97+
assert span[2].statement.raw == 'third'
98+
99+
span = hist.span('1:3')
100+
assert len(span) == 3
101+
assert span[0].statement.raw == 'first'
102+
assert span[1].statement.raw == 'second'
103+
assert span[2].statement.raw == 'third'
104+
105+
span = hist.span('2:-1')
106+
assert len(span) == 3
107+
assert span[0].statement.raw == 'second'
108+
assert span[1].statement.raw == 'third'
109+
assert span[2].statement.raw == 'fourth'
110+
111+
span = hist.span('-3:4')
112+
assert len(span) == 3
113+
assert span[0].statement.raw == 'second'
114+
assert span[1].statement.raw == 'third'
115+
assert span[2].statement.raw == 'fourth'
116+
117+
span = hist.span('-4:-2')
118+
assert len(span) == 3
119+
assert span[0].statement.raw == 'first'
120+
assert span[1].statement.raw == 'second'
121+
assert span[2].statement.raw == 'third'
122+
123+
span = hist.span(':-2')
124+
assert len(span) == 3
125+
assert span[0].statement.raw == 'first'
126+
assert span[1].statement.raw == 'second'
127+
assert span[2].statement.raw == 'third'
128+
129+
span = hist.span('..-2')
130+
assert len(span) == 3
131+
assert span[0].statement.raw == 'first'
132+
assert span[1].statement.raw == 'second'
133+
assert span[2].statement.raw == 'third'
85134

86135
value_errors = ['fred', 'fred:joe', 'a..b', '2 ..', '1 : 3', '1:0', '0:3']
87136
for tryit in value_errors:
88137
with pytest.raises(ValueError):
89138
hist.span(tryit)
90139

91140
def test_history_class_get(hist):
92-
assert hist.get('1') == 'first'
93-
assert hist.get(3) == 'third'
141+
assert hist.get('1').statement.raw == 'first'
142+
assert hist.get(3).statement.raw == 'third'
94143
assert hist.get('-2') == hist[-2]
95-
assert hist.get(-1) == 'fourth'
144+
assert hist.get(-1).statement.raw == 'fourth'
96145

97146
with pytest.raises(IndexError):
98147
hist.get(0)
@@ -115,12 +164,23 @@ def test_history_class_get(hist):
115164
hist.get(None)
116165

117166
def test_history_str_search(hist):
118-
assert hist.str_search('ir') == ['first', 'third']
119-
assert hist.str_search('rth') == ['fourth']
167+
items = hist.str_search('ir')
168+
assert len(items) == 2
169+
assert items[0].statement.raw == 'first'
170+
assert items[1].statement.raw == 'third'
171+
172+
items = hist.str_search('rth')
173+
assert len(items) == 1
174+
assert items[0].statement.raw == 'fourth'
120175

121176
def test_history_regex_search(hist):
122-
assert hist.regex_search('/i.*d/') == ['third']
123-
assert hist.regex_search('s[a-z]+ond') == ['second']
177+
items = hist.regex_search('/i.*d/')
178+
assert len(items) == 1
179+
assert items[0].statement.raw == 'third'
180+
181+
items = hist.regex_search('s[a-z]+ond')
182+
assert len(items) == 1
183+
assert items[0].statement.raw == 'second'
124184

125185
def test_history_max_length_zero(hist):
126186
hist.truncate(0)
@@ -132,8 +192,9 @@ def test_history_max_length_negative(hist):
132192

133193
def test_history_max_length(hist):
134194
hist.truncate(2)
135-
assert hist.get(1) == 'third'
136-
assert hist.get(2) == 'fourth'
195+
assert len(hist) == 2
196+
assert hist.get(1).statement.raw == 'third'
197+
assert hist.get(2).statement.raw == 'fourth'
137198

138199
def test_base_history(base_app):
139200
run_cmd(base_app, 'help')

0 commit comments

Comments
 (0)