diff options
author | kotfu <kotfu@kotfu.net> | 2019-11-23 17:10:19 -0700 |
---|---|---|
committer | kotfu <kotfu@kotfu.net> | 2019-11-23 17:10:19 -0700 |
commit | f58772040109e62b64c4781d0e8411fdc2f56ffb (patch) | |
tree | df73925b01be1c72290be980831fecb8d082a165 | |
parent | 3470018d919f6295ada022ce5078e015d6bbd287 (diff) | |
parent | aeb517d7249b6f17cbb0d09a1a22f2d689be1d57 (diff) | |
download | cmd2-git-f58772040109e62b64c4781d0e8411fdc2f56ffb.tar.gz |
Merge branch 'master' into generating_output_docs
-rw-r--r-- | CHANGELOG.md | 6 | ||||
-rw-r--r-- | cmd2/cmd2.py | 106 | ||||
-rw-r--r-- | docs/features/scripting.rst | 28 | ||||
-rwxr-xr-x | tests/test_cmd2.py | 44 |
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' |