From 8e4f944097c0f0d076b1d85e00f87eb2ac7caec5 Mon Sep 17 00:00:00 2001 From: "Michael R. Crusoe" Date: Fri, 29 Nov 2024 16:16:31 +0100 Subject: [PATCH] initial implementation of parameter groups --- cwltool/argparser.py | 41 +++++++++++++++++++++++++++++++------ cwltool/extensions-v1.2.yml | 32 +++++++++++++++++++++++++++++ tests/echo-groups.cwl | 34 ++++++++++++++++++++++++++++++ tests/test_ext.py | 13 ++++++++++++ 4 files changed, 114 insertions(+), 6 deletions(-) create mode 100755 tests/echo-groups.cwl diff --git a/cwltool/argparser.py b/cwltool/argparser.py index 7b3125d94..587e4f50e 100644 --- a/cwltool/argparser.py +++ b/cwltool/argparser.py @@ -10,7 +10,7 @@ from .process import Process, shortname from .resolver import ga4gh_tool_registries from .software_requirements import SOFTWARE_REQUIREMENTS_ENABLED -from .utils import DEFAULT_TMP_PREFIX +from .utils import DEFAULT_TMP_PREFIX, CWLObjectType def arg_parser() -> argparse.ArgumentParser: @@ -845,7 +845,7 @@ def __call__( def add_argument( - toolparser: argparse.ArgumentParser, + toolparser: Union[argparse.ArgumentParser, "argparse._ArgumentGroup"], name: str, inptype: Any, records: list[str], @@ -962,14 +962,18 @@ def generate_parser( toolparser.add_argument("job_order", nargs="?", help="Job input json file") namemap["job_order"] = "job_order" - for inp in tool.tool["inputs"]: - name = shortname(inp["id"]) + inps = tool.tool["inputs"] + + def process_input( + inp: CWLObjectType, parser: Union[argparse.ArgumentParser, "argparse._ArgumentGroup"] + ) -> None: + name = shortname(cast(str, inp["id"])) namemap[name.replace("-", "_")] = name inptype = inp["type"] - description = inp.get("doc", inp.get("label", "")) + description = cast(str, inp.get("doc", inp.get("label", ""))) default = inp.get("default", None) add_argument( - toolparser, + parser, name, inptype, records, @@ -980,4 +984,29 @@ def generate_parser( base_uri, ) + if (groups_req := tool.get_requirement("http://commonwl.org/cwltool#Groups")[0]) is not None: + groups = cast(CWLObjectType, groups_req["groups"]) + for group_name in groups.keys(): + group_inputs: list[CWLObjectType] = [] + group_definition = cast(CWLObjectType, groups[group_name]) + for input_name in cast(list[str], group_definition["groupMembers"]): + new_inps: list[CWLObjectType] = [] + for inp in inps: + if shortname(inp["id"]) == input_name: + group_inputs.append(inp) + else: + new_inps.append(inp) + inps = new_inps + + if len(group_inputs) > 0: + group = toolparser.add_argument_group( + title=cast(str, group_definition.get("label", group_name)), + description=cast(Optional[str], group_definition.get("doc", None)), + ) + for inp in group_inputs: + process_input(inp, group) + + for inp in inps: + process_input(inp, toolparser) + return toolparser diff --git a/cwltool/extensions-v1.2.yml b/cwltool/extensions-v1.2.yml index c39b15d07..ea437b913 100644 --- a/cwltool/extensions-v1.2.yml +++ b/cwltool/extensions-v1.2.yml @@ -240,6 +240,7 @@ $graph: - Specify the desired method of dealing with loop outputs - Default. Propagates only the last computed element to the subsequent steps when the loop terminates. - Propagates a single array with all output values to the subsequent steps when the loop terminates. + - name: ShmSize type: record extends: cwl:ProcessRequirement @@ -258,3 +259,34 @@ $graph: than 0. Unit is optional and can be `b` (bytes), `k` (kilobytes), `m` (megabytes), or `g` (gigabytes). If you omit the unit, the default is bytes. If you omit the size entirely, the value is `64m`." + +- name: Grouping + type: record + extends: [ sld:Documented ] + fields: + - name: GroupName + type: string + - name: groupMembers + type: string[] + jsonldPredicate: + "_type": "@id" + "_container": "@list" + refScope: 0 + +- name: Groups + type: record + extends: cwl:ProcessRequirement + inVocab: false + fields: + class: + type: string + doc: 'cwltool:Groups' + jsonldPredicate: + "_id": "@type" + "_type": "@vocab" + group: + type: + - type: array + items: "#Grouping" + jsonldPredicate: + mapSubject: GroupName diff --git a/tests/echo-groups.cwl b/tests/echo-groups.cwl new file mode 100755 index 000000000..affbf099a --- /dev/null +++ b/tests/echo-groups.cwl @@ -0,0 +1,34 @@ +#!/usr/bin/env cwl-runner +cwlVersion: v1.0 +class: CommandLineTool +inputs: + first: + type: string + inputBinding: {} + second: + type: string + inputBinding: {} + third: + type: string + label: groupless +outputs: + - id: out + type: string + outputBinding: + glob: out.txt + loadContents: true + outputEval: $(self[0].contents) +baseCommand: echo +stdout: out.txt + + +$namespaces: + cwltool: "http://commonwl.org/cwltool#" + +hints: + cwltool:Groups: + groups: + my_groups: + groupMembers: [first, second] + label: my great inputs + doc: "parameters related to the foobar feature" diff --git a/tests/test_ext.py b/tests/test_ext.py index 0d2665ca1..064024e5e 100644 --- a/tests/test_ext.py +++ b/tests/test_ext.py @@ -285,3 +285,16 @@ def test_ext_validation_no_namespace_warning() -> None: "URI prefix 'cwltool' of 'cwltool:loop' not recognized, are you " "missing a $namespaces section?" ) not in stderr + + +def test_ext_groups_help(capsys: pytest.CaptureFixture[str]) -> None: + error_code, stdout, stderr = get_main_output([get_data("tests/echo-groups.cwl"), "--help"]) + assert error_code == 0 + assert ( + """my great inputs: + parameters related to the foobar feature + + --first FIRST + --second SECOND""" + in capsys.readouterr().out + )