terom@1: """ terom@1: Invoke external commands, with python kwargs -> options mangling. terom@1: """ terom@1: terom@30: import logging terom@30: import os terom@1: import subprocess terom@1: terom@1: log = logging.getLogger('pvl.invoke') terom@1: terom@1: class InvokeError (Exception) : terom@1: def __init__ (self, cmd, exit, error) : terom@1: self.cmd = cmd terom@1: self.exit = exit terom@1: self.error = error terom@1: terom@1: def __str__ (self) : terom@1: return "{self.cmd} failed ({self.exit}): {self.error}".format(self=self) terom@1: terom@30: def invoke (cmd, args, stdin=None, setenv=None) : terom@1: """ terom@1: Invoke a command directly. terom@1: terom@1: stdin: terom@1: False -> passthrough stdin/stdout terom@1: None -> return lines of stdout terom@1: [lines] -> write lines on stdin, return lines of stdout terom@1: terom@1: Raises InvokeError on nonzero exit, otherwise log.warn's any stderr. terom@1: """ terom@1: terom@1: log.debug("{cmd} {args}".format(cmd=cmd, args=' '.join(args))) terom@1: terom@1: if stdin is False : terom@1: # keep process stdin/out terom@1: io = None terom@1: input = None terom@1: terom@1: elif stdin : terom@1: # return stdout, give stdin terom@1: io = subprocess.PIPE terom@1: input = '\n'.join(stdin) + '\n' terom@1: terom@1: else : terom@1: # return stdout terom@1: io = subprocess.PIPE terom@1: input = None terom@1: terom@30: if setenv : terom@30: env = dict(os.environ) terom@30: env.update(setenv) terom@30: terom@30: else : terom@30: env = None terom@30: terom@30: p = subprocess.Popen([cmd] + list(args), stdin=io, stdout=io, stderr=io, env=env) terom@1: terom@1: # get output terom@1: # returns None if not io terom@1: stdout, stderr = p.communicate(input=input) terom@1: terom@1: if p.returncode : terom@1: # failed terom@1: raise InvokeError(cmd, p.returncode, stderr) terom@1: terom@1: elif stderr : terom@1: log.warning("%s: %s", cmd, stderr) terom@1: terom@1: if stdout : terom@1: return stdout.splitlines() terom@1: else : terom@1: return None terom@1: terom@1: import collections terom@1: terom@1: def process_opt (name, value) : terom@1: """ terom@1: Mangle from python keyword-argument dict format to command-line option tuple format. terom@1: terom@1: >>> process_opt('foo', True) terom@1: ('--foo',) terom@1: >>> process_opt('foo', 2) terom@1: ('--foo', '2') terom@1: >>> process_opt('foo', 'bar') terom@1: ('--foo', 'bar') terom@1: >>> process_opt('foo_bar', 'asdf') terom@1: ('--foo-bar', 'asdf') terom@1: terom@1: # multi terom@1: >>> process_opt('foo', ['bar', 'quux']) terom@1: ('--foo', 'bar', '--foo', 'quux') terom@1: >>> process_opt('foo', [False, 'bar', True]) terom@1: ('--foo', 'bar', '--foo') terom@1: terom@1: # empty terom@1: >>> process_opt('foo', False) terom@1: () terom@1: >>> process_opt('foo', None) terom@1: () terom@1: >>> process_opt('bar', '') terom@1: () terom@1: terom@1: Returns a tuple of argv items. terom@1: """ terom@1: terom@1: # mangle opt terom@1: opt = '--' + name.replace('_', '-') terom@1: terom@1: if value is True : terom@1: # flag opt terom@1: return (opt, ) terom@1: terom@1: elif not value : terom@1: # flag opt / omit terom@1: return ( ) terom@1: terom@1: elif isinstance(value, basestring) : terom@1: return (opt, value) terom@1: terom@1: elif isinstance(value, collections.Iterable) : terom@1: opts = (process_opt(name, subvalue) for subvalue in value) terom@1: terom@1: # flatten terom@1: return tuple(part for parts in opts for part in parts) terom@1: terom@1: else : terom@1: # as-is terom@1: return (opt, str(value)) terom@1: terom@1: def optargs (*args, **kwargs) : terom@1: """ terom@1: Convert args/options into command-line format terom@1: terom@1: >>> optargs('foo') terom@1: ['foo'] terom@1: >>> optargs(foo=True) terom@1: ['--foo'] terom@1: >>> optargs(foo=False) terom@1: [] terom@1: >>> optargs(foo='bar') terom@1: ['--foo', 'bar'] terom@1: """ terom@1: terom@1: ## opts terom@1: # process terom@1: opts = [process_opt(opt, value) for opt, value in kwargs.iteritems()] terom@1: terom@1: # flatten terom@1: opts = [str(part) for parts in opts for part in parts] terom@1: terom@1: ## args terom@1: args = [str(arg) for arg in args if arg] terom@1: terom@1: return opts + args terom@1: terom@1: # XXX: move to pvl.utils or something random? terom@1: def merge (*dicts, **kwargs) : terom@1: """ terom@1: Merge given dicts together. terom@1: terom@1: >>> merge(foo=1, bar=2) terom@1: {'foo': 1, 'bar': 2} terom@1: >>> merge(dict(foo=1), bar=2) terom@1: {'foo': 1, 'bar': 2} terom@1: >>> merge(dict(foo=1), bar=2, foo=3) terom@1: {'foo': 3, 'bar': 2} terom@1: >>> merge(dict(foo=1), dict(bar=2), foo=3) terom@1: {'foo': 3, 'bar': 2} terom@1: >>> merge(dict(bar=2), dict(foo=1), foo=3) terom@1: {'foo': 3, 'bar': 2} terom@1: terom@1: """ terom@1: terom@1: return dict((k, v) for d in (dicts + (kwargs, )) for k, v in d.iteritems()) terom@1: terom@1: terom@1: def command (cmd, *args, **opts) : terom@1: """ terom@1: Invoke a command with options/arguments, given via Python arguments/keyword arguments. terom@1: terom@1: Return stdout. terom@1: """ terom@1: terom@1: log.debug("{cmd} {opts} {args}".format(cmd=cmd, args=args, opts=opts)) terom@1: terom@1: # invoke terom@1: return invoke(cmd, optargs(*args, **opts)) terom@1: terom@1: if __name__ == '__main__': terom@1: import doctest terom@1: doctest.testmod() terom@1: