55
66import re
77
8- from typing import List , Optional , Union
8+ from typing import List , Union
99
1010from . import utils
1111from .parsing import Statement
@@ -73,26 +73,62 @@ class History(list):
7373 """
7474
7575 # noinspection PyMethodMayBeStatic
76- def _zero_based_index (self , onebased : int ) -> int :
76+ def _zero_based_index (self , onebased : Union [ int , str ] ) -> int :
7777 """Convert a one-based index to a zero-based index."""
78- result = onebased
78+ result = int ( onebased )
7979 if result > 0 :
8080 result -= 1
8181 return result
8282
83- def _to_index (self , raw : str ) -> Optional [int ]:
84- if raw :
85- result = self ._zero_based_index (int (raw ))
86- else :
87- result = None
88- return result
83+ def append (self , new : Statement ) -> None :
84+ """Append a HistoryItem to end of the History list
8985
90- spanpattern = re .compile (r'^\s*(?P<start>-?\d+)?\s*(?P<separator>:|(\.{2,}))?\s*(?P<end>-?\d+)?\s*$' )
86+ :param new: command line to convert to HistoryItem and add to the end of the History list
87+ """
88+ new = HistoryItem (new )
89+ list .append (self , new )
90+ new .idx = len (self )
9191
92- def span (self , raw : str ) -> List [ HistoryItem ] :
93- """Parses the input string and return a slice from the History list.
92+ def get (self , index : Union [ int , str ] ) -> HistoryItem :
93+ """Get item from the History list using 1-based indexing .
9494
95- :param raw: string potentially containing a span
95+ :param index: optional item to get (index as either integer or string)
96+ :return: a single HistoryItem
97+ """
98+ index = int (index )
99+ if index == 0 :
100+ raise IndexError
101+ elif index < 0 :
102+ return self [index ]
103+ else :
104+ return self [index - 1 ]
105+
106+ # This regular expression parses input for the span() method. There are five parts:
107+ #
108+ # ^\s* matches any whitespace at the beginning of the
109+ # input. This is here so you don't have to trim the input
110+ #
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'
114+ #
115+ # (?P<separator>:|(\.{2,}))? create a capture group named 'separator' which matches either
116+ # a colon or two periods. This group is optional so we can
117+ # match a string like '3'
118+ #
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:'
122+ #
123+ # \s*$ match any whitespace at the end of the input. This is here so
124+ # you don't have to trim the input
125+ #
126+ spanpattern = re .compile (r'^\s*(?P<start>-?\d+)?(?P<separator>:|(\.{2,}))?(?P<end>-?\d+)?\s*$' )
127+
128+ def span (self , span : str ) -> List [HistoryItem ]:
129+ """Return an index or slice of the History list,
130+
131+ :param raw: string containing an index or a slice
96132 :return: a list of HistoryItems
97133
98134 This method can accommodate input in any of these forms:
@@ -107,84 +143,71 @@ def span(self, raw: str) -> List[HistoryItem]:
107143
108144 Different from native python indexing and slicing of arrays, this method
109145 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:
146+ 0 based numbering. Programmers can usually grok either. Which reminds me,
147+ there are only two hard problems in programming:
112148
113149 - naming
114150 - cache invalidation
115151 - off by one errors
116152
117153 """
118- if raw .lower () in ('*' , '-' , 'all' ):
119- raw = ':'
120- results = self .spanpattern .search (raw )
154+ if span .lower () in ('*' , '-' , 'all' ):
155+ span = ':'
156+ results = self .spanpattern .search (span )
121157 if not results :
122- raise IndexError
123- if not results .group ('separator' ):
124- return [self [self ._to_index (results .group ('start' ))]]
125- start = self ._to_index (results .group ('start' )) or 0 # Ensure start is not None
126- end = self ._to_index (results .group ('end' ))
127- reverse = False
128- if end is not None :
129- if end < start :
130- (start , end ) = (end , start )
131- reverse = True
132- end += 1
133- result = self [start :end ]
134- if reverse :
135- result .reverse ()
158+ # our regex doesn't match the input, bail out
159+ raise ValueError
160+
161+ sep = results .group ('separator' )
162+ start = results .group ('start' )
163+ if start :
164+ start = self ._zero_based_index (start )
165+ end = results .group ('end' )
166+ if end :
167+ end = int (end )
168+
169+ if start is not None and end is not None :
170+ # we have both start and end, return a slice of history, unless both are negative
171+ if start < 0 and end < 0 :
172+ raise ValueError
173+ result = self [start :end ]
174+ elif start is not None and sep is not None :
175+ # take a slice of the array
176+ result = self [start :]
177+ elif end is not None and sep is not None :
178+ result = self [:end ]
179+ elif start is not None :
180+ # there was no separator so it's either a posative or negative integer
181+ result = [self [start ]]
182+ else :
183+ # we just have a separator, return the whole list
184+ result = self [:]
136185 return result
137186
138- rangePattern = re .compile (r'^\s*(?P<start>[\d]+)?\s*-\s*(?P<end>[\d]+)?\s*$' )
139-
140- def append (self , new : Statement ) -> None :
141- """Append a HistoryItem to end of the History list
142-
143- :param new: command line to convert to HistoryItem and add to the end of the History list
144- """
145- new = HistoryItem (new )
146- list .append (self , new )
147- new .idx = len (self )
148-
149- def get (self , index : Union [int , str ]) -> HistoryItem :
150- """Get item from the History list using 1-based indexing.
187+ def str_search (self , search : str ) -> List [HistoryItem ]:
188+ """Find history items which contain a given string
151189
152- :param index: optional item to get (index as either integer or string)
153- :return: a single HistoryItem
190+ :param search: the string to search for
191+ :return: a list of history items, or an empty list if the string was not found
154192 """
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
193+ def isin (history_item ):
194+ """filter function for string search of history"""
195+ sloppy = utils .norm_fold (search )
196+ return sloppy in utils .norm_fold (history_item ) or sloppy in utils .norm_fold (history_item .expanded )
197+ return [item for item in self if isin (item )]
167198
168199 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 )
200+ """Find history items which match a given regular expression
173201
174- def isin (hi ):
175- """Listcomp filter function for doing a regular expression search of History.
176-
177- :param hi: HistoryItem
178- :return: bool - True if search matches
179- """
180- return finder .search (hi ) or finder .search (hi .expanded )
181- else :
182- def isin (hi ):
183- """Listcomp filter function for doing a case-insensitive string search of History.
184-
185- :param hi: HistoryItem
186- :return: bool - True if search matches
187- """
188- srch = utils .norm_fold (regex )
189- return srch in utils .norm_fold (hi ) or srch in utils .norm_fold (hi .expanded )
190- return [itm for itm in self if isin (itm )]
202+ :param regex: the regular expression to search for.
203+ :return: a list of history items, or an empty list if the string was not found
204+ """
205+ regex = regex .strip ()
206+ if regex .startswith (r'/' ) and regex .endswith (r'/' ):
207+ regex = regex [1 :- 1 ]
208+ finder = re .compile (regex , re .DOTALL | re .MULTILINE )
209+
210+ def isin (hi ):
211+ """filter function for doing a regular expression search of history"""
212+ return finder .search (hi ) or finder .search (hi .expanded )
213+ return [itm for itm in self if isin (itm )]
0 commit comments