summaryrefslogtreecommitdiff
path: root/cmd2/history.py
diff options
context:
space:
mode:
authorkotfu <kotfu@kotfu.net>2019-03-09 23:19:16 -0700
committerkotfu <kotfu@kotfu.net>2019-03-09 23:19:16 -0700
commitdfe5864fb2ca9334bb1b6e729ec31f5f5890f1cb (patch)
treee9a07f64e94f7e7638af61cab03e1ac3075acb77 /cmd2/history.py
parentd8ef258758be7ca690c591a762cbed7ee4c5a838 (diff)
downloadcmd2-git-dfe5864fb2ca9334bb1b6e729ec31f5f5890f1cb.tar.gz
Clean up history command
Diffstat (limited to 'cmd2/history.py')
-rw-r--r--cmd2/history.py183
1 files changed, 103 insertions, 80 deletions
diff --git a/cmd2/history.py b/cmd2/history.py
index 77b1da51..819989b1 100644
--- a/cmd2/history.py
+++ b/cmd2/history.py
@@ -5,7 +5,7 @@ History management classes
import re
-from typing import List, Optional, Union
+from typing import List, Union
from . import utils
from .parsing import Statement
@@ -73,26 +73,62 @@ class History(list):
"""
# noinspection PyMethodMayBeStatic
- def _zero_based_index(self, onebased: int) -> int:
+ def _zero_based_index(self, onebased: Union[int, str]) -> int:
"""Convert a one-based index to a zero-based index."""
- result = onebased
+ result = int(onebased)
if result > 0:
result -= 1
return result
- def _to_index(self, raw: str) -> Optional[int]:
- if raw:
- result = self._zero_based_index(int(raw))
- else:
- result = None
- return result
+ def append(self, new: Statement) -> None:
+ """Append a HistoryItem to end of the History list
- spanpattern = re.compile(r'^\s*(?P<start>-?\d+)?\s*(?P<separator>:|(\.{2,}))?\s*(?P<end>-?\d+)?\s*$')
+ :param new: command line to convert to HistoryItem and add to the end of the History list
+ """
+ new = HistoryItem(new)
+ list.append(self, new)
+ new.idx = len(self)
- def span(self, raw: str) -> List[HistoryItem]:
- """Parses the input string and return a slice from the History list.
+ def get(self, index: Union[int, str]) -> HistoryItem:
+ """Get item from the History list using 1-based indexing.
- :param raw: string potentially containing a span
+ :param index: optional item to get (index as either integer or string)
+ :return: a single HistoryItem
+ """
+ index = int(index)
+ if index == 0:
+ raise IndexError
+ elif index < 0:
+ return self[index]
+ else:
+ return self[index - 1]
+
+ # This regular expression parses input for the span() method. There are five parts:
+ #
+ # ^\s* matches any whitespace at the beginning of the
+ # input. This is here so you don't have to trim the input
+ #
+ # (?P<start>-?\d+)? create a capture group named 'start' which matches one
+ # or more digits, optionally preceeded by a minus sign. This
+ # group is optional so that we can match a string like '..2'
+ #
+ # (?P<separator>:|(\.{2,}))? create a capture group named 'separator' which matches either
+ # a colon or two periods. This group is optional so we can
+ # match a string like '3'
+ #
+ # (?P<end>-?\d+)? create a capture group named 'end' which matches one or more
+ # digits, optionally preceeded by a minus sign. This group is
+ # optional so that we can match a string like ':' or '5:'
+ #
+ # \s*$ match any whitespace at the end of the input. This is here so
+ # you don't have to trim the input
+ #
+ spanpattern = re.compile(r'^\s*(?P<start>-?\d+)?(?P<separator>:|(\.{2,}))?(?P<end>-?\d+)?\s*$')
+
+ def span(self, span: str) -> List[HistoryItem]:
+ """Return an index or slice of the History list,
+
+ :param raw: string containing an index or a slice
:return: a list of HistoryItems
This method can accommodate input in any of these forms:
@@ -107,84 +143,71 @@ class History(list):
Different from native python indexing and slicing of arrays, this method
uses 1-based array numbering. Users who are not programmers can't grok
- 0 based numbering. Programmers can grok either. Which reminds me, there
- are only two hard problems in programming:
+ 0 based numbering. Programmers can usually grok either. Which reminds me,
+ there are only two hard problems in programming:
- naming
- cache invalidation
- off by one errors
"""
- if raw.lower() in ('*', '-', 'all'):
- raw = ':'
- results = self.spanpattern.search(raw)
+ if span.lower() in ('*', '-', 'all'):
+ span = ':'
+ results = self.spanpattern.search(span)
if not results:
- raise IndexError
- if not results.group('separator'):
- return [self[self._to_index(results.group('start'))]]
- start = self._to_index(results.group('start')) or 0 # Ensure start is not None
- end = self._to_index(results.group('end'))
- reverse = False
- if end is not None:
- if end < start:
- (start, end) = (end, start)
- reverse = True
- end += 1
- result = self[start:end]
- if reverse:
- result.reverse()
+ # our regex doesn't match the input, bail out
+ raise ValueError
+
+ sep = results.group('separator')
+ start = results.group('start')
+ if start:
+ start = self._zero_based_index(start)
+ end = results.group('end')
+ if end:
+ end = int(end)
+
+ if start is not None and end is not None:
+ # we have both start and end, return a slice of history, unless both are negative
+ if start < 0 and end < 0:
+ raise ValueError
+ result = self[start:end]
+ elif start is not None and sep is not None:
+ # take a slice of the array
+ result = self[start:]
+ elif end is not None and sep is not None:
+ result = self[:end]
+ elif start is not None:
+ # there was no separator so it's either a posative or negative integer
+ result = [self[start]]
+ else:
+ # we just have a separator, return the whole list
+ result = self[:]
return result
- rangePattern = re.compile(r'^\s*(?P<start>[\d]+)?\s*-\s*(?P<end>[\d]+)?\s*$')
-
- def append(self, new: Statement) -> None:
- """Append a HistoryItem to end of the History list
-
- :param new: command line to convert to HistoryItem and add to the end of the History list
- """
- new = HistoryItem(new)
- list.append(self, new)
- new.idx = len(self)
-
- def get(self, index: Union[int, str]) -> HistoryItem:
- """Get item from the History list using 1-based indexing.
+ def str_search(self, search: str) -> List[HistoryItem]:
+ """Find history items which contain a given string
- :param index: optional item to get (index as either integer or string)
- :return: a single HistoryItem
+ :param search: the string to search for
+ :return: a list of history items, or an empty list if the string was not found
"""
- index = int(index)
- if index == 0:
- raise IndexError
- elif index < 0:
- return self[index]
- else:
- return self[index - 1]
-
-
-
- def str_search(self, search: str) -> List[HistoryItem]:
- pass
+ def isin(history_item):
+ """filter function for string search of history"""
+ sloppy = utils.norm_fold(search)
+ return sloppy in utils.norm_fold(history_item) or sloppy in utils.norm_fold(history_item.expanded)
+ return [item for item in self if isin(item)]
def regex_search(self, regex: str) -> List[HistoryItem]:
- regex = regex.strip()
-
- if regex.startswith(r'/') and regex.endswith(r'/'):
- finder = re.compile(regex[1:-1], re.DOTALL | re.MULTILINE | re.IGNORECASE)
+ """Find history items which match a given regular expression
- def isin(hi):
- """Listcomp filter function for doing a regular expression search of History.
-
- :param hi: HistoryItem
- :return: bool - True if search matches
- """
- return finder.search(hi) or finder.search(hi.expanded)
- else:
- def isin(hi):
- """Listcomp filter function for doing a case-insensitive string search of History.
-
- :param hi: HistoryItem
- :return: bool - True if search matches
- """
- srch = utils.norm_fold(regex)
- return srch in utils.norm_fold(hi) or srch in utils.norm_fold(hi.expanded)
- return [itm for itm in self if isin(itm)]
+ :param regex: the regular expression to search for.
+ :return: a list of history items, or an empty list if the string was not found
+ """
+ regex = regex.strip()
+ if regex.startswith(r'/') and regex.endswith(r'/'):
+ regex = regex[1:-1]
+ finder = re.compile(regex, re.DOTALL | re.MULTILINE)
+
+ def isin(hi):
+ """filter function for doing a regular expression search of history"""
+ return finder.search(hi) or finder.search(hi.expanded)
+ return [itm for itm in self if isin(itm)]