summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md6
-rw-r--r--cmd2/cmd2.py106
-rw-r--r--docs/features/scripting.rst28
-rwxr-xr-xtests/test_cmd2.py44
4 files changed, 134 insertions, 50 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d78bf0ea..780c9ad5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,12 @@
an argument to disable tab completion while input is being entered.
* Added capability to override the argument parser class used by cmd2 built-in commands. See override_parser.py
example for more details.
+ * Added `end` argument to `pfeedback()` to be consistent with the other print functions like `poutput()`.
+ * Added `apply_style` to `pwarning()`.
+* Breaking changes
+ * For consistency between all the print functions:
+ * Made `end` and `chop` keyword-only arguments of `ppaged()`
+ * `end` is always added to message in `ppaged()`
## 0.9.20 (November 12, 2019)
* Bug Fixes
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index bd9dd4ff..1eeb4212 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -467,13 +467,17 @@ class Cmd(cmd.Cmd):
final_msg = "{}".format(msg)
ansi.ansi_aware_write(sys.stderr, final_msg + end)
- def pwarning(self, msg: Any, *, end: str = '\n') -> None:
- """Apply the warning style to a message and print it to sys.stderr
+ def pwarning(self, msg: Any, *, end: str = '\n', apply_style: bool = True) -> None:
+ """Like perror, but applies ansi.style_warning by default
:param msg: message to print (anything convertible to a str with '{}'.format() is OK)
:param end: string appended after the end of the message, default a newline
+ :param apply_style: If True, then ansi.style_warning will be applied to the message text. Set to False in cases
+ where the message text already has the desired style. Defaults to True.
"""
- self.perror(ansi.style_warning(msg), end=end, apply_style=False)
+ if apply_style:
+ msg = ansi.style_warning(msg)
+ self.perror(msg, end=end, apply_style=False)
def pexcept(self, msg: Any, *, end: str = '\n', apply_style: bool = True) -> None:
"""Print Exception message to sys.stderr. If debug is true, print exception traceback if one exists.
@@ -499,26 +503,28 @@ class Cmd(cmd.Cmd):
warning = "\nTo enable full traceback, run the following command: 'set debug true'"
final_msg += ansi.style_warning(warning)
- # Set apply_style to False since style has already been applied
self.perror(final_msg, end=end, apply_style=False)
- def pfeedback(self, msg: str) -> None:
+ def pfeedback(self, msg: Any, *, end: str = '\n') -> None:
"""For printing nonessential feedback. Can be silenced with `quiet`.
- Inclusion in redirected output is controlled by `feedback_to_output`."""
+ Inclusion in redirected output is controlled by `feedback_to_output`.
+ :param msg: message to print (anything convertible to a str with '{}'.format() is OK)
+ :param end: string appended after the end of the message, default a newline
+ """
if not self.quiet:
if self.feedback_to_output:
- self.poutput(msg)
+ self.poutput(msg, end=end)
else:
- ansi.ansi_aware_write(sys.stderr, "{}\n".format(msg))
+ self.perror(msg, end=end, apply_style=False)
- def ppaged(self, msg: str, end: str = '\n', chop: bool = False) -> None:
+ def ppaged(self, msg: Any, *, end: str = '\n', chop: bool = False) -> None:
"""Print output using a pager if it would go off screen and stdout isn't currently being redirected.
Never uses a pager inside of a script (Python or text) or when output is being redirected or piped or when
stdout or stdin are not a fully functional terminal.
:param msg: message to print to current stdout (anything convertible to a str with '{}'.format() is OK)
- :param end: string appended after the end of the message if not already present, default a newline
+ :param end: string appended after the end of the message, default a newline
:param chop: True -> causes lines longer than the screen width to be chopped (truncated) rather than wrapped
- truncated text is still accessible by scrolling with the right & left arrow keys
- chopping is ideal for displaying wide tabular data as is done in utilities like pgcli
@@ -527,44 +533,48 @@ class Cmd(cmd.Cmd):
WARNING: On Windows, the text always wraps regardless of what the chop argument is set to
"""
- import subprocess
- if msg is not None and msg != '':
- try:
- msg_str = '{}'.format(msg)
- if not msg_str.endswith(end):
- msg_str += end
-
- # Attempt to detect if we are not running within a fully functional terminal.
- # Don't try to use the pager when being run by a continuous integration system like Jenkins + pexpect.
- functional_terminal = False
-
- if self.stdin.isatty() and self.stdout.isatty():
- if sys.platform.startswith('win') or os.environ.get('TERM') is not None:
- functional_terminal = True
-
- # Don't attempt to use a pager that can block if redirecting or running a script (either text or Python)
- # Also only attempt to use a pager if actually running in a real fully functional terminal
- if functional_terminal and not self._redirecting and not self.in_pyscript() and not self.in_script():
- if ansi.allow_ansi.lower() == ansi.ANSI_NEVER.lower():
- msg_str = ansi.strip_ansi(msg_str)
-
- pager = self.pager
- if chop:
- pager = self.pager_chop
-
- # Prevent KeyboardInterrupts while in the pager. The pager application will
- # still receive the SIGINT since it is in the same process group as us.
- with self.sigint_protection:
- pipe_proc = subprocess.Popen(pager, shell=True, stdin=subprocess.PIPE)
- pipe_proc.communicate(msg_str.encode('utf-8', 'replace'))
- else:
- self.poutput(msg_str, end='')
- except BrokenPipeError:
- # This occurs if a command's output is being piped to another process and that process closes before the
- # command is finished. If you would like your application to print a warning message, then set the
- # broken_pipe_warning attribute to the message you want printed.`
- if self.broken_pipe_warning:
- sys.stderr.write(self.broken_pipe_warning)
+ # msg can be any type, so convert to string before checking if it's blank
+ msg_str = str(msg)
+
+ # Consider None to be no data to print
+ if msg is None or msg_str == '':
+ return
+
+ try:
+ import subprocess
+
+ # Attempt to detect if we are not running within a fully functional terminal.
+ # Don't try to use the pager when being run by a continuous integration system like Jenkins + pexpect.
+ functional_terminal = False
+
+ if self.stdin.isatty() and self.stdout.isatty():
+ if sys.platform.startswith('win') or os.environ.get('TERM') is not None:
+ functional_terminal = True
+
+ # Don't attempt to use a pager that can block if redirecting or running a script (either text or Python)
+ # Also only attempt to use a pager if actually running in a real fully functional terminal
+ if functional_terminal and not self._redirecting and not self.in_pyscript() and not self.in_script():
+ if ansi.allow_ansi.lower() == ansi.ANSI_NEVER.lower():
+ msg_str = ansi.strip_ansi(msg_str)
+ msg_str += end
+
+ pager = self.pager
+ if chop:
+ pager = self.pager_chop
+
+ # Prevent KeyboardInterrupts while in the pager. The pager application will
+ # still receive the SIGINT since it is in the same process group as us.
+ with self.sigint_protection:
+ pipe_proc = subprocess.Popen(pager, shell=True, stdin=subprocess.PIPE)
+ pipe_proc.communicate(msg_str.encode('utf-8', 'replace'))
+ else:
+ self.poutput(msg_str, end=end)
+ except BrokenPipeError:
+ # This occurs if a command's output is being piped to another process and that process closes before the
+ # command is finished. If you would like your application to print a warning message, then set the
+ # broken_pipe_warning attribute to the message you want printed.`
+ if self.broken_pipe_warning:
+ sys.stderr.write(self.broken_pipe_warning)
# ----- Methods related to tab completion -----
diff --git a/docs/features/scripting.rst b/docs/features/scripting.rst
index b41855ca..62af2e6d 100644
--- a/docs/features/scripting.rst
+++ b/docs/features/scripting.rst
@@ -59,7 +59,31 @@ session.
Python Scripts
--------------
+.. _arg_printer:
+ https://github.com/python-cmd2/cmd2/blob/master/examples/scripts/arg_printer.py
+
If you require logic flow, loops, branching, or other advanced features, you
can write a python script which executes in the context of your ``cmd2`` app.
-This script is run using the ``run_pyscript`` command. See
-:ref:`features/embedded_python_shells:Embedded Python Shells`.
+This script is run using the ``run_pyscript`` command. A simple example of
+using ``run_pyscript`` is shown below along with the arg_printer_ script::
+
+ (Cmd) run_pyscript examples/scripts/arg_printer.py foo bar 'baz 23'
+ Running Python script 'arg_printer.py' which was called with 3 arguments
+ arg 1: 'foo'
+ arg 2: 'bar'
+ arg 3: 'baz 23'
+
+``run_pyscript`` supports tab-completion of file system paths, and as shown
+above it has the ability to pass command-line arguments to the scripts invoked.
+
+Python scripts executed with ``run_pyscript`` can run ``cmd2`` application
+commands by using the syntax::
+
+ app(‘command args’)
+
+where:
+
+* ``app`` is a configurable name which can be changed by setting the
+ ``py_bridge_name`` attribute of your ``cmd2.Cmd`` class instance
+* ``command`` and ``args`` are entered exactly like they would be entered on
+ the command line of your ``cmd2`` application
diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py
index f9c3e61d..88447416 100755
--- a/tests/test_cmd2.py
+++ b/tests/test_cmd2.py
@@ -1882,6 +1882,38 @@ def test_nonexistent_macro(base_app):
assert exception is not None
+def test_perror_style(base_app, capsys):
+ msg = 'testing...'
+ end = '\n'
+ ansi.allow_ansi = ansi.ANSI_ALWAYS
+ base_app.perror(msg)
+ out, err = capsys.readouterr()
+ assert err == ansi.style_error(msg) + end
+
+def test_perror_no_style(base_app, capsys):
+ msg = 'testing...'
+ end = '\n'
+ ansi.allow_ansi = ansi.ANSI_ALWAYS
+ base_app.perror(msg, apply_style=False)
+ out, err = capsys.readouterr()
+ assert err == msg + end
+
+def test_pwarning_style(base_app, capsys):
+ msg = 'testing...'
+ end = '\n'
+ ansi.allow_ansi = ansi.ANSI_ALWAYS
+ base_app.pwarning(msg)
+ out, err = capsys.readouterr()
+ assert err == ansi.style_warning(msg) + end
+
+def test_pwarning_no_style(base_app, capsys):
+ msg = 'testing...'
+ end = '\n'
+ ansi.allow_ansi = ansi.ANSI_ALWAYS
+ base_app.pwarning(msg, apply_style=False)
+ out, err = capsys.readouterr()
+ assert err == msg + end
+
def test_ppaged(outsim_app):
msg = 'testing...'
end = '\n'
@@ -1889,6 +1921,18 @@ def test_ppaged(outsim_app):
out = outsim_app.stdout.getvalue()
assert out == msg + end
+def test_ppaged_blank(outsim_app):
+ msg = ''
+ outsim_app.ppaged(msg)
+ out = outsim_app.stdout.getvalue()
+ assert not out
+
+def test_ppaged_none(outsim_app):
+ msg = None
+ outsim_app.ppaged(msg)
+ out = outsim_app.stdout.getvalue()
+ assert not out
+
def test_ppaged_strips_ansi_when_redirecting(outsim_app):
msg = 'testing...'
end = '\n'