summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd2/cmd2.py18
-rw-r--r--cmd2/history.py27
-rwxr-xr-xexamples/cmd_as_argument.py3
-rw-r--r--tests/test_cmd2.py14
-rw-r--r--tests/test_history.py31
-rw-r--r--tests/test_parsing.py12
6 files changed, 85 insertions, 20 deletions
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index 02462d96..8f5a7abe 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -1913,13 +1913,17 @@ class Cmd(cmd.Cmd):
:return: parsed command line as a Statement
"""
used_macros = []
- orig_line = line
+ orig_line = None
# Continue until all macros are resolved
while True:
# Make sure all input has been read and convert it to a Statement
statement = self._complete_statement(line)
+ # Save the fully entered line if this is the first loop iteration
+ if orig_line is None:
+ orig_line = statement.raw
+
# Check if this command matches a macro and wasn't already processed to avoid an infinite loop
if statement.command in self.macros.keys() and statement.command not in used_macros:
used_macros.append(statement.command)
@@ -3483,11 +3487,13 @@ class Cmd(cmd.Cmd):
if rl_type != RlType.NONE:
last = None
for item in history:
- # readline only adds a single entry for multiple sequential identical commands
- # so we emulate that behavior here
- if item.raw != last:
- readline.add_history(item.raw)
- last = item.raw
+ # Break the command into its individual lines
+ for line in item.raw.splitlines():
+ # readline only adds a single entry for multiple sequential identical lines
+ # so we emulate that behavior here
+ if line != last:
+ readline.add_history(line)
+ last = line
# register a function to write history at save
# if the history file is in plain text format from 0.9.12 or lower
diff --git a/cmd2/history.py b/cmd2/history.py
index bbeb9199..0f47d542 100644
--- a/cmd2/history.py
+++ b/cmd2/history.py
@@ -44,22 +44,23 @@ class HistoryItem():
:return: pretty print string version of a HistoryItem
"""
if verbose:
- ret_str = self._listformat.format(self.idx, self.raw)
+ ret_str = self._listformat.format(self.idx, self.raw.rstrip())
if self.raw != self.expanded.rstrip():
- ret_str += self._ex_listformat.format(self.idx, self.expanded)
+ ret_str += self._ex_listformat.format(self.idx, self.expanded.rstrip())
else:
- if script:
- # display without entry numbers
- if expanded or self.statement.multiline_command:
- ret_str = self.expanded.rstrip()
- else:
- ret_str = self.raw.rstrip()
+ if expanded:
+ ret_str = self.expanded.rstrip()
else:
- # display a numbered list
- if expanded or self.statement.multiline_command:
- ret_str = self._listformat.format(self.idx, self.expanded.rstrip())
- else:
- ret_str = self._listformat.format(self.idx, self.raw.rstrip())
+ ret_str = self.raw.rstrip()
+
+ # In non-verbose mode, display raw multiline commands on 1 line
+ if self.statement.multiline_command:
+ ret_str = ret_str.replace('\n', ' ')
+
+ # Display a numbered list if not writing to a script
+ if not script:
+ ret_str = self._listformat.format(self.idx, ret_str)
+
return ret_str
diff --git a/examples/cmd_as_argument.py b/examples/cmd_as_argument.py
index 538feac1..1e7901b9 100755
--- a/examples/cmd_as_argument.py
+++ b/examples/cmd_as_argument.py
@@ -31,8 +31,9 @@ class CmdLineApp(cmd2.Cmd):
shortcuts = dict(self.DEFAULT_SHORTCUTS)
shortcuts.update({'&': 'speak'})
# Set use_ipython to True to enable the "ipy" command which embeds and interactive IPython shell
- super().__init__(allow_cli_args=False, use_ipython=False, multiline_commands=['orate'], shortcuts=shortcuts)
+ super().__init__(allow_cli_args=False, use_ipython=True, multiline_commands=['orate'], shortcuts=shortcuts)
+ self.locals_in_py = True
self.maxrepeats = 3
# Make maxrepeats settable at runtime
self.settable['maxrepeats'] = 'max repetitions for speak command'
diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py
index 0dc8c7c2..72643308 100644
--- a/tests/test_cmd2.py
+++ b/tests/test_cmd2.py
@@ -1317,6 +1317,20 @@ def test_multiline_complete_statement_with_unclosed_quotes(multiline_app):
assert statement.multiline_command == 'orate'
assert statement.terminator == ';'
+def test_multiline_input_line_to_statement(multiline_app):
+ # Verify _input_line_to_statement saves the fully entered input line for multiline commands
+
+ # Mock out the input call so we don't actually wait for a user's response
+ # on stdin when it looks for more input
+ m = mock.MagicMock(name='input', side_effect=['person', '\n'])
+ builtins.input = m
+
+ line = 'orate hi'
+ statement = multiline_app._input_line_to_statement(line)
+ assert statement.raw == 'orate hi\nperson\n'
+ assert statement == 'hi person'
+ assert statement.command == 'orate'
+ assert statement.multiline_command == 'orate'
def test_clipboard_failure(base_app, capsys):
# Force cmd2 clipboard to be disabled
diff --git a/tests/test_history.py b/tests/test_history.py
index 5e01688c..ce915d1a 100644
--- a/tests/test_history.py
+++ b/tests/test_history.py
@@ -10,6 +10,8 @@ import pytest
# Python 3.5 had some regressions in the unitest.mock module, so use
# 3rd party mock if available
+from cmd2.parsing import StatementParser
+
try:
import mock
except ImportError:
@@ -262,6 +264,35 @@ def histitem():
histitem = HistoryItem(statement, 1)
return histitem
+@pytest.fixture
+def parser():
+ from cmd2.parsing import StatementParser
+ parser = StatementParser(
+ allow_redirection=True,
+ terminators=[';', '&'],
+ multiline_commands=['multiline'],
+ aliases={'helpalias': 'help',
+ '42': 'theanswer',
+ 'l': '!ls -al',
+ 'anothermultiline': 'multiline',
+ 'fake': 'pyscript'},
+ shortcuts=[('?', 'help'), ('!', 'shell')]
+ )
+ return parser
+
+def test_multiline_histitem(parser):
+ from cmd2.history import History
+ line = 'multiline foo\nbar\n\n'
+ statement = parser.parse(line)
+ history = History()
+ history.append(statement)
+ assert len(history) == 1
+ hist_item = history[0]
+ assert hist_item.raw == line
+ pr_lines = hist_item.pr(verbose=True).splitlines()
+ assert pr_lines[0].endswith('multiline foo')
+ assert pr_lines[1] == 'bar'
+
def test_history_item_instantiate():
from cmd2.parsing import Statement
from cmd2.history import HistoryItem
diff --git a/tests/test_parsing.py b/tests/test_parsing.py
index 5ba02a95..3bd635a1 100644
--- a/tests/test_parsing.py
+++ b/tests/test_parsing.py
@@ -486,6 +486,18 @@ def test_parse_unfinished_multiliine_command(parser):
assert statement.arg_list == statement.argv[1:]
assert statement.terminator == ''
+def test_parse_basic_multiline_command(parser):
+ line = 'multiline foo\nbar\n\n'
+ statement = parser.parse(line)
+ assert statement.multiline_command == 'multiline'
+ assert statement.command == 'multiline'
+ assert statement == 'foo bar'
+ assert statement.args == statement
+ assert statement.argv == ['multiline', 'foo', 'bar']
+ assert statement.arg_list == ['foo', 'bar']
+ assert statement.raw == line
+ assert statement.terminator == '\n'
+
@pytest.mark.parametrize('line,terminator',[
('multiline has > inside;', ';'),
('multiline has > inside;;;', ';'),