Skip to content

Commit 4b6ac3b

Browse files
committed
shell.define can take a list of tokens as well as a single string
1 parent f64a886 commit 4b6ac3b

File tree

2 files changed

+31
-33
lines changed

2 files changed

+31
-33
lines changed

pydra/design/shell.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import typing as ty
55
import re
66
from collections import defaultdict
7+
import shlex
78
import inspect
89
from copy import copy
910
import attrs
@@ -338,16 +339,13 @@ def make(
338339
ShellDef, ShellOutputs, klass, arg, out, auto_attribs
339340
)
340341
else:
341-
if not isinstance(wrapped, str):
342+
if not isinstance(wrapped, (str, list)):
342343
raise ValueError(
343344
f"wrapped must be a class or a string, not {wrapped!r}"
344345
)
345346
klass = None
346347
input_helps, output_helps = {}, {}
347348

348-
if isinstance(wrapped, list):
349-
wrapped = " ".join(wrapped)
350-
351349
executable, inferred_inputs, inferred_outputs = parse_command_line_template(
352350
wrapped,
353351
inputs=inputs,
@@ -435,8 +433,10 @@ def make(
435433
# If wrapped is provided (i.e. this is not being used as a decorator), return the
436434
# interface class
437435
if wrapped is not None:
438-
if not isinstance(wrapped, (type, str)):
439-
raise ValueError(f"wrapped must be a class or a string, not {wrapped!r}")
436+
if not isinstance(wrapped, (type, str, list)):
437+
raise ValueError(
438+
f"wrapped must be a class, a string or a list, not {wrapped!r}"
439+
)
440440
return make(wrapped)
441441
return make
442442

@@ -504,10 +504,13 @@ def parse_command_line_template(
504504
else:
505505
assert outputs is None
506506
outputs = {}
507-
parts = template.split()
507+
if isinstance(template, list):
508+
tokens = template
509+
else:
510+
tokens = shlex.split(template)
508511
executable = []
509512
start_args_index = 0
510-
for part in parts:
513+
for part in tokens:
511514
if part.startswith("<") or part.startswith("-"):
512515
break
513516
executable.append(part)
@@ -516,10 +519,9 @@ def parse_command_line_template(
516519
raise ValueError(f"Found no executable in command line template: {template}")
517520
if len(executable) == 1:
518521
executable = executable[0]
519-
args_str = " ".join(parts[start_args_index:])
520-
if not args_str:
522+
tokens = tokens[start_args_index:]
523+
if not tokens:
521524
return executable, inputs, outputs
522-
tokens = re.split(r"\s+", args_str.strip())
523525
arg_pattern = r"<([:a-zA-Z0-9_,\|\-\.\/\+\*]+(?:\?|=[^>]+)?)>"
524526
opt_pattern = r"--?[a-zA-Z0-9_]+"
525527
arg_re = re.compile(arg_pattern)
@@ -661,7 +663,7 @@ def from_type_str(type_str) -> type:
661663
option = token
662664
else:
663665
raise ValueError(
664-
f"Found unknown token '{token}' in command line template: {template}"
666+
f"Found unknown token {token!r} in command line template: {template}"
665667
)
666668

667669
remaining_pos = remaining_positions(arguments, len(arguments) + 1, 1)

pydra/engine/tests/test_singularity.py

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import shutil
22
import subprocess as sp
33
import pytest
4-
from ..submitter import Submitter
4+
from pydra.engine.submitter import Submitter
5+
from pydra.engine.specs import ShellDef, ShellOutputs
56
from pydra.design import shell, workflow
67
from fileformats.generic import File
78
from pydra.engine.environments import Singularity
@@ -369,30 +370,25 @@ def test_singularity_cmd_inputspec_copyfile_1(plugin, tmp_path):
369370
with open(file, "w") as f:
370371
f.write("hello from pydra\n")
371372

372-
cmd = "sed -is 's/hello/hi/'"
373373
image = "docker://alpine"
374374

375-
Singu = shell.define(
376-
cmd,
377-
inputs=[
378-
shell.arg(
379-
name="orig_file",
380-
type=File,
381-
position=1,
382-
argstr="",
383-
help="orig file",
384-
copy_mode=File.CopyMode.copy,
385-
),
386-
],
387-
outputs=[
388-
shell.outarg(
389-
name="out_file",
390-
type=File,
375+
@shell.define
376+
class Singu(ShellDef["Singu.Outputs"]):
377+
378+
executable = ["sed", "-is", "'s/hello/hi/'"]
379+
380+
orig_file: File = shell.arg(
381+
position=1,
382+
argstr="",
383+
help="orig file",
384+
copy_mode=File.CopyMode.copy,
385+
)
386+
387+
class Outputs(ShellOutputs):
388+
out_file: File = shell.outarg(
391389
path_template="{orig_file}",
392390
help="output file",
393-
),
394-
],
395-
)
391+
)
396392

397393
singu = Singu(orig_file=str(file))
398394

0 commit comments

Comments
 (0)