summaryrefslogtreecommitdiff
path: root/cmd2/cmd2.py
diff options
context:
space:
mode:
Diffstat (limited to 'cmd2/cmd2.py')
-rw-r--r--cmd2/cmd2.py183
1 files changed, 36 insertions, 147 deletions
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index 24e140fd..c404ee1d 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -49,6 +49,7 @@ from . import utils
from .argparse_completer import AutoCompleter, ACArgumentParser, ACTION_ARG_CHOICES
from .clipboard import can_clip, get_paste_buffer, write_to_paste_buffer
from .parsing import StatementParser, Statement, Macro, MacroArg
+from .history import History, HistoryItem
# Set up readline
from .rl_utils import rl_type, RlType, rl_get_point, rl_set_prompt, vt100_support, rl_make_safe_prompt
@@ -290,30 +291,6 @@ class EmptyStatement(Exception):
pass
-class HistoryItem(str):
- """Class used to represent an item in the History list.
-
- Thin wrapper around str class which adds a custom format for printing. It
- also keeps track of its index in the list as well as a lowercase
- representation of itself for convenience/efficiency.
-
- """
- listformat = '-------------------------[{}]\n{}\n'
-
- # noinspection PyUnusedLocal
- def __init__(self, instr: str) -> None:
- str.__init__(self)
- self.lowercase = self.lower()
- self.idx = None
-
- def pr(self) -> str:
- """Represent a HistoryItem in a pretty fashion suitable for printing.
-
- :return: pretty print string version of a HistoryItem
- """
- return self.listformat.format(self.idx, str(self).rstrip())
-
-
class Cmd(cmd.Cmd):
"""An easy but powerful framework for writing line-oriented command interpreters.
@@ -2007,7 +1984,7 @@ class Cmd(cmd.Cmd):
if func:
# Since we have a valid command store it in the history
if statement.command not in self.exclude_from_history:
- self.history.append(statement.raw)
+ self.history.append(statement)
stop = func(statement)
@@ -2070,7 +2047,7 @@ class Cmd(cmd.Cmd):
"""
if self.default_to_shell:
if 'shell' not in self.exclude_from_history:
- self.history.append(statement.raw)
+ self.history.append(statement)
return self.do_shell(statement.command_and_args)
else:
@@ -2283,7 +2260,7 @@ class Cmd(cmd.Cmd):
" would for the actual command the alias resolves to.\n"
"\n"
"Examples:\n"
- " alias ls !ls -lF\n"
+ " alias create ls !ls -lF\n"
" alias create show_log !cat \"log file.txt\"\n"
" alias create save_results print_results \">\" out.txt\n")
@@ -3188,18 +3165,27 @@ class Cmd(cmd.Cmd):
load_ipy(bridge)
history_parser = ACArgumentParser()
- history_parser_group = history_parser.add_mutually_exclusive_group()
- history_parser_group.add_argument('-r', '--run', action='store_true', help='run selected history items')
- history_parser_group.add_argument('-e', '--edit', action='store_true',
+ history_action_group = history_parser.add_mutually_exclusive_group()
+ history_action_group.add_argument('-r', '--run', action='store_true', help='run selected history items')
+ history_action_group.add_argument('-e', '--edit', action='store_true',
help='edit and then run selected history items')
- history_parser_group.add_argument('-s', '--script', action='store_true', help='output commands in script format')
- setattr(history_parser_group.add_argument('-o', '--output-file', metavar='FILE',
- help='output commands to a script file'),
+ setattr(history_action_group.add_argument('-o', '--output-file', metavar='FILE',
+ help='output commands to a script file, implies -s'),
ACTION_ARG_CHOICES, ('path_complete',))
- setattr(history_parser_group.add_argument('-t', '--transcript',
- help='output commands and results to a transcript file'),
+ setattr(history_action_group.add_argument('-t', '--transcript',
+ help='output commands and results to a transcript file, implies -s'),
ACTION_ARG_CHOICES, ('path_complete',))
- history_parser_group.add_argument('-c', '--clear', action="store_true", help='clear all history')
+ history_action_group.add_argument('-c', '--clear', action='store_true', help='clear all history')
+
+ history_format_group = history_parser.add_argument_group(title='formatting')
+ history_script_help = 'output commands in script format, i.e. without command numbers'
+ history_format_group.add_argument('-s', '--script', action='store_true', help=history_script_help)
+ history_expand_help = 'output expanded commands instead of entered command'
+ history_format_group.add_argument('-x', '--expanded', action='store_true', help=history_expand_help)
+ history_format_group.add_argument('-v', '--verbose', action='store_true',
+ help='display history and include expanded commands if they'
+ ' differ from the typed command')
+
history_arg_help = ("empty all history items\n"
"a one history item by number\n"
"a..b, a:b, a:, ..b items by indices (inclusive)\n"
@@ -3211,6 +3197,19 @@ class Cmd(cmd.Cmd):
def do_history(self, args: argparse.Namespace) -> None:
"""View, run, edit, save, or clear previously entered commands"""
+ # -v must be used alone with no other options
+ if args.verbose:
+ if args.clear or args.edit or args.output_file or args.run or args.transcript or args.expanded or args.script:
+ self.poutput("-v can not be used with any other options")
+ self.poutput(self.history_parser.format_usage())
+ return
+
+ # -s and -x can only be used if none of these options are present: [-c -r -e -o -t]
+ if (args.script or args.expanded) and (args.clear or args.edit or args.output_file or args.run or args.transcript):
+ self.poutput("-s and -x can not be used with -c, -r, -e, -o, or -t")
+ self.poutput(self.history_parser.format_usage())
+ return
+
if args.clear:
# Clear command and readline history
self.history.clear()
@@ -3279,10 +3278,7 @@ class Cmd(cmd.Cmd):
else:
# Display the history items retrieved
for hi in history:
- if args.script:
- self.poutput(hi)
- else:
- self.poutput(hi.pr())
+ self.poutput(hi.pr(script=args.script, expanded=args.expanded, verbose=args.verbose))
def _generate_transcript(self, history: List[HistoryItem], transcript_file: str) -> None:
"""Generate a transcript file from a given history of commands."""
@@ -3807,113 +3803,6 @@ class Cmd(cmd.Cmd):
self._cmdfinalization_hooks.append(func)
-class History(list):
- """ A list of HistoryItems that knows how to respond to user requests. """
-
- # noinspection PyMethodMayBeStatic
- def _zero_based_index(self, onebased: int) -> int:
- """Convert a one-based index to a zero-based index."""
- result = 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
-
- spanpattern = re.compile(r'^\s*(?P<start>-?\d+)?\s*(?P<separator>:|(\.{2,}))?\s*(?P<end>-?\d+)?\s*$')
-
- def span(self, raw: str) -> List[HistoryItem]:
- """Parses the input string search for a span pattern and if if found, returns a slice from the History list.
-
- :param raw: string potentially containing a span of the forms a..b, a:b, a:, ..b
- :return: slice from the History list
- """
- if raw.lower() in ('*', '-', 'all'):
- raw = ':'
- results = self.spanpattern.search(raw)
- 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()
- return result
-
- rangePattern = re.compile(r'^\s*(?P<start>[\d]+)?\s*-\s*(?P<end>[\d]+)?\s*$')
-
- def append(self, new: str) -> 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, getme: Optional[Union[int, str]] = None) -> List[HistoryItem]:
- """Get an item or items from the History list using 1-based indexing.
-
- :param getme: optional item(s) to get (either an integer index or string to search for)
- :return: list of HistoryItems matching the retrieval criteria
- """
- if not getme:
- return self
- try:
- getme = int(getme)
- if getme < 0:
- return self[:(-1 * getme)]
- else:
- return [self[getme - 1]]
- except IndexError:
- return []
- except ValueError:
- range_result = self.rangePattern.search(getme)
- if range_result:
- start = range_result.group('start') or None
- end = range_result.group('start') or None
- if start:
- start = int(start) - 1
- if end:
- end = int(end)
- return self[start:end]
-
- getme = getme.strip()
-
- if getme.startswith(r'/') and getme.endswith(r'/'):
- finder = re.compile(getme[1:-1], re.DOTALL | re.MULTILINE | re.IGNORECASE)
-
- 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)
- 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
- """
- return utils.norm_fold(getme) in utils.norm_fold(hi)
- return [itm for itm in self if isin(itm)]
-
-
class Statekeeper(object):
"""Class used to save and restore state during load and py commands as well as when redirecting output or pipes."""
def __init__(self, obj: Any, attribs: Iterable) -> None: