diff --git a/src/runcrate/cli.py b/src/runcrate/cli.py index 46be1c0..d7246d0 100644 --- a/src/runcrate/cli.py +++ b/src/runcrate/cli.py @@ -56,7 +56,19 @@ def cli(): type=click.Path(exists=True, dir_okay=False, readable=True, path_type=Path), help="path to a README file (should be README.md in Markdown format)", ) -def convert(root, output, license, workflow_name, readme): +@click.option( + "-n", + "--crate-name", + metavar="STRING", + help="crate name", +) +@click.option( + "-d", + "--crate-desc", + metavar="STRING", + help="crate description", +) +def convert(root, output, license, workflow_name, readme, crate_name, crate_desc): """\ Convert a CWLProv RO bundle into a Workflow Run RO-Crate. @@ -64,7 +76,7 @@ def convert(root, output, license, workflow_name, readme): """ if not output: output = Path(f"{root.name}.crate.zip") - builder = ProvCrateBuilder(root, workflow_name, license, readme) + builder = ProvCrateBuilder(root, workflow_name, license, readme, crate_name, crate_desc) crate = builder.build() if output.suffix == ".zip": crate.write_zip(output) diff --git a/src/runcrate/convert.py b/src/runcrate/convert.py index 4d02336..88551c9 100644 --- a/src/runcrate/convert.py +++ b/src/runcrate/convert.py @@ -61,7 +61,9 @@ CWLPROV_NONE = "https://w3id.org/cwl/prov#None" +# https://w3id.org/workflowhub/workflow-ro-crate/1.0 WROC_PROFILE_VERSION = "1.0" +DEFAULT_LICENSE = "notspecified" DOCKER_IMG_TYPE = "https://w3id.org/ro/terms/workflow-run#DockerImage" @@ -197,11 +199,14 @@ def get_workflow(wf_path): class ProvCrateBuilder: - def __init__(self, root, workflow_name=None, license=None, readme=None): + def __init__(self, root, workflow_name=None, license=None, readme=None, + crate_name=None, crate_desc=None): self.root = Path(root) self.workflow_name = workflow_name - self.license = license + self.license = license or DEFAULT_LICENSE self.readme = Path(readme) if readme else readme + self.crate_name = crate_name + self.crate_desc = crate_desc self.wf_path = self.root / "workflow" / WORKFLOW_BASENAME self.cwl_defs = get_workflow(self.wf_path) self.step_maps = self._get_step_maps(self.cwl_defs) @@ -290,9 +295,9 @@ def get_dict(self, entity): def build(self): crate = ROCrate(gen_preview=False) crate.metadata.extra_contexts.append(TERMS_NAMESPACE) - self.add_root_metadata(crate) self.add_profiles(crate) self.add_workflow(crate) + self.add_root_metadata(crate) self.add_engine_run(crate) self.add_action(crate, self.workflow_run) self.patch_workflow_input_collection(crate) @@ -301,13 +306,16 @@ def build(self): return crate def add_root_metadata(self, crate): - if self.license: - crate.root_dataset["license"] = self.license + crate.root_dataset["license"] = self.license if self.readme: readme = crate.add_file(self.readme) readme["about"] = crate.root_dataset if self.readme.suffix.lower() == ".md": readme["encodingFormat"] = "text/markdown" + if not self.crate_name: + self.crate_name = f"run of {self.workflow_name}" + crate.root_dataset["name"] = self.crate_name + crate.root_dataset["description"] = self.crate_desc or self.crate_name def add_profiles(self, crate): profiles = [] @@ -338,10 +346,11 @@ def add_workflow(self, crate): lang_version=lang_version, gen_cwl=False, properties=properties ) cwl_workflow = self.cwl_defs[workflow.id] - wf_name = self.wf_path.name + if not self.workflow_name: + self.workflow_name = self.wf_path.name if hasattr(cwl_workflow, "label") and cwl_workflow.label: - wf_name = cwl_workflow.label - workflow["name"] = self.workflow_name or wf_name + self.workflow_name = cwl_workflow.label + workflow["name"] = self.workflow_name if hasattr(cwl_workflow, "doc") and cwl_workflow.doc: workflow["description"] = cwl_workflow.doc # cannot convert "intent" to featureList: workflow is not a SoftwareApplication diff --git a/tests/test_cli.py b/tests/test_cli.py index b522004..b616faa 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -32,7 +32,7 @@ def test_cli_convert(data_dir, tmpdir, monkeypatch): crate_zip = tmpdir / f"{root.name}.crate.zip" assert crate_zip.is_file() crate = ROCrate(crate_zip) - assert not crate.root_dataset.get("license") + assert crate.root_dataset.get("license") == "notspecified" workflow = crate.mainEntity assert workflow["name"] == "packed.cwl" @@ -47,7 +47,7 @@ def test_cli_convert_output(data_dir, tmpdir): assert result.exit_code == 0, result.exception assert crate_zip.is_file() crate = ROCrate(crate_zip) - assert not crate.root_dataset.get("license") + assert crate.root_dataset.get("license") == "notspecified" workflow = crate.mainEntity assert workflow["name"] == "packed.cwl" crate_zip.unlink() @@ -59,7 +59,7 @@ def test_cli_convert_output(data_dir, tmpdir): assert result.exit_code == 0, result.exception assert crate_dir.is_dir() crate = ROCrate(crate_dir) - assert not crate.root_dataset.get("license") + assert crate.root_dataset.get("license") == "notspecified" workflow = crate.mainEntity assert workflow["name"] == "packed.cwl" @@ -102,6 +102,34 @@ def test_cli_convert_workflow_name(data_dir, tmpdir): assert workflow["name"] == workflow_name +def test_cli_convert_crate_name(data_dir, tmpdir): + root = data_dir / "revsort-run-1" + crate_dir = tmpdir / "revsort-run-1-crate" + workflow_name = "RevSort" + crate_name = "name of the crate" + runner = CliRunner() + args = ["convert", str(root), "-o", str(crate_dir), "-w", workflow_name, "-n", crate_name] + result = runner.invoke(cli, args) + assert result.exit_code == 0, result.exception + crate = ROCrate(crate_dir) + assert crate.root_dataset["name"] == crate_name + assert crate.root_dataset["description"] == crate_name + + +def test_cli_convert_crate_desc(data_dir, tmpdir): + root = data_dir / "revsort-run-1" + crate_dir = tmpdir / "revsort-run-1-crate" + workflow_name = "RevSort" + crate_desc = "description of the crate" + runner = CliRunner() + args = ["convert", str(root), "-o", str(crate_dir), "-w", workflow_name, "-d", crate_desc] + result = runner.invoke(cli, args) + assert result.exit_code == 0, result.exception + crate = ROCrate(crate_dir) + assert crate.root_dataset["name"] == f"run of {workflow_name}" + assert crate.root_dataset["description"] == crate_desc + + def test_cli_convert_readme(data_dir, tmpdir): root = data_dir / "revsort-run-1" crate_dir = tmpdir / "revsort-run-1-crate" diff --git a/tests/test_cwlprov_crate_builder.py b/tests/test_cwlprov_crate_builder.py index ec4fe17..b3b74e1 100644 --- a/tests/test_cwlprov_crate_builder.py +++ b/tests/test_cwlprov_crate_builder.py @@ -47,7 +47,9 @@ def test_revsort(data_dir, tmpdir): assert readme_f["about"] is crate.root_dataset workflow = crate.mainEntity assert workflow.id == "packed.cwl" - assert workflow["name"] == "RevSort" + assert workflow["name"] == workflow_name + assert crate.root_dataset["name"] == f"run of {workflow_name}" + assert crate.root_dataset["description"] == f"run of {workflow_name}" tools = workflow["hasPart"] assert len(tools) == 2 for entity in tools: @@ -1191,3 +1193,24 @@ def test_agent_no_name(data_dir, tmpdir): assert agent.type == "Person" assert agent.id == "https://orcid.org/0000-0001-9447-460X" assert "name" not in agent + + +def test_no_license(data_dir): + root = data_dir / "revsort-run-1" + builder = ProvCrateBuilder(root) + crate = builder.build() + assert crate.root_dataset["license"] == "notspecified" + + +def test_name_desc(data_dir): + root = data_dir / "revsort-run-1" + workflow_name = "RevSort" + crate_name = "name of the crate" + builder = ProvCrateBuilder(root, workflow_name=workflow_name, crate_name=crate_name) + crate = builder.build() + assert crate.root_dataset["name"] == crate_name + assert crate.root_dataset["description"] == crate_name + crate_desc = "description of the crate" + builder = ProvCrateBuilder(root, workflow_name=workflow_name, crate_name=crate_name, crate_desc=crate_desc) + crate = builder.build() + assert crate.root_dataset["description"] == crate_desc