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