Skip to content

Commit 0fef7ac

Browse files
committed
improve input object template generator
1 parent 1a83925 commit 0fef7ac

File tree

1 file changed

+125
-46
lines changed

1 file changed

+125
-46
lines changed

cwltool/main.py

Lines changed: 125 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#!/usr/bin/env python
2+
"""Entry point for cwltool."""
23
from __future__ import absolute_import, print_function
34

45
import argparse
@@ -86,56 +87,136 @@ def _signal_handler(signum, _):
8687
sys.exit(signum)
8788

8889

89-
def generate_example_input(inptype):
90-
# type: (Union[Text, Dict[Text, Any]]) -> Any
90+
def generate_example_input(inptype, # type: Any
91+
default # type: Optional[Any]
92+
): # type: (...) -> Tuple[Any, Text]
93+
"""Converts a single input schema into an example."""
94+
example = None
95+
comment = u""
9196
defaults = {u'null': 'null',
9297
u'Any': 'null',
9398
u'boolean': False,
9499
u'int': 0,
95100
u'long': 0,
96101
u'float': 0.1,
97102
u'double': 0.1,
98-
u'string': 'default_string',
99-
u'File': {'class': 'File',
100-
'path': 'default/file/path'},
101-
u'Directory': {'class': 'Directory',
102-
'path': 'default/directory/path'}
103+
u'string': 'a_string',
104+
u'File': yaml.comments.CommentedMap([
105+
('class', 'File'), ('path', 'a/file/path')]),
106+
u'Directory': yaml.comments.CommentedMap([
107+
('class', 'Directory'), ('path', 'a/directory/path')])
103108
} # type: Dict[Text, Any]
104-
if (not isinstance(inptype, string_types) and
105-
not isinstance(inptype, collections.Mapping)
106-
and isinstance(inptype, collections.MutableSet)):
107-
if len(inptype) == 2 and 'null' in inptype:
109+
if isinstance(inptype, collections.MutableSequence):
110+
optional = False
111+
if 'null' in inptype:
108112
inptype.remove('null')
109-
return generate_example_input(inptype[0])
110-
# TODO: indicate that this input is optional
111-
raise Exception("multi-types other than optional not yet supported"
112-
" for generating example input objects: "
113-
"{}".format(inptype))
114-
if isinstance(inptype, collections.Mapping) and 'type' in inptype:
113+
optional = True
114+
if len(inptype) == 1:
115+
example, comment = generate_example_input(inptype[0], default)
116+
if optional:
117+
if comment:
118+
comment = u"{} (optional)".format(comment)
119+
else:
120+
comment = u"optional"
121+
else:
122+
example = yaml.comments.CommentedSeq()
123+
for index, entry in enumerate(inptype):
124+
value, e_comment = generate_example_input(entry, default)
125+
example.append(value)
126+
example.yaml_add_eol_comment(e_comment, index)
127+
if optional:
128+
comment = u"optional"
129+
elif isinstance(inptype, collections.Mapping) and 'type' in inptype:
115130
if inptype['type'] == 'array':
116-
return [generate_example_input(inptype['items'])]
117-
if inptype['type'] == 'enum':
118-
return 'valid_enum_value'
119-
# TODO: list valid values in a comment
120-
if inptype['type'] == 'record':
121-
record = {}
131+
if len(inptype['items']) == 1 and 'type' in inptype['items'][0] \
132+
and inptype['items'][0]['type'] == 'enum':
133+
# array of just an enum then list all the options
134+
example = inptype['items'][0]['symbols']
135+
if 'name' in inptype['items'][0]:
136+
comment = u'array of type "{}".'.format(inptype['items'][0]['name'])
137+
else:
138+
value, comment = generate_example_input(inptype['items'], None)
139+
comment = u"array of " + comment
140+
if len(inptype['items']) == 1:
141+
example = [value]
142+
else:
143+
example = value
144+
if default:
145+
example = default
146+
elif inptype['type'] == 'enum':
147+
if default:
148+
example = default
149+
elif 'default' in inptype:
150+
example = inptype['default']
151+
elif len(inptype['symbols']) == 1:
152+
example = inptype['symbols'][0]
153+
else:
154+
example = '{}_enum_value'.format(inptype.get('name', 'valid'))
155+
comment = u'enum; valid values: "{}"'.format(
156+
'", "'.join(inptype['symbols']))
157+
elif inptype['type'] == 'record':
158+
example = yaml.comments.CommentedMap()
159+
if 'name' in inptype:
160+
comment = u'"{}" record type.'.format(inptype['name'])
122161
for field in inptype['fields']:
123-
record[shortname(field['name'])] = generate_example_input(
124-
field['type'])
125-
return record
126-
elif isinstance(inptype, string_types):
127-
return defaults.get(Text(inptype), 'custom_type')
128-
# TODO: support custom types, complex arrays
129-
return None
130-
162+
value, f_comment = generate_example_input(field['type'], None)
163+
example.insert(0, shortname(field['name']), value, f_comment)
164+
elif 'default' in inptype:
165+
example = inptype['default']
166+
comment = u'default value of type "{}".'.format(inptype['type'])
167+
else:
168+
example = defaults.get(inptype['type'], Text(inptype))
169+
comment = u'type "{}".'.format(inptype['type'])
170+
else:
171+
if not default:
172+
example = defaults.get(Text(inptype), Text(inptype))
173+
comment = u'type "{}"'.format(inptype)
174+
else:
175+
example = default
176+
comment = u'default value of type "{}".'.format(inptype)
177+
return example, comment
178+
179+
def realize_input_schema(input_types, schema_defs):
180+
# type: (List[Dict[Text, Any]], Dict[Text, Any]) -> List[Dict[Text, Any]]
181+
"""Replace references to named typed with the actual types."""
182+
for index, entry in enumerate(input_types):
183+
if isinstance(entry, string_types):
184+
if '#' in entry:
185+
_, input_type_name = entry.split('#')
186+
else:
187+
input_type_name = entry
188+
if input_type_name in schema_defs:
189+
entry = input_types[index] = schema_defs[input_type_name]
190+
if isinstance(entry, collections.Mapping):
191+
if isinstance(entry['type'], string_types) and '#' in entry['type']:
192+
_, input_type_name = entry['type'].split('#')
193+
if input_type_name in schema_defs:
194+
input_types[index]['type'] = realize_input_schema(
195+
schema_defs[input_type_name], schema_defs)
196+
if isinstance(entry['type'], collections.MutableSequence):
197+
input_types[index]['type'] = realize_input_schema(
198+
entry['type'], schema_defs)
199+
if isinstance(entry['type'], collections.Mapping):
200+
input_types[index]['type'] = realize_input_schema(
201+
[input_types[index]['type']], schema_defs)
202+
if entry['type'] == 'array':
203+
items = entry['items'] if \
204+
not isinstance(entry['items'], string_types) else [entry['items']]
205+
input_types[index]['items'] = realize_input_schema(items, schema_defs)
206+
if entry['type'] == 'record':
207+
input_types[index]['fields'] = realize_input_schema(
208+
entry['fields'], schema_defs)
209+
return input_types
131210

