Skip to content

Commit c2d1de6

Browse files
committed
Added way to turn off storing stdout and stderr in StdSim
1 parent d6c6cf3 commit c2d1de6

File tree

2 files changed

+63
-32
lines changed

2 files changed

+63
-32
lines changed

cmd2/pyscript_bridge.py

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,26 @@ class CommandResult(namedtuple_with_defaults('CommandResult', ['stdout', 'stderr
2525
Named tuple attributes
2626
----------------------
2727
stdout: str - Output captured from stdout while this command is executing
28-
stderr: str - Output captured from stderr while this command is executing. None if no error captured
28+
stderr: str - Output captured from stderr while this command is executing. None if no error captured.
2929
data - Data returned by the command.
3030
31+
Any combination of these fields can be used when developing a scripting API for a given command.
32+
By default stdout and stderr will be captured for you. If there is additional command specific data,
33+
then write that to cmd2's _last_result member. That becomes the data member of this tuple.
34+
35+
In some cases, the data member may contain everything needed for a command and storing stdout
36+
and stderr might just be a duplication of data that wastes memory. In that case, the StdSim can
37+
be told not to store output with its set_store_output() method.
38+
39+
The code would look like this:
40+
if isinstance(self.stdout, StdSim):
41+
self.stdout.set_store_output(False)
42+
43+
if isinstance(sys.stderr, StdSim):
44+
sys.stderr.set_store_output(False)
45+
46+
See StdSim class in utils.py for more information
47+
3148
NOTE: Named tuples are immutable. So the contents are there for access, not for modification.
3249
"""
3350
def __bool__(self) -> bool:
@@ -67,25 +84,25 @@ def __call__(self, command: str, echo: Optional[bool] = None) -> CommandResult:
6784
if echo is None:
6885
echo = self.cmd_echo
6986

70-
copy_stdout = StdSim(sys.stdout, echo)
71-
copy_stderr = StdSim(sys.stderr, echo)
72-
87+
# This will be used to capture _cmd2_app.stdout and sys.stdout
7388
copy_cmd_stdout = StdSim(self._cmd2_app.stdout, echo)
7489

90+
# This will be used to capture sys.stderr
91+
copy_stderr = StdSim(sys.stderr, echo)
92+
7593
self._cmd2_app._last_result = None
7694

7795
try:
7896
self._cmd2_app.stdout = copy_cmd_stdout
79-
with redirect_stdout(copy_stdout):
97+
with redirect_stdout(copy_cmd_stdout):
8098
with redirect_stderr(copy_stderr):
8199
# Include a newline in case it's a multiline command
82100
self._cmd2_app.onecmd_plus_hooks(command + '\n')
83101
finally:
84102
self._cmd2_app.stdout = copy_cmd_stdout.inner_stream
85103

86-
# if stderr is empty, set it to None
87-
stderr = copy_stderr.getvalue() if copy_stderr.getvalue() else None
88-
89-
outbuf = copy_cmd_stdout.getvalue() if copy_cmd_stdout.getvalue() else copy_stdout.getvalue()
90-
result = CommandResult(stdout=outbuf, stderr=stderr, data=self._cmd2_app._last_result)
104+
# Save the output. If stderr is empty, set it to None.
105+
result = CommandResult(stdout=copy_cmd_stdout.getvalue(),
106+
stderr=copy_stderr.getvalue() if copy_stderr.getvalue() else None,
107+
data=self._cmd2_app._last_result)
91108
return result

cmd2/utils.py

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -261,28 +261,10 @@ def natural_sort(list_to_sort: Iterable[str]) -> List[str]:
261261

262262

263263
class StdSim(object):
264-
"""Class to simulate behavior of sys.stdout or sys.stderr.
265-
264+
"""
265+
Class to simulate behavior of sys.stdout or sys.stderr.
266266
Stores contents in internal buffer and optionally echos to the inner stream it is simulating.
267267
"""
268-
class ByteBuf(object):
269-
"""Inner class which stores an actual bytes buffer and does the actual output if echo is enabled."""
270-
def __init__(self, inner_stream, echo: bool = False,
271-
encoding: str = 'utf-8', errors: str = 'replace') -> None:
272-
self.byte_buf = b''
273-
self.inner_stream = inner_stream
274-
self.echo = echo
275-
self.encoding = encoding
276-
self.errors = errors
277-
278-
def write(self, b: bytes) -> None:
279-
"""Add bytes to internal bytes buffer and if echo is True, echo contents to inner stream."""
280-
if not isinstance(b, bytes):
281-
raise TypeError('a bytes-like object is required, not {}'.format(type(b)))
282-
self.byte_buf += b
283-
if self.echo:
284-
self.inner_stream.buffer.write(b)
285-
286268
def __init__(self, inner_stream, echo: bool = False,
287269
encoding: str = 'utf-8', errors: str = 'replace') -> None:
288270
"""
@@ -292,17 +274,20 @@ def __init__(self, inner_stream, echo: bool = False,
292274
:param encoding: codec for encoding/decoding strings (defaults to utf-8)
293275
:param errors: how to handle encoding/decoding errors (defaults to replace)
294276
"""
295-
self.buffer = self.ByteBuf(inner_stream, echo)
296277
self.inner_stream = inner_stream
297278
self.echo = echo
298279
self.encoding = encoding
299280
self.errors = errors
281+
self.__store_output = True
282+
self.buffer = ByteBuf(self)
300283

301284
def write(self, s: str) -> None:
302285
"""Add str to internal bytes buffer and if echo is True, echo contents to inner stream"""
303286
if not isinstance(s, str):
304287
raise TypeError('write() argument must be str, not {}'.format(type(s)))
305-
self.buffer.byte_buf += s.encode(encoding=self.encoding, errors=self.errors)
288+
289+
if self.__store_output:
290+
self.buffer.byte_buf += s.encode(encoding=self.encoding, errors=self.errors)
306291
if self.echo:
307292
self.inner_stream.write(s)
308293

@@ -330,13 +315,42 @@ def clear(self) -> None:
330315
"""Clear the internal contents"""
331316
self.buffer.byte_buf = b''
332317

318+
def get_store_output(self) -> bool:
319+
return self.__store_output
320+
321+
def set_store_output(self, store_output: bool) -> None:
322+
"""
323+
Set whether output should be saved in buffer.byte_buf
324+
:param store_output: Store output if True, otherwise do not and clear the buffer
325+
"""
326+
self.__store_output = self.buffer.store_output = store_output
327+
self.clear()
328+
333329
def __getattr__(self, item: str):
334330
if item in self.__dict__:
335331
return self.__dict__[item]
336332
else:
337333
return getattr(self.inner_stream, item)
338334

339335

336+
class ByteBuf(object):
337+
"""
338+
Used by StdSim to write binary data and stores the actual bytes written
339+
"""
340+
def __init__(self, std_sim_instance: StdSim) -> None:
341+
self.byte_buf = b''
342+
self.std_sim_instance = std_sim_instance
343+
344+
def write(self, b: bytes) -> None:
345+
"""Add bytes to internal bytes buffer and if echo is True, echo contents to inner stream."""
346+
if not isinstance(b, bytes):
347+
raise TypeError('a bytes-like object is required, not {}'.format(type(b)))
348+
if self.std_sim_instance.get_store_output():
349+
self.byte_buf += b
350+
if self.std_sim_instance.echo:
351+
self.std_sim_instance.inner_stream.buffer.write(b)
352+
353+
340354
def unquote_redirection_tokens(args: List[str]) -> None:
341355
"""
342356
Unquote redirection tokens in a list of command-line arguments

0 commit comments

Comments
 (0)