|
1 | 1 | #!/usr/bin/env python
|
| 2 | +"""Entry point for cwltool.""" |
2 | 3 | from __future__ import absolute_import, print_function
|
3 | 4 |
|
4 | 5 | import argparse
|
|
14 | 15 | import sys
|
15 | 16 |
|
16 | 17 | from typing import (IO, Any, Callable, Dict, Iterable, List, Mapping,
|
17 |
| - MutableMapping, Optional, TextIO, Tuple, Union, cast) |
| 18 | + MutableMapping, MutableSequence, Optional, TextIO, Tuple, |
| 19 | + Union, cast) |
18 | 20 | from typing_extensions import Text # pylint: disable=unused-import
|
19 | 21 | # move to a regular typing import when Python 3.3-3.6 is no longer supported
|
20 | 22 | import pkg_resources # part of setuptools
|
@@ -86,56 +88,137 @@ def _signal_handler(signum, _):
|
86 | 88 | sys.exit(signum)
|
87 | 89 |
|
88 | 90 |
|
89 |
| -def generate_example_input(inptype): |
90 |
| - # type: (Union[Text, Dict[Text, Any]]) -> Any |
| 91 | +def generate_example_input(inptype, # type: Any |
| 92 | + default # type: Optional[Any] |
| 93 | + ): # type: (...) -> Tuple[Any, Text] |
| 94 | + """Converts a single input schema into an example.""" |
| 95 | + example = None |
| 96 | + comment = u"" |
91 | 97 | defaults = {u'null': 'null',
|
92 | 98 | u'Any': 'null',
|
93 | 99 | u'boolean': False,
|
94 | 100 | u'int': 0,
|
95 | 101 | u'long': 0,
|
96 | 102 | u'float': 0.1,
|
97 | 103 | 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'} |
| 104 | + u'string': 'a_string', |
| 105 | + u'File': yaml.comments.CommentedMap([ |
| 106 | + ('class', 'File'), ('path', 'a/file/path')]), |
| 107 | + u'Directory': yaml.comments.CommentedMap([ |
| 108 | + ('class', 'Directory'), ('path', 'a/directory/path')]) |
103 | 109 | } # 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: |
| 110 | + if isinstance(inptype, collections.MutableSequence): |
| 111 | + optional = False |
| 112 | + if 'null' in inptype: |
108 | 113 | 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: |
| 114 | + optional = True |
| 115 | + if len(inptype) == 1: |
| 116 | + example, comment = generate_example_input(inptype[0], default) |
| 117 | + if optional: |
| 118 | + if comment: |
| 119 | + comment = u"{} (optional)".format(comment) |
| 120 | + else: |
| 121 | + comment = u"optional" |
| 122 | + else: |
| 123 | + example = yaml.comments.CommentedSeq() |
| 124 | + for index, entry in enumerate(inptype): |
| 125 | + value, e_comment = generate_example_input(entry, default) |
| 126 | + example.append(value) |
| 127 | + example.yaml_add_eol_comment(e_comment, index) |
| 128 | + if optional: |
| 129 | + comment = u"optional" |
| 130 | + elif isinstance(inptype, collections.Mapping) and 'type' in inptype: |
115 | 131 | 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 = {} |
| 132 | + if len(inptype['items']) == 1 and 'type' in inptype['items'][0] \ |
| 133 | + and inptype['items'][0]['type'] == 'enum': |
| 134 | + # array of just an enum then list all the options |
| 135 | + example = inptype['items'][0]['symbols'] |
| 136 | + if 'name' in inptype['items'][0]: |
| 137 | + comment = u'array of type "{}".'.format(inptype['items'][0]['name']) |
| 138 | + else: |
| 139 | + value, comment = generate_example_input(inptype['items'], None) |
| 140 | + comment = u"array of " + comment |
| 141 | + if len(inptype['items']) == 1: |
| 142 | + example = [value] |
| 143 | + else: |
| 144 | + example = value |
| 145 | + if default: |
| 146 | + example = default |
| 147 | + elif inptype['type'] == 'enum': |
| 148 | + if default: |
| 149 | + example = default |
| 150 | + elif 'default' in inptype: |
| 151 | + example = inptype['default'] |
| 152 | + elif len(inptype['symbols']) == 1: |
| 153 | + example = inptype['symbols'][0] |
| 154 | + else: |
| 155 | + example = '{}_enum_value'.format(inptype.get('name', 'valid')) |
| 156 | + comment = u'enum; valid values: "{}"'.format( |
| 157 | + '", "'.join(inptype['symbols'])) |
| 158 | + elif inptype['type'] == 'record': |
| 159 | + example = yaml.comments.CommentedMap() |
| 160 | + if 'name' in inptype: |
| 161 | + comment = u'"{}" record type.'.format(inptype['name']) |
122 | 162 | 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 |
| - |
| 163 | + value, f_comment = generate_example_input(field['type'], None) |
| 164 | + example.insert(0, shortname(field['name']), value, f_comment) |
| 165 | + elif 'default' in inptype: |
| 166 | + example = inptype['default'] |
| 167 | + comment = u'default value of type "{}".'.format(inptype['type']) |
| 168 | + else: |
| 169 | + example = defaults.get(inptype['type'], Text(inptype)) |
| 170 | + comment = u'type "{}".'.format(inptype['type']) |
| 171 | + else: |
| 172 | + if not default: |
| 173 | + example = defaults.get(Text(inptype), Text(inptype)) |
| 174 | + comment = u'type "{}"'.format(inptype) |
| 175 | + else: |
| 176 | + example = default |
| 177 | + comment = u'default value of type "{}".'.format(inptype) |
| 178 | + return example, comment |
| 179 | + |
| 180 | +def realize_input_schema(input_types, # type: MutableSequence[Dict[Text, Any]] |
| 181 | + schema_defs # type: Dict[Text, Any] |
| 182 | + ): # type: (...) -> MutableSequence[Dict[Text, Any]] |
| 183 | + """Replace references to named typed with the actual types.""" |
| 184 | + for index, entry in enumerate(input_types): |
| 185 | + if isinstance(entry, string_types): |
| 186 | + if '#' in entry: |
| 187 | + _, input_type_name = entry.split('#') |
| 188 | + else: |
| 189 | + input_type_name = entry |
| 190 | + if input_type_name in schema_defs: |
| 191 | + entry = input_types[index] = schema_defs[input_type_name] |
| 192 | + if isinstance(entry, collections.Mapping): |
| 193 | + if isinstance(entry['type'], string_types) and '#' in entry['type']: |
| 194 | + _, input_type_name = entry['type'].split('#') |
| 195 | + if input_type_name in schema_defs: |
| 196 | + input_types[index]['type'] = realize_input_schema( |
| 197 | + schema_defs[input_type_name], schema_defs) |
| 198 | + if isinstance(entry['type'], collections.MutableSequence): |
| 199 | + input_types[index]['type'] = realize_input_schema( |
| 200 | + entry['type'], schema_defs) |
| 201 | + if isinstance(entry['type'], collections.Mapping): |
| 202 | + input_types[index]['type'] = realize_input_schema( |
| 203 | + [input_types[index]['type']], schema_defs) |
| 204 | + if entry['type'] == 'array': |
| 205 | + items = entry['items'] if \ |
| 206 | + not isinstance(entry['items'], string_types) else [entry['items']] |
| 207 | + input_types[index]['items'] = realize_input_schema(items, schema_defs) |
| 208 | + if entry['type'] == 'record': |
| 209 | + input_types[index]['fields'] = realize_input_schema( |
| 210 | + entry['fields'], schema_defs) |
| 211 | + return input_types |
131 | 212 |
|
132 | 213 | def generate_input_template(tool):
|
133 | 214 | # type: (Process) -> Dict[Text, Any]
|
134 |
| - template = {} |
135 |
| - for inp in tool.tool["inputs"]: |
| 215 | + """Generate an example input object for the given CWL process.""" |
| 216 | + template = yaml.comments.CommentedMap() |
| 217 | + for inp in realize_input_schema(tool.tool["inputs"], tool.schemaDefs): |
136 | 218 | name = shortname(inp["id"])
|
137 |
| - inptype = inp["type"] |
138 |
| - template[name] = generate_example_input(inptype) |
| 219 | + value, comment = generate_example_input( |
| 220 | + inp['type'], inp.get('default', None)) |
| 221 | + template.insert(0, name, value, comment) |
139 | 222 | return template
|
140 | 223 |
|
141 | 224 | def load_job_order(args, # type: argparse.Namespace
|
@@ -454,20 +537,14 @@ def main(argsl=None, # type: List[str]
|
454 | 537 | res.close()
|
455 | 538 | else:
|
456 | 539 | use_standard_schema("v1.0")
|
457 |
| - #call function from provenance.py if the provenance flag is enabled. |
458 | 540 | if args.provenance:
|
459 | 541 | if not args.compute_checksum:
|
460 | 542 | _logger.error("--provenance incompatible with --no-compute-checksum")
|
461 | 543 | return 1
|
462 |
| - |
463 | 544 | runtimeContext.research_obj = ResearchObject(
|
464 |
| - temp_prefix_ro=args.tmpdir_prefix, |
465 |
| - # Optionals, might be None |
466 |
| - orcid=args.orcid, |
| 545 | + temp_prefix_ro=args.tmpdir_prefix, orcid=args.orcid, |
467 | 546 | full_name=args.cwl_full_name)
|
468 | 547 |
|
469 |
| - |
470 |
| - |
471 | 548 | if loadingContext is None:
|
472 | 549 | loadingContext = LoadingContext(vars(args))
|
473 | 550 | else:
|
@@ -528,13 +605,17 @@ def main(argsl=None, # type: List[str]
|
528 | 605 | tool = make_tool(document_loader, avsc_names,
|
529 | 606 | metadata, uri, loadingContext)
|
530 | 607 | 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) |
| 608 | + def my_represent_none(self, data): # pylint: disable=unused-argument |
| 609 | + """Force clean representation of 'null'.""" |
| 610 | + return self.represent_scalar(u'tag:yaml.org,2002:null', u'null') |
| 611 | + yaml.RoundTripRepresenter.add_representer(type(None), my_represent_none) |
| 612 | + yaml.round_trip_dump( |
| 613 | + generate_input_template(tool), sys.stdout, |
| 614 | + default_flow_style=False, indent=4, block_seq_indent=2) |
534 | 615 | return 0
|
535 | 616 |
|
536 | 617 | if args.validate:
|
537 |
| - _logger.info("Tool definition is valid") |
| 618 | + print("{} is valid CWL.".format(args.workflow)) |
538 | 619 | return 0
|
539 | 620 |
|
540 | 621 | if args.print_rdf:
|
|
0 commit comments