132211
def generate_input_template(tool):
133212
# type: (Process) -> Dict[Text, Any]
134-
template = {}
135-
for inp in tool.tool["inputs"]:
213+
"""Generate an example input object for the given CWL process."""
214+
template = yaml.comments.CommentedMap()
215+
for inp in realize_input_schema(tool.tool["inputs"], tool.schemaDefs):
136216
name = shortname(inp["id"])
137-
inptype = inp["type"]
138-
template[name] = generate_example_input(inptype)
217+
value, comment = generate_example_input(
218+
inp['type'], inp.get('default', None))
219+
template.insert(0, name, value, comment)
139220
return template
140221

141222
def load_job_order(args, # type: argparse.Namespace
@@ -454,20 +535,14 @@ def main(argsl=None, # type: List[str]
454535
res.close()
455536
else:
456537
use_standard_schema("v1.0")
457-
#call function from provenance.py if the provenance flag is enabled.
458538
if args.provenance:
459539
if not args.compute_checksum:
460540
_logger.error("--provenance incompatible with --no-compute-checksum")
461541
return 1
462-
463542
runtimeContext.research_obj = ResearchObject(
464-
temp_prefix_ro=args.tmpdir_prefix,
465-
# Optionals, might be None
466-
orcid=args.orcid,
543+
temp_prefix_ro=args.tmpdir_prefix, orcid=args.orcid,
467544
full_name=args.cwl_full_name)
468545

469-
470-
471546
if loadingContext is None:
472547
loadingContext = LoadingContext(vars(args))
473548
else:
@@ -528,13 +603,17 @@ def main(argsl=None, # type: List[str]
528603
tool = make_tool(document_loader, avsc_names,
529604
metadata, uri, loadingContext)
530605
if args.make_template:
531-
yaml.safe_dump(generate_input_template(tool), sys.stdout,
532-
default_flow_style=False, indent=4,
533-
block_seq_indent=2)
606+
def my_represent_none(self, data): # pylint: disable=unused-argument
607+
"""Force clean representation of 'null'."""
608+
return self.represent_scalar(u'tag:yaml.org,2002:null', u'null')
609+
yaml.RoundTripRepresenter.add_representer(type(None), my_represent_none)
610+
yaml.round_trip_dump(
611+
generate_input_template(tool), sys.stdout,
612+
default_flow_style=False, indent=4, block_seq_indent=2)
534613
return 0
535614

536615
if args.validate:
537-
_logger.info("Tool definition is valid")
616+
print("{} is valid CWL.".format(args.workflow))
538617
return 0
539618

540619
if args.print_rdf:

0 commit comments

Comments
 (0)