run.py 3.86 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python
#
# run.py - Functions for running shell commands
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
"""This module provides some functions for running shell commands.

.. autosummary::
   :nosignatures:

   run
   runfsl
   fslsub
"""


import               logging
19
import               contextlib
20
21
22
import subprocess as sp
import os.path    as op

23
24
import               six

25
26
27
28
29
30
from fsl.utils.platform import platform as fslplatform


log = logging.getLogger(__name__)


31
32
33
34
35
36
DRY_RUN = False
"""If ``True``, the :func:`run` function will only log commands, but will not
execute them.
"""


37
38
39
40
41
42
43
class FSLNotPresent(Exception):
    """Error raised by the :func:`runfsl` function when ``$FSLDIR`` cannot
    be found.
    """
    pass


44
45
46
47
@contextlib.contextmanager
def dryrun(*args):
    """Context manager which causes all calls to :func:`run` to be logged but
    not executed. See the :data:`DRY_RUN` flag.
48
49

    The returned standard output will be equal to ``' '.join(args)``.
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
    """
    global DRY_RUN

    oldval  = DRY_RUN
    DRY_RUN = True

    try:
        yield
    finally:
        DRY_RUN = oldval


def _prepareArgs(args):
    """Used by the :func:`run` function. Ensures that the given arguments is a
    list of strings.
65
66
67
68
    """

    if len(args) == 1:

69
70
71
        # Argument was a command string
        if isinstance(args[0], six.string_types):
            args = args[0].split()
72

73
74
75
        # Argument was an unpacked sequence
        else:
            args = args[0]
76

77
78
79
    return list(args)


80
def run(*args, **kwargs):
81
82
    """Call a command and return its output. You can pass the command and
    arguments as a single string, or as a regular or unpacked sequence.
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98

    An exception is raised if the command returns a non-zero exit code, unless
    the ``returnCode`` option is set to ``True``.

    :arg err: Must be passed as a keyword argument. Defaults to
              ``False``. If ``True``, standard error is captured and
              returned.

    :arg ret: Must be passed as a keyword argument. Defaults to
              ``False``. If ``True``, and the command's return code
              is non-0, an exception is not raised.

    :returns: A string containing the command's standard output. Or,
              if ``err is True`` and/or ``returnCode is True``, a tuple
              containing the standard output, standard error (if
              ``err``), and return code (if ``returnCode``).
99
100
    """

101
102
    err  = kwargs.get('err', False)
    ret  = kwargs.get('ret', False)
103
104
105
106
107
108
109
110
    args = _prepareArgs(args)

    if DRY_RUN:
        log.debug('dryrun: {}'.format(' '.join(args)))
    else:
        log.debug('run: {}'.format(' '.join(args)))

    if DRY_RUN:
111
        stdout = ' '.join(args)
112
        stderr = ''
113
    else:
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
        proc           = sp.Popen(args, stdout=sp.PIPE, stderr=sp.PIPE)
        stdout, stderr = proc.communicate()
        retcode        = proc.returncode

        stdout = stdout.decode('utf-8').strip()
        stderr = stderr.decode('utf-8').strip()

        log.debug('stdout: {}'.format(stdout))
        log.debug('stderr: {}'.format(stderr))

        if not ret and (retcode != 0):
            raise RuntimeError('{} returned non-zero exit code: {}'.format(
                args[0], retcode))

    results = [stdout]
129

130
131
    if err: results.append(stderr)
    if ret: results.append(retcode)
132

133
134
    if len(results) == 1: return results[0]
    else:                 return tuple(results)
135
136


137
def runfsl(*args, **kwargs):
138
    """Call a FSL command and return its output. This function simply prepends
139
    ``$FSLDIR/bin/`` to the command before passing it to :func:`run`.
140
141
142
    """

    if fslplatform.fsldir is None:
143
        raise FSLNotPresent('$FSLDIR is not set - FSL cannot be found!')
144

145
    args    = _prepareArgs(args)
146
147
    args[0] = op.join(fslplatform.fsldir, 'bin', args[0])

148
    return run(*args, **kwargs)
149
150
151
152
153


def fslsub(*args):
    """Not implemented yet. """
    raise NotImplementedError('')