diff options
author | Lars Wirzenius <liw@liw.fi> | 2014-07-31 09:21:21 +0100 |
---|---|---|
committer | Lars Wirzenius <liw@liw.fi> | 2014-07-31 09:21:21 +0100 |
commit | f846d8c4628dcd87e4afee551f5dd8644f1e8a56 (patch) | |
tree | 3fe3cc57c587780a1e8278f8c92752f0ff7e2ee0 | |
parent | 938bad57588976a82c715c3acfdb12e755dd6f3e (diff) | |
parent | 0a40f6667af9ff73f8d6b1eb460e050d4ddeb95c (diff) | |
download | cliapp-f846d8c4628dcd87e4afee551f5dd8644f1e8a56.tar.gz |
Add callbacks to cliapp.runcmd for stdout/err data
-rw-r--r-- | NEWS | 7 | ||||
-rw-r--r-- | cliapp/runcmd.py | 23 | ||||
-rw-r--r-- | cliapp/runcmd_tests.py | 35 | ||||
-rw-r--r-- | example5.py | 46 | ||||
-rw-r--r-- | without-tests | 1 |
5 files changed, 107 insertions, 5 deletions
@@ -1,6 +1,13 @@ NEWS for cliapp =============== +Version UNRELEASED +------------------ + +* Richard Ipsum added callbacks to `cliapp.runcmd` for handling + captured stdout/stderr output from the pipeline. This allows, for + example, progress reporting during a long-running command. + Version 1.20140719 ------------------ diff --git a/cliapp/runcmd.py b/cliapp/runcmd.py index 6304a48..ee9698e 100644 --- a/cliapp/runcmd.py +++ b/cliapp/runcmd.py @@ -84,10 +84,15 @@ def runcmd_unchecked(argv, *argvs, **kwargs): else: return default + def noop(_): + pass + feed_stdin = pop_kwarg('feed_stdin', '') pipe_stdin = pop_kwarg('stdin', subprocess.PIPE) pipe_stdout = pop_kwarg('stdout', subprocess.PIPE) pipe_stderr = pop_kwarg('stderr', subprocess.PIPE) + stdout_callback = pop_kwarg('stdout_callback', noop) + stderr_callback = pop_kwarg('stderr_callback', noop) try: pipeline = _build_pipeline(argvs, @@ -96,7 +101,8 @@ def runcmd_unchecked(argv, *argvs, **kwargs): pipe_stderr, kwargs) return _run_pipeline(pipeline, feed_stdin, pipe_stdin, - pipe_stdout, pipe_stderr) + pipe_stdout, pipe_stderr, + stdout_callback, stderr_callback) except OSError, e: # pragma: no cover if e.errno == errno.ENOENT and e.filename is None: e.filename = argv[0] @@ -129,7 +135,8 @@ def _build_pipeline(argvs, pipe_stdin, pipe_stdout, pipe_stderr, kwargs): return procs -def _run_pipeline(procs, feed_stdin, pipe_stdin, pipe_stdout, pipe_stderr): +def _run_pipeline(procs, feed_stdin, pipe_stdin, pipe_stdout, pipe_stderr, + stdout_callback, stderr_callback): stdout_eof = False stderr_eof = False @@ -194,14 +201,22 @@ def _run_pipeline(procs, feed_stdin, pipe_stdin, pipe_stdout, pipe_stderr): if procs[-1].stdout in r: data = procs[-1].stdout.read(io_size) if data: - out.append(data) + logging.debug('calling stdout callback: %r', stdout_callback) + data_new = stdout_callback(data) + if data_new is None: + data_new = data + out.append(data_new) else: stdout_eof = True if procs[-1].stderr in r: data = procs[-1].stderr.read(io_size) if data: - err.append(data) + logging.debug('calling stderr callback: %r', stderr_callback) + data_new = stderr_callback(data) + if data_new is None: + data_new = data + err.append(data_new) else: stderr_eof = True diff --git a/cliapp/runcmd_tests.py b/cliapp/runcmd_tests.py index e89b73e..3346830 100644 --- a/cliapp/runcmd_tests.py +++ b/cliapp/runcmd_tests.py @@ -124,6 +124,40 @@ class RuncmdTests(unittest.TestCase): self.assertEqual(exit, 0) self.assertEqual(data, '') + def test_runcmd_calls_stdout_callback_when_msg_on_stdout(self): + msgs = [] + + def logger(s): + msgs.append(s) + + # We return a string to allow the callback to mangle + # the data being returned. + return 'foo' + + test_input = 'hello fox' + exit, out, err = cliapp.runcmd_unchecked(['echo', '-n', test_input], + stdout_callback=logger) + + self.assertEqual(out, 'foo') + self.assertEqual(msgs, [test_input]) + + def test_runcmd_calls_stderr_callback_when_msg_on_stderr(self): + msgs = [] + + def logger(s): + msgs.append(s) + + # We return None to signal that the data should not be + # mangled. + return None + + exit, out, err = cliapp.runcmd_unchecked(['ls', 'nosuchthing'], + stderr_callback=logger) + + self.assertEqual(len(msgs), 1) + self.assertNotEqual(err, '') + self.assertEqual(err, msgs[0]) + class ShellQuoteTests(unittest.TestCase): @@ -141,4 +175,3 @@ class ShellQuoteTests(unittest.TestCase): def test_quotes_single_quote(self): self.assertEqual(cliapp.shell_quote("'"), '"\'"') - diff --git a/example5.py b/example5.py new file mode 100644 index 0000000..2e83bae --- /dev/null +++ b/example5.py @@ -0,0 +1,46 @@ +# Copyright (C) 2014 Richard Ipsum +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + +import cliapp +import logging + + +class LoggerApp(cliapp.Application): + + def log(self, s): + ''' A dummy logger function + + In practice this would log the contents + to a file or a set of files, perhaps filtering the output or + prefixing each line with a date and time. + + ''' + + self.output.write('log: %s' % s) + self.output.flush() + + def process_args(self, args): + commands = [['echo', 'hello world!'], ['ls', 'nosuchthing']] + + for command in commands: + try: + self.runcmd(command, stdout_callback=self.log, + stderr_callback=self.log) + except cliapp.AppException: + pass + +LoggerApp().run() diff --git a/without-tests b/without-tests index 9f87e60..c6dc59d 100644 --- a/without-tests +++ b/without-tests @@ -10,3 +10,4 @@ ./test-plugins/aaa_hello_plugin.py example3.py example4.py +example5.py |