summaryrefslogtreecommitdiff
path: root/cmd2/py_bridge.py
diff options
context:
space:
mode:
authorKevin Van Brunt <kmvanbrunt@gmail.com>2019-07-20 16:19:04 -0400
committerKevin Van Brunt <kmvanbrunt@gmail.com>2019-07-20 16:19:04 -0400
commit89523d4fa0de91d5959497496dd4bc0a430da333 (patch)
treea6e45284d133bd839765ca26e937aec5308d887e /cmd2/py_bridge.py
parent3254626f8d7c5a353b3f615fc80fa716006137fd (diff)
downloadcmd2-git-89523d4fa0de91d5959497496dd4bc0a430da333.tar.gz
Renamed PyscriptBridge to PyBridge
Diffstat (limited to 'cmd2/py_bridge.py')
-rw-r--r--cmd2/py_bridge.py109
1 files changed, 109 insertions, 0 deletions
diff --git a/cmd2/py_bridge.py b/cmd2/py_bridge.py
new file mode 100644
index 00000000..9864b0ac
--- /dev/null
+++ b/cmd2/py_bridge.py
@@ -0,0 +1,109 @@
+# coding=utf-8
+"""
+Bridges calls made inside of a Python environments to the Cmd2 host app while maintaining a reasonable
+degree of isolation between the two
+"""
+
+import sys
+from contextlib import redirect_stdout, redirect_stderr
+from typing import Optional
+
+from .utils import namedtuple_with_defaults, StdSim
+
+
+class CommandResult(namedtuple_with_defaults('CommandResult', ['stdout', 'stderr', 'stop', 'data'])):
+ """Encapsulates the results from a cmd2 app command
+
+ Named tuple attributes
+ ----------------------
+ stdout: str - output captured from stdout while this command is executing
+ stderr: str - output captured from stderr while this command is executing. None if no error captured.
+ stop: bool - return value of onecmd_plus_hooks after it runs the given command line.
+ data - possible data populated by the command.
+
+ Any combination of these fields can be used when developing a scripting API for a given command.
+ By default stdout, stderr, and stop will be captured for you. If there is additional command specific data,
+ then write that to cmd2's last_result member. That becomes the data member of this tuple.
+
+ In some cases, the data member may contain everything needed for a command and storing stdout
+ and stderr might just be a duplication of data that wastes memory. In that case, the StdSim can
+ be told not to store output with its pause_storage member. While this member is True, any output
+ sent to StdSim won't be saved in its buffer.
+
+ The code would look like this:
+ if isinstance(self.stdout, StdSim):
+ self.stdout.pause_storage = True
+
+ if isinstance(sys.stderr, StdSim):
+ sys.stderr.pause_storage = True
+
+ See StdSim class in utils.py for more information
+
+ NOTE: Named tuples are immutable. So the contents are there for access, not for modification.
+ """
+ def __bool__(self) -> bool:
+ """Returns True if the command succeeded, otherwise False"""
+
+ # If data has a __bool__ method, then call it to determine success of command
+ if self.data is not None and callable(getattr(self.data, '__bool__', None)):
+ return bool(self.data)
+
+ # Otherwise check if stderr was filled out
+ else:
+ return not self.stderr
+
+
+class PyBridge(object):
+ """Provides a Python API wrapper for application commands."""
+ def __init__(self, cmd2_app):
+ self._cmd2_app = cmd2_app
+ self.cmd_echo = False
+
+ # Tells if any of the commands run via __call__ returned True for stop
+ self.stop = False
+
+ def __dir__(self):
+ """Return a custom set of attribute names"""
+ attributes = []
+ attributes.insert(0, 'cmd_echo')
+ return attributes
+
+ def __call__(self, command: str, echo: Optional[bool] = None) -> CommandResult:
+ """
+ Provide functionality to call application commands by calling PyBridge
+ ex: app('help')
+ :param command: command line being run
+ :param echo: if True, output will be echoed to stdout/stderr while the command runs
+ this temporarily overrides the value of self.cmd_echo
+ """
+ if echo is None:
+ echo = self.cmd_echo
+
+ # This will be used to capture _cmd2_app.stdout and sys.stdout
+ copy_cmd_stdout = StdSim(self._cmd2_app.stdout, echo)
+
+ # Pause the storing of stdout until onecmd_plus_hooks enables it
+ copy_cmd_stdout.pause_storage = True
+
+ # This will be used to capture sys.stderr
+ copy_stderr = StdSim(sys.stderr, echo)
+
+ self._cmd2_app.last_result = None
+
+ stop = False
+ try:
+ self._cmd2_app.stdout = copy_cmd_stdout
+ with redirect_stdout(copy_cmd_stdout):
+ with redirect_stderr(copy_stderr):
+ stop = self._cmd2_app.onecmd_plus_hooks(command, py_bridge_call=True)
+ finally:
+ with self._cmd2_app.sigint_protection:
+ self._cmd2_app.stdout = copy_cmd_stdout.inner_stream
+ self.stop = stop or self.stop
+
+ # Save the output. If stderr is empty, set it to None.
+ result = CommandResult(stdout=copy_cmd_stdout.getvalue(),
+ stderr=copy_stderr.getvalue() if copy_stderr.getvalue() else None,
+ stop=stop,
+ data=self._cmd2_app.last_result)
+ return result