diff options
author | Kevin Van Brunt <kmvanbrunt@gmail.com> | 2019-07-20 16:19:04 -0400 |
---|---|---|
committer | Kevin Van Brunt <kmvanbrunt@gmail.com> | 2019-07-20 16:19:04 -0400 |
commit | 89523d4fa0de91d5959497496dd4bc0a430da333 (patch) | |
tree | a6e45284d133bd839765ca26e937aec5308d887e /cmd2/py_bridge.py | |
parent | 3254626f8d7c5a353b3f615fc80fa716006137fd (diff) | |
download | cmd2-git-89523d4fa0de91d5959497496dd4bc0a430da333.tar.gz |
Renamed PyscriptBridge to PyBridge
Diffstat (limited to 'cmd2/py_bridge.py')
-rw-r--r-- | cmd2/py_bridge.py | 109 |
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 |