44import typing as ty
55import re
66from collections import defaultdict
7+ import shlex
78import inspect
89from copy import copy
910import 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 )
0 commit comments