diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 09d15c5fca9ef6fac5b8d8dc67705e816e8c8ed1..f85840ef56f69a20c2fbdd3c2f80466358e97a98 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,20 @@ This document contains the ``fslpy`` release history in reverse chronological order. + +3.3.2 (Tuesday 12th October 2020) +--------------------------------- + + +Changed +^^^^^^^ + + +* Most :func:`.wrapper` functions now accept an argument called ``cmdonly`` + which, if ``True``, will cause the generated command-line call to be + returned, instead of executed. + + 3.3.1 (Thursday 8th October 2020) --------------------------------- diff --git a/fsl/utils/run.py b/fsl/utils/run.py index 3ed799ca8330f2c17585d8959da448c3b9ccf6eb..cf75fc505ce80e7db8046133970061fdca4ead90 100644 --- a/fsl/utils/run.py +++ b/fsl/utils/run.py @@ -151,6 +151,10 @@ def run(*args, **kwargs): the :func:`.fslsub.submit` function. May also be a dictionary containing arguments to that function. + :arg cmdonly: Defaults to ``False``. If ``True``, the command is not + executed, but rather is returned directly, as a list of + arguments. + :arg log: Must be passed as a keyword argument. An optional ``dict`` which may be used to redirect the command's standard output and error. The following keys are recognised: @@ -181,6 +185,7 @@ def run(*args, **kwargs): returnStderr = kwargs.pop('stderr', False) returnExitcode = kwargs.pop('exitcode', False) submit = kwargs.pop('submit', {}) + cmdonly = kwargs.pop('cmdonly', False) log = kwargs.pop('log', None) args = prepareArgs(args) @@ -207,6 +212,9 @@ def run(*args, **kwargs): raise ValueError('submit must be a mapping containing ' 'options for fsl.utils.fslsub.submit') + if cmdonly: + return args + if DRY_RUN: return _dryrun( submit, returnStdout, returnStderr, returnExitcode, *args) diff --git a/fsl/wrappers/wrapperutils.py b/fsl/wrappers/wrapperutils.py index 7a58c02317928c83dea9c9e7c899de0f666ff78d..2bfbf500c69dcef76c1e3b0a3a2de6fb8a043eb1 100644 --- a/fsl/wrappers/wrapperutils.py +++ b/fsl/wrappers/wrapperutils.py @@ -149,46 +149,73 @@ def _unwrap(func): return func -def cmdwrapper(func): - """This decorator can be used on functions which generate a command line. - It will pass the return value of the function to the - :func:`fsl.utils.run.run` function in a standardised manner. +def genxwrapper(func, runner): + """This function is used by :func:`cmdwrapper` and :func:`fslwrapper`. + It is not intended to be used in any other circumstances. + + This function generates a wrapper function which calls ``func`` to + generate a command-line call, and then uses ``runner`` to invoke that + command. + + ``func`` is assumed to be a wrapper function which generates a command- + line. ``runner`` is assumed to be Either :func:`.run.run` or + :func:`.run.runfsl`. + + The generated wrapper function will pass all of its arguments to ``func``, + and will then pass the generated command-line to ``runner``, returning + whatever is returned. + + The following keyword arguments will be intercepted by the wrapper + function, and will *not* be passed to ``func``: + - ``stdout``: Passed to ``runner``. Defaults to ``True``. + - ``stderr``: Passed to ``runner``. Defaults to ``True``. + - ``exitcode``: Passed to ``runner``. Defaults to ``False``. + - ``submit``: Passed to ``runner``. Defaults to ``None``. + - ``log``: Passed to ``runner``. Defaults to ``{'tee':True}``. + - ``cmdonly``: Passed to ``runner``. Defaults to ``False``. + + :arg func: A function which generates a command line. + :arg runner: Either :func:`.run.run` or :func:`.run.runfsl`. """ + def wrapper(*args, **kwargs): stdout = kwargs.pop('stdout', True) stderr = kwargs.pop('stderr', True) exitcode = kwargs.pop('exitcode', False) submit = kwargs.pop('submit', None) + cmdonly = kwargs.pop('cmdonly', False) log = kwargs.pop('log', {'tee' : True}) cmd = func(*args, **kwargs) - return run.run(cmd, - stderr=stderr, - log=log, - submit=submit, - stdout=stdout, - exitcode=exitcode) + + return runner(cmd, + stderr=stderr, + log=log, + submit=submit, + cmdonly=cmdonly, + stdout=stdout, + exitcode=exitcode) + return _update_wrapper(wrapper, func) +def cmdwrapper(func): + """This decorator can be used on functions which generate a command line. + It will pass the return value of the function to the + :func:`fsl.utils.run.run` function in a standardised manner. + + See the :func:`genxwrapper` function for details. + """ + return genxwrapper(func, run.run) + + def fslwrapper(func): """This decorator can be used on functions which generate a FSL command line. It will pass the return value of the function to the :func:`fsl.utils.run.runfsl` function in a standardised manner. + + See the :func:`genxwrapper` function for details. """ - def wrapper(*args, **kwargs): - stdout = kwargs.pop('stdout', True) - stderr = kwargs.pop('stderr', True) - exitcode = kwargs.pop('exitcode', False) - submit = kwargs.pop('submit', None) - log = kwargs.pop('log', {'tee' : True}) - cmd = func(*args, **kwargs) - return run.runfsl(cmd, - stderr=stderr, - log=log, - submit=submit, - stdout=stdout, - exitcode=exitcode) - return _update_wrapper(wrapper, func) + return genxwrapper(func, run.runfsl) SHOW_IF_TRUE = object() diff --git a/tests/test_run.py b/tests/test_run.py index 363d4489ec30d25799e152f23e96becf12f06ab9..7308b849c72f6f82afd00eb417c4d479b58d0f30 100644 --- a/tests/test_run.py +++ b/tests/test_run.py @@ -179,6 +179,13 @@ def test_run_passthrough(): assert run.run('./script.sh', env=env) == expstdout +def test_cmdonly(): + assert run.run('script.sh', cmdonly=True) == ['script.sh'] + assert run.run('script.sh 1 2 3', cmdonly=True) == ['script.sh', '1', '2', '3'] + assert run.run(['script.sh'], cmdonly=True) == ['script.sh'] + assert run.run(['script.sh', '1'], cmdonly=True) == ['script.sh', '1'] + + def test_dryrun(): test_script = textwrap.dedent(""" diff --git a/tests/test_wrappers/test_wrapperutils.py b/tests/test_wrappers/test_wrapperutils.py index e928057ff858a37b030f149782cf33400fe54e02..d19e3ed13374217fb4d7e393ee5d87ccb5327802 100644 --- a/tests/test_wrappers/test_wrapperutils.py +++ b/tests/test_wrappers/test_wrapperutils.py @@ -753,15 +753,20 @@ def test_cmdwrapper(): with run.dryrun(): assert func(1, 2)[0] == 'func 1 2' + assert func(1, 2, cmdonly=True) == ['func', '1', '2'] + def test_fslwrapper(): @wutils.fslwrapper def func(a, b): return ['func', str(a), str(b)] - with run.dryrun(), mockFSLDIR(bin=('func',)) as fsldir: + with mockFSLDIR(bin=('func',)) as fsldir: expected = '{} 1 2'.format(op.join(fsldir, 'bin', 'func')) - assert func(1, 2)[0] == expected + with run.dryrun(): + assert func(1, 2)[0] == expected + + func(1, 2, cmdonly=True)[0] == list(shlex.split(expected)) _test_script = textwrap.dedent("""