Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 194cb8d0f1 | |||
| b90bd3453a | |||
| b5f42c8055 | |||
| cd1815354d | |||
| e9dce89f32 | |||
| 0cbd5c00cb |
42
CHANGELOG.md
42
CHANGELOG.md
@@ -1,3 +1,45 @@
|
||||
# demultiplex v0.3.11
|
||||
|
||||
# New features
|
||||
|
||||
* Output demultiplexer logs and metrics (PR #41).
|
||||
|
||||
# demultiplex v0.3.10
|
||||
|
||||
## Minor changes
|
||||
|
||||
* Moved the test resources to their new location (PR #37).
|
||||
|
||||
# demultiplex v0.3.9
|
||||
|
||||
## Bug fixes
|
||||
|
||||
* Fix defaults for output arguments in nextflow schema's.
|
||||
|
||||
* Fix an issue where an integer being passed to a argument with `type: double` resulted in an error (PR #44).
|
||||
|
||||
## Minor changes
|
||||
|
||||
* Bump viash to 0.9.4, which adds support for nextflow versions starting major version 25.01 (PR #43 and #44).
|
||||
|
||||
# demultiplex v0.3.8
|
||||
|
||||
## Bug fixes
|
||||
|
||||
* Provide a proper error when a FASTQ file is empty after demultiplexing (PR #40).
|
||||
|
||||
# demultiplex v0.3.7
|
||||
|
||||
## Minor updates
|
||||
|
||||
* Ignore lines starting with '#' when parsing run information CSV (PR #39).
|
||||
|
||||
# demultiplex v0.3.6
|
||||
|
||||
## Minor updates
|
||||
|
||||
* Allow letter case variants for headers when looking for sample information in run information CSV (PR #38).
|
||||
|
||||
# demultiplex v0.3.5
|
||||
|
||||
## Breaking changes
|
||||
|
||||
235
README.md
235
README.md
@@ -1,6 +1,10 @@
|
||||
|
||||
|
||||
# Demultiplex.vsh
|
||||
|
||||
Demultiplex.vsh is a workflow for demultiplexing of raw sequencing data. Currently data from Illumina and Element Biosciences sequencers are supported.
|
||||
Demultiplex.vsh is a workflow for demultiplexing of raw sequencing data.
|
||||
Currently data from Illumina and Element Biosciences sequencers are
|
||||
supported.
|
||||
|
||||
[](https://web.viash-hub.com/packages/demultiplex)
|
||||
[](https://github.com/viash-hub/demultiplex)
|
||||
@@ -9,92 +13,207 @@ License](https://img.shields.io/github/license/viash-hub/demultiplex.svg)](https
|
||||
[](https://github.com/viash-hub/demultiplex/issues)
|
||||
[](https://viash.io)
|
||||
version](https://img.shields.io/badge/Viash-v0.9.4-blue)](https://viash.io)
|
||||
|
||||
## Introcuction
|
||||
|
||||
This workflow is designed to demultiplex raw RNA-seq sequencing data
|
||||
from Illumina and Element Biosciences sequencers.
|
||||
|
||||
The workflow is built in a modular fashion, where most of the base
|
||||
functionality is provided by components from
|
||||
[`biobox`](https://www.viash-hub.com/packages/biobox/latest)
|
||||
supplemented by custom base components and workflow components in this
|
||||
package. Each of these components can be used independently as
|
||||
stand-alone modules with a standardized interface.
|
||||
|
||||
The full workflow can be run in two ways:
|
||||
|
||||
1. Run the [main
|
||||
workflow](https://www.viash-hub.com/packages/demultiplex/v0.3.4/components/demultiplex)
|
||||
containing the main functionality.
|
||||
2. Run the [(opinianated)
|
||||
`runner`](https://www.viash-hub.com/packages/demultiplex/v0.3.4/components/runner)
|
||||
where a number of choices (input/output structure and location) have
|
||||
been made.
|
||||
|
||||
## Workflow Overview
|
||||
The workflow executes the following steps:
|
||||
1. Unpacking the input data (when a TAR archive is provided)
|
||||
2. Run `bclconvert` or `bases2fastq`
|
||||
3. Run `falco` and convert Illumina InterOp information to csv
|
||||
4. Run `multiqc` to generate a report
|
||||
|
||||
## Usage
|
||||
The workflow executes the following steps:
|
||||
|
||||
Two variants of the same workflow are provided, depending on the flexibility in the ouput structure required:
|
||||
1. Unpacking the input data (when a TAR archive is provided)
|
||||
2. Run `bclconvert` or `bases2fastq`
|
||||
3. Run `falco` and convert Illumina InterOp information to csv
|
||||
4. Run `multiqc` to generate a report
|
||||
|
||||
* The `runner` workflow provides a predifined output structure. It requires the minimal amount of parameters to be provided, at the cost of being less flexible. It is located at `target/nextflow/runner/main.nf`
|
||||
* The `demultiplex` workflow (`target/nextflow/demultiplex/main.nf`) allows for more fine-grained tuning, but required more parameters to be provided.
|
||||
## Example usage
|
||||
|
||||
Two variants of the same workflow are provided, depending on the
|
||||
flexibility in the ouput structure required:
|
||||
|
||||
- The `runner` workflow provides a predifined output structure. It
|
||||
requires the minimal amount of parameters to be provided, at the cost
|
||||
of being less flexible. It is located at
|
||||
`target/nextflow/runner/main.nf`
|
||||
- The `demultiplex` workflow (`target/nextflow/demultiplex/main.nf`)
|
||||
allows for more fine-grained tuning, but required more parameters to
|
||||
be provided.
|
||||
|
||||
### Test data
|
||||
|
||||
We have provided test data at `gs://viash-hub-test-data/demultiplex/v3/demultiplex_htrnaseq_meta/SingleCell-RNA_P3_2`, but please feel free to bring your own. The URL of the test data can be provided as-is to the workflow, or you can download everything and specify a local path.
|
||||
We have provided test data at
|
||||
`gs://viash-hub-resources/demultiplex/v3/demultiplex_htrnaseq_meta/SingleCell-RNA_P3_2`
|
||||
(Illumina), but please feel free to bring your own. The URL of the test
|
||||
data can be provided as-is to the workflow, or you can download
|
||||
everything and specify a local path.
|
||||
|
||||
The input data should follow the structure of either Illumina or Element
|
||||
Biosciences sequencers. The workflow will automatically detect which
|
||||
demultiplexer to use (`bclconvert` or `bases2fastq`) based on the
|
||||
presence of either `SampleSheet.csv` or `RunParameters.xml` in the input
|
||||
directory. Demultiplexer can also be set explicitly using the
|
||||
`--demultiplexer` parameter.
|
||||
|
||||
### Setup
|
||||
|
||||
In order to use the workflows in this package, you'll need to do the following:
|
||||
* Install [nextflow](https://www.nextflow.io/docs/latest/install.html)
|
||||
* Install a nextflow compatible executor. This workflow provides a profile for [docker](https://docs.docker.com/get-started/).
|
||||
In order to use the workflows in this package, you’ll need to do the
|
||||
following:
|
||||
|
||||
- Install [nextflow](https://www.nextflow.io/docs/latest/install.html)
|
||||
- Install a nextflow compatible executor. This workflow provides a
|
||||
profile for [docker](https://docs.docker.com/get-started/).
|
||||
|
||||
### Run from Viash Hub
|
||||
|
||||
1. Open [Viash Hub](https://www.viash-hub.com) and browse to the
|
||||
[demultiplex
|
||||
component](https://www.viash-hub.com/packages/demultiplex/v0.3.4/components/demultiplex).
|
||||
Press the ‘Launch’ button and follow the instructions.
|
||||
|
||||

|
||||
|
||||
2. We will start an example run and set profile to `docker`.
|
||||
|
||||

|
||||
|
||||
3. In the next step, we provide the paramters as follows and leave the
|
||||
rest as defalut:
|
||||
|
||||
- `input`:
|
||||
`gs://viash-hub-resources/demultiplex/v3/demultiplex_htrnaseq_meta/SingleCell-RNA_P3_2`
|
||||
|
||||

|
||||
|
||||
Press the ‘Launch’ button at the end to get the instructions on how to
|
||||
run the workflow from the CLI.
|
||||
|
||||
### Run using NF-Tower / Seqera Cloud
|
||||
|
||||
It’s possible to run the workflow directly from [Seqera
|
||||
Cloud](https://cloud.seqera.io). The necessary [Nextflow schema
|
||||
file](https://nextflow-io.github.io/nf-schema/latest/nextflow_schema/nextflow_schema_specification/)
|
||||
has been built and provided with the workflows in order to use the
|
||||
form-based input.
|
||||
|
||||
1. Select the option to run the workflow using Seqera Cloud. You will
|
||||
need to create an API token for your account. Once this token is
|
||||
filled in in the corresponding field, we will get the option to
|
||||
select a ‘Workspace’ and a ‘Compute environment’.
|
||||
|
||||

|
||||
|
||||
2. Provide the parameters similar to the previous step.
|
||||
|
||||
3. In the next screen, pressing the ‘Launch’ button will actually start
|
||||
the workflow on Seqera Cloud. A message is shown when the submit was
|
||||
successful.
|
||||
|
||||

|
||||
|
||||
### Setting up SCM
|
||||
|
||||
In order to let nextflow use the viash-hub workflows, you need to setup a [SCM](https://www.nextflow.io/docs/latest/git.html#git-configuration) file. This can be done once by creating `$HOME/.nextflow/scm` and adding the following:
|
||||
```
|
||||
providers {
|
||||
vsh {
|
||||
platform = 'gitlab'
|
||||
server = "packages.viash-hub.com"
|
||||
}
|
||||
}
|
||||
```
|
||||
In order to let nextflow use the viash-hub workflows, you need to setup
|
||||
a [SCM](https://www.nextflow.io/docs/latest/git.html#git-configuration)
|
||||
file. This can be done once by creating `$HOME/.nextflow/scm` and adding
|
||||
the following:
|
||||
|
||||
Alternatively, a custom location for the SCM file can be specified using the `NXF_SCM_FILE` environment variable.
|
||||
providers {
|
||||
vsh {
|
||||
platform = 'gitlab'
|
||||
server = "packages.viash-hub.com"
|
||||
}
|
||||
}
|
||||
|
||||
You can check if everything is working by getting the `--help` for a workflow:
|
||||
```bash
|
||||
Alternatively, a custom location for the SCM file can be specified using
|
||||
the `NXF_SCM_FILE` environment variable.
|
||||
|
||||
You can check if everything is working by getting the `--help` for a
|
||||
workflow:
|
||||
|
||||
``` bash
|
||||
nextflow run \
|
||||
vsh/demultiplex \
|
||||
-r v0.3.4 \
|
||||
-r v0.3.11 \
|
||||
--help
|
||||
```
|
||||
|
||||
### Run from the CLI
|
||||
|
||||
Running from the CLI directly without using Viash hub is possible as
|
||||
well. The easiest is to use the integrated help functionality, for
|
||||
instance using the following:
|
||||
|
||||
``` bash
|
||||
nextflow run vsh/demultiplex \
|
||||
-revision v0.3.11 \
|
||||
-main-script target/nextflow/workflows/runner/main.nf \
|
||||
--help
|
||||
```
|
||||
|
||||
Having this project available locally, you can run the following
|
||||
command:
|
||||
|
||||
``` bash
|
||||
nextflow run vsh/demultiplex \
|
||||
-r v0.3.11 \
|
||||
-main-script target/nextflow/runner/main.nf \
|
||||
--input "gs://viash-hub-resources/demultiplex/v3/demultiplex_htrnaseq_meta/SingleCell-RNA_P3_2" \
|
||||
--demultiplexer bclconvert \
|
||||
--skip_copycomplete_check \
|
||||
--publish_dir example_output/ \
|
||||
-profile docker \
|
||||
-c src/config/labels.config
|
||||
```
|
||||
|
||||
### (Optional) Resource usage tuning
|
||||
|
||||
Nextflow's labels can be used to specify the amount of resources a process can use. This workflow uses the following labels for CPU and memory:
|
||||
* `verylowmem`, `lowmem`, `midmem`, `highmem`
|
||||
* `verylowcpu`, `lowcpu`, `midcpu`, `highcpu`
|
||||
Nextflow’s labels can be used to specify the amount of resources a
|
||||
process can use. This workflow uses the following labels for CPU and
|
||||
memory:
|
||||
|
||||
The defaults for these labels can be found at `src/config/labels.config`. Nextflow checks that the specified resources for a process do not exceed what is available on the machine and will not start if it does. Create your own config file to tune the labels to your needs, for example:
|
||||
- `verylowmem`, `lowmem`, `midmem`, `highmem`
|
||||
- `verylowcpu`, `lowcpu`, `midcpu`, `highcpu`
|
||||
|
||||
```
|
||||
// Resource labels
|
||||
withLabel: verylowcpu { cpus = 2 }
|
||||
withLabel: lowcpu { cpus = 8 }
|
||||
withLabel: midcpu { cpus = 16 }
|
||||
withLabel: highcpu { cpus = 16 }
|
||||
The defaults for these labels can be found at
|
||||
`src/config/labels.config`. Nextflow checks that the specified resources
|
||||
for a process do not exceed what is available on the machine and will
|
||||
not start if it does. Create your own config file to tune the labels to
|
||||
your needs, for example:
|
||||
|
||||
withLabel: verylowmem { memory = 4.GB }
|
||||
withLabel: lowmem { memory = 8.GB }
|
||||
withLabel: midmem { memory = 8.GB }
|
||||
withLabel: highmem { memory = 8.GB }
|
||||
```
|
||||
// Resource labels
|
||||
withLabel: verylowcpu { cpus = 2 }
|
||||
withLabel: lowcpu { cpus = 8 }
|
||||
withLabel: midcpu { cpus = 16 }
|
||||
withLabel: highcpu { cpus = 16 }
|
||||
|
||||
When starting nextflow using the CLI, you can use `-c` to provide the file to nextflow and overwrite the defaults.
|
||||
withLabel: verylowmem { memory = 4.GB }
|
||||
withLabel: lowmem { memory = 8.GB }
|
||||
withLabel: midmem { memory = 8.GB }
|
||||
withLabel: highmem { memory = 8.GB }
|
||||
|
||||
### Example
|
||||
|
||||
```bash
|
||||
nextflow run vsh/demultiplex \
|
||||
-r v0.3.4 \
|
||||
-main-script target/nextflow/runner/main.nf \
|
||||
--input "gs://viash-hub-test-data/demultiplex/v3/demultiplex_htrnaseq_meta/SingleCell-RNA_P3_2" \
|
||||
--demultiplexer bclconvert \
|
||||
--publish_dir example_output/ \
|
||||
-profile docker \
|
||||
-c labels.config
|
||||
```
|
||||
When starting nextflow using the CLI, you can use `-c` to provide the
|
||||
file to nextflow and overwrite the defaults.
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
Developed in collaboration with Data Intuitive and Open Analytics.
|
||||
|
||||
|
||||
|
||||
191
README.qmd
Normal file
191
README.qmd
Normal file
@@ -0,0 +1,191 @@
|
||||
---
|
||||
format: gfm
|
||||
---
|
||||
```{r setup, include=FALSE}
|
||||
project <- yaml::read_yaml("_viash.yaml")
|
||||
license <- paste0(project$links$repository, "/blob/main/LICENSE")
|
||||
```
|
||||
|
||||
# Demultiplex.vsh
|
||||
|
||||
Demultiplex.vsh is a workflow for demultiplexing of raw sequencing data. Currently data from Illumina and Element Biosciences sequencers are supported.
|
||||
|
||||
[](https://web.viash-hub.com/packages/demultiplex)
|
||||
[](https://github.com/viash-hub/demultiplex)
|
||||
[](https://github.com/viash-hub/demultiplex/blob/main/LICENSE)
|
||||
[](https://github.com/viash-hub/demultiplex/issues)
|
||||
[](https://viash.io)
|
||||
|
||||
## Introcuction
|
||||
This workflow is designed to demultiplex raw RNA-seq sequencing data from Illumina and Element Biosciences sequencers.
|
||||
|
||||
The workflow is built in a modular fashion, where most of the base functionality is provided by components from
|
||||
[`biobox`](https://www.viash-hub.com/packages/biobox/latest) supplemented by custom base components and workflow components in this package. Each of these components can be used independently as stand-alone modules with a
|
||||
standardized interface.
|
||||
|
||||
The full workflow can be run in two ways:
|
||||
|
||||
1. Run the [main
|
||||
workflow](https://www.viash-hub.com/packages/demultiplex/v0.3.4/components/demultiplex)
|
||||
containing the main functionality.
|
||||
2. Run the [(opinianated)
|
||||
`runner`](https://www.viash-hub.com/packages/demultiplex/v0.3.4/components/runner)
|
||||
where a number of choices (input/output structure and location) have
|
||||
been made.
|
||||
|
||||
## Workflow Overview
|
||||
|
||||
The workflow executes the following steps:
|
||||
|
||||
1. Unpacking the input data (when a TAR archive is provided)
|
||||
2. Run `bclconvert` or `bases2fastq`
|
||||
3. Run `falco` and convert Illumina InterOp information to csv
|
||||
4. Run `multiqc` to generate a report
|
||||
|
||||
## Example usage
|
||||
|
||||
Two variants of the same workflow are provided, depending on the flexibility in the ouput structure required:
|
||||
|
||||
* The `runner` workflow provides a predifined output structure. It requires the minimal amount of parameters to be provided, at the cost of being less flexible. It is located at `target/nextflow/runner/main.nf`
|
||||
* The `demultiplex` workflow (`target/nextflow/demultiplex/main.nf`) allows for more fine-grained tuning, but required more parameters to be provided.
|
||||
|
||||
### Test data
|
||||
|
||||
We have provided test data at `gs://viash-hub-resources/demultiplex/v3/demultiplex_htrnaseq_meta/SingleCell-RNA_P3_2` (Illumina), but please feel free to bring your own. The URL of the test data can be provided as-is to the workflow, or you can download everything and specify a local path.
|
||||
|
||||
The input data should follow the structure of either Illumina or Element Biosciences sequencers. The workflow will automatically detect which demultiplexer to use (`bclconvert` or `bases2fastq`) based on the
|
||||
presence of either `SampleSheet.csv` or `RunParameters.xml` in the input directory. Demultiplexer can also be set explicitly using the `--demultiplexer` parameter.
|
||||
|
||||
### Setup
|
||||
|
||||
In order to use the workflows in this package, you'll need to do the following:
|
||||
|
||||
* Install [nextflow](https://www.nextflow.io/docs/latest/install.html)
|
||||
* Install a nextflow compatible executor. This workflow provides a profile for [docker](https://docs.docker.com/get-started/).
|
||||
|
||||
### Run from Viash Hub
|
||||
|
||||
1. Open [Viash Hub](https://www.viash-hub.com) and browse to the [demultiplex
|
||||
component](https://www.viash-hub.com/packages/demultiplex/v0.3.4/components/demultiplex).
|
||||
Press the ‘Launch’ button and follow the instructions.
|
||||
|
||||

|
||||
|
||||
2. We will start an example run and set profile to `docker`.
|
||||
|
||||

|
||||
|
||||
3. In the next step, we provide the paramters as follows and leave the rest as defalut:
|
||||
|
||||
- `input`:
|
||||
`gs://viash-hub-resources/demultiplex/v3/demultiplex_htrnaseq_meta/SingleCell-RNA_P3_2`
|
||||
|
||||

|
||||
|
||||
Press the ‘Launch’ button at the end to get the instructions on how to
|
||||
run the workflow from the CLI.
|
||||
|
||||
### Run using NF-Tower / Seqera Cloud
|
||||
|
||||
It’s possible to run the workflow directly from [Seqera
|
||||
Cloud](https://cloud.seqera.io). The necessary [Nextflow schema
|
||||
file](https://nextflow-io.github.io/nf-schema/latest/nextflow_schema/nextflow_schema_specification/)
|
||||
has been built and provided with the workflows in order to use the
|
||||
form-based input.
|
||||
|
||||
1. Select the option to run the workflow using Seqera Cloud. You
|
||||
will need to create an API token for your account. Once this token is
|
||||
filled in in the corresponding field, we will get the option to select
|
||||
a ‘Workspace’ and a ‘Compute environment’.
|
||||
|
||||

|
||||
|
||||
2. Provide the parameters similar to the previous step.
|
||||
|
||||
3. In the next screen, pressing the ‘Launch’ button will actually start the
|
||||
workflow on Seqera Cloud. A message is shown when the submit was
|
||||
successful.
|
||||
|
||||

|
||||
|
||||
### Setting up SCM
|
||||
|
||||
In order to let nextflow use the viash-hub workflows, you need to setup a [SCM](https://www.nextflow.io/docs/latest/git.html#git-configuration) file. This can be done once by creating `$HOME/.nextflow/scm` and adding the following:
|
||||
```
|
||||
providers {
|
||||
vsh {
|
||||
platform = 'gitlab'
|
||||
server = "packages.viash-hub.com"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively, a custom location for the SCM file can be specified using the `NXF_SCM_FILE` environment variable.
|
||||
|
||||
You can check if everything is working by getting the `--help` for a workflow:
|
||||
```bash
|
||||
nextflow run \
|
||||
vsh/demultiplex \
|
||||
-r v0.3.11 \
|
||||
--help
|
||||
```
|
||||
|
||||
### Run from the CLI
|
||||
|
||||
Running from the CLI directly without using Viash hub is possible as well. The
|
||||
easiest is to use the integrated help functionality, for instance
|
||||
using the following:
|
||||
|
||||
``` bash
|
||||
nextflow run vsh/demultiplex \
|
||||
-revision v0.3.11 \
|
||||
-main-script target/nextflow/workflows/runner/main.nf \
|
||||
--help
|
||||
```
|
||||
|
||||
Having this project available locally, you can run the following command:
|
||||
|
||||
```bash
|
||||
nextflow run vsh/demultiplex \
|
||||
-r v0.3.11 \
|
||||
-main-script target/nextflow/runner/main.nf \
|
||||
--input "gs://viash-hub-resources/demultiplex/v3/demultiplex_htrnaseq_meta/SingleCell-RNA_P3_2" \
|
||||
--demultiplexer bclconvert \
|
||||
--skip_copycomplete_check \
|
||||
--publish_dir example_output/ \
|
||||
-profile docker \
|
||||
-c src/config/labels.config
|
||||
```
|
||||
|
||||
### (Optional) Resource usage tuning
|
||||
|
||||
Nextflow's labels can be used to specify the amount of resources a process can use. This workflow uses the following labels for CPU and memory:
|
||||
|
||||
* `verylowmem`, `lowmem`, `midmem`, `highmem`
|
||||
* `verylowcpu`, `lowcpu`, `midcpu`, `highcpu`
|
||||
|
||||
The defaults for these labels can be found at `src/config/labels.config`. Nextflow checks that the specified resources for a process do not exceed what is available on the machine and will not start if it does. Create your own config file to tune the labels to your needs, for example:
|
||||
|
||||
```
|
||||
// Resource labels
|
||||
withLabel: verylowcpu { cpus = 2 }
|
||||
withLabel: lowcpu { cpus = 8 }
|
||||
withLabel: midcpu { cpus = 16 }
|
||||
withLabel: highcpu { cpus = 16 }
|
||||
|
||||
withLabel: verylowmem { memory = 4.GB }
|
||||
withLabel: lowmem { memory = 8.GB }
|
||||
withLabel: midmem { memory = 8.GB }
|
||||
withLabel: highmem { memory = 8.GB }
|
||||
```
|
||||
|
||||
When starting nextflow using the CLI, you can use `-c` to provide the file to nextflow and overwrite the defaults.
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
Developed in collaboration with Data Intuitive and Open Analytics.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: demultiplex
|
||||
version: v0.3.5
|
||||
version: v0.3.11
|
||||
description: |
|
||||
Demultiplexing pipeline
|
||||
license: MIT
|
||||
@@ -9,13 +9,13 @@ links:
|
||||
repository: https://github.com/viash-hub/demultiplex
|
||||
info:
|
||||
test_resources:
|
||||
- path: gs://viash-hub-test-data/demultiplex/v2/
|
||||
- path: gs://viash-hub-resources/demultiplex/v3
|
||||
dest: testData
|
||||
|
||||
viash_version: 0.9.0
|
||||
viash_version: 0.9.4
|
||||
|
||||
config_mods: |
|
||||
.requirements.commands := ['ps']
|
||||
.requirements.commands += ['ps']
|
||||
.runners[.type == 'nextflow'].directives.tag := '$id'
|
||||
.resources += {path: '/src/config/labels.config', dest: 'nextflow_labels.config'}
|
||||
.runners[.type == 'nextflow'].config.script := 'includeConfig("nextflow_labels.config")'
|
||||
|
||||
BIN
assets/demultiplex-launch-parameters-1.png
Normal file
BIN
assets/demultiplex-launch-parameters-1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 59 KiB |
BIN
assets/demultiplex-launch-parameters-2.png
Normal file
BIN
assets/demultiplex-launch-parameters-2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
BIN
assets/demultiplex-launch-parameters-3.png
Normal file
BIN
assets/demultiplex-launch-parameters-3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 110 KiB |
BIN
assets/demultiplex-launch-parameters-4.png
Normal file
BIN
assets/demultiplex-launch-parameters-4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
BIN
assets/demultiplex-launch-small.png
Normal file
BIN
assets/demultiplex-launch-small.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
@@ -1,3 +1,26 @@
|
||||
import java.util.zip.GZIPInputStream
|
||||
import java.nio.file.Files
|
||||
import java.io.BufferedInputStream
|
||||
|
||||
def is_empty(file_to_check){
|
||||
/*
|
||||
Checks if a file has content
|
||||
*/
|
||||
if (file_to_check.size() == 0) {
|
||||
return true
|
||||
}
|
||||
def input_stream = Files.newInputStream(file_to_check)
|
||||
def gzInputStream
|
||||
try {
|
||||
gzInputStream = new GZIPInputStream(new BufferedInputStream(input_stream))
|
||||
} catch (java.io.EOFException ex) {
|
||||
// This is not a gzipfile...
|
||||
return false
|
||||
}
|
||||
def read_one_byte = gzInputStream.read()
|
||||
return read_one_byte == -1
|
||||
}
|
||||
|
||||
workflow run_wf {
|
||||
take:
|
||||
input_ch
|
||||
@@ -17,8 +40,8 @@ workflow run_wf {
|
||||
println "Processing run information file ${sample_sheet}"
|
||||
csv_lines = sample_sheet.splitCsv(header: false, sep: ',')
|
||||
csv_lines.any { csv_items ->
|
||||
if (csv_items.isEmpty()) {
|
||||
// skip empty line
|
||||
if (csv_items.isEmpty() || csv_items[0].startsWith("#")) {
|
||||
// skip empty or commented line
|
||||
return
|
||||
}
|
||||
def possible_header = csv_items[0]
|
||||
@@ -30,8 +53,8 @@ workflow run_wf {
|
||||
return true
|
||||
}
|
||||
// [Data], [BCLConvert_Data] for illumina
|
||||
// [Samples] for Element Biosciences
|
||||
if (header in ["Data", "Samples", "BCLConvert_Data"]) {
|
||||
// [Samples] or sometimes [SAMPLES] for Element Biosciences
|
||||
if (header.toLowerCase() in ["data", "samples", "bclconvert_data"]) {
|
||||
println "Found header [${header}], start parsing."
|
||||
start_parsing = true
|
||||
return
|
||||
@@ -78,6 +101,9 @@ workflow run_wf {
|
||||
"Found forward: ${forward_fastq} and reverse: ${reverse_fastq}."
|
||||
println "Found ${forward_fastq.size()} forward and ${reverse_fastq.size()} reverse " +
|
||||
"fastq files for sample ${sample_id}"
|
||||
|
||||
assert forward_fastq.every{!is_empty(it)} && reverse_fastq.every{!is_empty(it)}:
|
||||
"A fastq file for sample '${sample_id}' appears to be empty!"
|
||||
def fastqs_state = [
|
||||
"fastq_forward": forward_fastq,
|
||||
"fastq_reverse": reverse_fastq,
|
||||
|
||||
@@ -54,6 +54,11 @@ argument_groups:
|
||||
direction: "output"
|
||||
required: true
|
||||
default: "$id/run_information.csv"
|
||||
- name: "--demultiplexer_logs"
|
||||
type: file
|
||||
direction: output
|
||||
required: true
|
||||
default: "$id/demultiplexer_logs"
|
||||
- name: "Other arguments"
|
||||
arguments:
|
||||
- name: --skip_copycomplete_check
|
||||
|
||||
@@ -124,14 +124,15 @@ workflow run_wf {
|
||||
bcl_input_directory: state.input,
|
||||
sample_sheet: state.run_information,
|
||||
output_directory: state.output,
|
||||
reports: "reports",
|
||||
logs: "logs"
|
||||
reports: state.demultiplexer_logs,
|
||||
logs: state.demultiplexer_logs,
|
||||
]
|
||||
},
|
||||
toState: {id, result, state ->
|
||||
def toAdd = [
|
||||
"output_demultiplexer" : result.output_directory,
|
||||
"run_id": id,
|
||||
"demultiplexer_logs": result.reports,
|
||||
]
|
||||
def newState = state + toAdd
|
||||
return newState
|
||||
@@ -141,11 +142,15 @@ workflow run_wf {
|
||||
| bases2fastq.run(
|
||||
runIf: {id, state -> state.demultiplexer in ["bases2fastq"]},
|
||||
directives: [label: ["highmem", "midcpu"]],
|
||||
fromState: [
|
||||
"analysis_directory": "input",
|
||||
"run_manifest": "run_information",
|
||||
"output_directory": "output",
|
||||
],
|
||||
fromState: { id, state ->
|
||||
[
|
||||
"analysis_directory": state.input,
|
||||
"run_manifest": state.run_information,
|
||||
"output_directory": state.output,
|
||||
"report": state.demultiplexer_logs + "/report.html",
|
||||
"logs": state.demultiplexer_logs,
|
||||
]
|
||||
},
|
||||
args: [
|
||||
"no_projects": true, // Do not put output files in a subfolder for project
|
||||
//"split_lanes": true,
|
||||
@@ -156,6 +161,8 @@ workflow run_wf {
|
||||
def toAdd = [
|
||||
"output_demultiplexer" : result.output_directory,
|
||||
"run_id": id,
|
||||
"demultiplexer_logs": result.logs,
|
||||
|
||||
]
|
||||
def newState = state + toAdd
|
||||
return newState
|
||||
@@ -225,6 +232,7 @@ workflow run_wf {
|
||||
state + [ "output_multiqc" : result.output_report ]
|
||||
}
|
||||
)
|
||||
|
||||
| setState(
|
||||
[
|
||||
//"_meta": "_meta",
|
||||
@@ -232,6 +240,7 @@ workflow run_wf {
|
||||
"output_falco": "output_falco",
|
||||
"output_multiqc": "output_multiqc",
|
||||
"output_run_information": "run_information",
|
||||
"demultiplexer_logs": "demultiplexer_logs"
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@@ -23,6 +23,14 @@ workflow test_illumina {
|
||||
assert output.size() == 2 : "outputs should contain two elements; [id, file]"
|
||||
"Output: $output"
|
||||
}
|
||||
|
||||
event_count_ch = output_ch
|
||||
| toSortedList()
|
||||
| map { state ->
|
||||
assert state.size() == 1 : "Expected one event in the output channel"
|
||||
}
|
||||
|
||||
assert_ch = output_ch
|
||||
| map {id, state ->
|
||||
assert state.output.isDirectory(): "Expected bclconvert output to be a directory"
|
||||
state.output_falco.each{
|
||||
@@ -57,6 +65,21 @@ workflow test_illumina {
|
||||
|1,sampletest,PatientSample,UDP0004,ATTCCATAAG,TGCCTGGTGG
|
||||
|""".stripMargin()
|
||||
assert state.output_run_information.text.replaceAll("\r\n", "\n") == expected_run_information
|
||||
|
||||
println "ID: ${id}"
|
||||
println "State: ${state}"
|
||||
|
||||
assert state.demultiplexer_logs.isDirectory():
|
||||
"Expected BCL Convert reports to be a directory"
|
||||
|
||||
def logs_files = state.demultiplexer_logs.listFiles()
|
||||
println "Logs files: ${logs_files}"
|
||||
assert logs_files.size() > 0: "Expected BCL Convert logs dir to contain files"
|
||||
|
||||
assert logs_files.find { it.name == "Demultiplex_Stats.csv" }:
|
||||
"Expected to find BCL Convert Demultiplex_Stats.csv"
|
||||
assert logs_files.find { it.name == "Logs" }:
|
||||
"Expected to find BCL Convert Logs directory"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,5 +103,14 @@ workflow test_bases2fastq {
|
||||
assert state.output.isDirectory(): "Expected bases2fastq output to be a directory"
|
||||
state.output_falco.each{assert it.isDirectory(): "Expected falco output to be a directory"}
|
||||
assert state.output_multiqc.isFile(): "Expected multiQC output to be a file"
|
||||
|
||||
def logs_files = state.demultiplexer_logs.listFiles()
|
||||
println "Logs files: ${logs_files}"
|
||||
assert logs_files.size() > 0: "Expected bases2fastq logs dir to contain files"
|
||||
|
||||
assert logs_files.find { it.name == "report.html" } != null:
|
||||
"Expected to find bases2fastq report.html"
|
||||
assert logs_files.find { it.name == "info" }:
|
||||
"Expected to find bases2fastq info directory"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ requirements:
|
||||
resources:
|
||||
- type: bash_script
|
||||
path: script.sh
|
||||
test_resources:
|
||||
- type: bash_script
|
||||
path: test.sh
|
||||
- path: /testData/iseq-DI
|
||||
engines:
|
||||
- type: docker
|
||||
image: debian:stable-slim
|
||||
|
||||
18
src/io/interop_summary_to_csv/test.sh
Normal file
18
src/io/interop_summary_to_csv/test.sh
Normal file
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
# create tempdir
|
||||
echo ">>> Creating temporary test directory."
|
||||
TMPDIR=$(mktemp -d "$meta_temp_dir/$meta_functionality_name-XXXXXX")
|
||||
function clean_up {
|
||||
[[ -d "$TMPDIR" ]] && rm -r "$TMPDIR"
|
||||
}
|
||||
trap clean_up EXIT
|
||||
echo ">>> Created temporary directory '$TMPDIR'."
|
||||
|
||||
echo ">>> Run simple execution"
|
||||
./$meta_functionality_name \
|
||||
--input "$meta_resources_dir/iseq-DI" \
|
||||
--output_run_summary "$TMPDIR/run_summary.csv" \
|
||||
--output_index_summary "$TMPDIR/index_summary.csv"
|
||||
@@ -5,6 +5,7 @@ set -eo pipefail
|
||||
declare -A input_output_mapping=(["par_input"]="par_output"
|
||||
["par_input_multiqc"]="par_output_multiqc"
|
||||
["par_input_run_information"]="par_output_run_information"
|
||||
["par_input_demultiplexer_logs"]="par_output_demultiplexer_logs"
|
||||
)
|
||||
|
||||
for input_argument_name in "${!input_output_mapping[@]}"
|
||||
|
||||
@@ -21,6 +21,9 @@ argument_groups:
|
||||
description: "Location where to write the run information to."
|
||||
type: file
|
||||
required: true
|
||||
- name: "--input_demultiplexer_logs"
|
||||
type: file
|
||||
required: true
|
||||
- name: Output arguments
|
||||
arguments:
|
||||
- name: --output
|
||||
@@ -39,6 +42,10 @@ argument_groups:
|
||||
type: file
|
||||
direction: output
|
||||
default: run_information.csv
|
||||
- name: "--output_demultiplexer_logs"
|
||||
type: file
|
||||
direction: output
|
||||
default: "demultiplexer_logs"
|
||||
|
||||
resources:
|
||||
- type: bash_script
|
||||
|
||||
@@ -49,6 +49,10 @@ argument_groups:
|
||||
type: file
|
||||
direction: output
|
||||
default: "qc/multiqc_report.html"
|
||||
- name: "--demultiplexer_logs"
|
||||
type: file
|
||||
direction: output
|
||||
default: "demultiplexer_logs"
|
||||
- name: "Other arguments"
|
||||
arguments:
|
||||
- name: --skip_copycomplete_check
|
||||
|
||||
@@ -28,6 +28,7 @@ workflow run_wf {
|
||||
"output": "$id/fastq",
|
||||
"output_falco": "$id/qc/fastqc",
|
||||
"output_multiqc": "$id/qc/multiqc_report.html",
|
||||
"demultiplexer_logs": "$id/demultiplexer_logs",
|
||||
]
|
||||
if (state.run_information) {
|
||||
state_to_pass += ["output_run_information": state.run_information.getName()]
|
||||
@@ -48,6 +49,7 @@ workflow run_wf {
|
||||
def falco_output_1 = (id2 == "run") ? state.falco_output : "${id2}/" + state.falco_output
|
||||
def multiqc_output_1 = (id2 == "run") ? state.multiqc_output : "${id2}/" + state.multiqc_output
|
||||
def run_information_output_1 = (id2 == "run") ? "${state.output_run_information.getName()}" : "${id2}/${state.output_run_information.getName()}"
|
||||
def demultiplexer_logs_output = (id2 == "run") ? state.demultiplexer_logs : "${id2}/${state.demultiplexer_logs.getName()}"
|
||||
|
||||
if (id2 == "run") {
|
||||
println("Publising to ${params.publish_dir}")
|
||||
@@ -60,10 +62,12 @@ workflow run_wf {
|
||||
input_falco: state.output_falco,
|
||||
input_multiqc: state.output_multiqc,
|
||||
input_run_information: state.output_run_information,
|
||||
input_demultiplexer_logs: state.demultiplexer_logs,
|
||||
output: fastq_output_1,
|
||||
output_falco: falco_output_1,
|
||||
output_multiqc: multiqc_output_1,
|
||||
output_run_information: run_information_output_1,
|
||||
output_demultiplexer_logs: demultiplexer_logs_output,
|
||||
]
|
||||
},
|
||||
toState: { id, result, state -> [:] },
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: "interop_summary_to_csv"
|
||||
namespace: "io"
|
||||
version: "v0.3.5"
|
||||
version: "v0.3.11"
|
||||
argument_groups:
|
||||
- name: "Input arguments"
|
||||
arguments:
|
||||
@@ -41,10 +41,21 @@ resources:
|
||||
- type: "file"
|
||||
path: "nextflow_labels.config"
|
||||
dest: "nextflow_labels.config"
|
||||
test_resources:
|
||||
- type: "bash_script"
|
||||
path: "test.sh"
|
||||
is_executable: true
|
||||
- type: "file"
|
||||
path: "iseq-DI"
|
||||
info: null
|
||||
status: "enabled"
|
||||
scope:
|
||||
image: "public"
|
||||
target: "public"
|
||||
requirements:
|
||||
commands:
|
||||
- "summary"
|
||||
- "index-summary"
|
||||
- "ps"
|
||||
license: "MIT"
|
||||
links:
|
||||
@@ -121,7 +132,7 @@ engines:
|
||||
id: "docker"
|
||||
image: "debian:stable-slim"
|
||||
target_registry: "images.viash-hub.com"
|
||||
target_tag: "v0.3.5"
|
||||
target_tag: "v0.3.11"
|
||||
namespace_separator: "/"
|
||||
setup:
|
||||
- type: "apt"
|
||||
@@ -145,29 +156,29 @@ build_info:
|
||||
engine: "docker|native"
|
||||
output: "target/executable/io/interop_summary_to_csv"
|
||||
executable: "target/executable/io/interop_summary_to_csv/interop_summary_to_csv"
|
||||
viash_version: "0.9.0"
|
||||
git_commit: "18e092d169cc2704a2e4e705485d9231911d0484"
|
||||
viash_version: "0.9.4"
|
||||
git_commit: "cd2cfac18e14622e1a5d7a0d489f64c30e83dab2"
|
||||
git_remote: "https://github.com/viash-hub/demultiplex"
|
||||
git_tag: "v0.3.4-6-g18e092d"
|
||||
git_tag: "v0.3.10-5-gcd2cfac"
|
||||
package_config:
|
||||
name: "demultiplex"
|
||||
version: "v0.3.5"
|
||||
version: "v0.3.11"
|
||||
description: "Demultiplexing pipeline\n"
|
||||
info:
|
||||
test_resources:
|
||||
- path: "gs://viash-hub-test-data/demultiplex/v2/"
|
||||
- path: "gs://viash-hub-resources/demultiplex/v3"
|
||||
dest: "testData"
|
||||
viash_version: "0.9.0"
|
||||
viash_version: "0.9.4"
|
||||
source: "src"
|
||||
target: "target"
|
||||
config_mods:
|
||||
- ".requirements.commands := ['ps']\n.runners[.type == 'nextflow'].directives.tag\
|
||||
- ".requirements.commands += ['ps']\n.runners[.type == 'nextflow'].directives.tag\
|
||||
\ := '$id'\n.resources += {path: '/src/config/labels.config', dest: 'nextflow_labels.config'}\n\
|
||||
.runners[.type == 'nextflow'].config.script := 'includeConfig(\"nextflow_labels.config\"\
|
||||
)'\n"
|
||||
- ".engines += { type: \"native\" }"
|
||||
- ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'"
|
||||
- ".engines[.type == 'docker'].target_tag := 'v0.3.5'"
|
||||
- ".engines[.type == 'docker'].target_tag := 'v0.3.11'"
|
||||
keywords:
|
||||
- "bioinformatics"
|
||||
- "sequence"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# interop_summary_to_csv v0.3.5
|
||||
# interop_summary_to_csv v0.3.11
|
||||
#
|
||||
# This wrapper script is auto-generated by viash 0.9.0 and is thus a derivative
|
||||
# This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative
|
||||
# work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data
|
||||
# Intuitive.
|
||||
#
|
||||
@@ -169,22 +169,6 @@ VIASH_META_CONFIG="$VIASH_META_RESOURCES_DIR/.config.vsh.yaml"
|
||||
VIASH_META_TEMP_DIR="$VIASH_TEMP"
|
||||
|
||||
|
||||
# ViashHelp: Display helpful explanation about this executable
|
||||
function ViashHelp {
|
||||
echo "interop_summary_to_csv v0.3.5"
|
||||
echo ""
|
||||
echo "Input arguments:"
|
||||
echo " --input"
|
||||
echo " type: file, required parameter, file must exist"
|
||||
echo " Sequencing run folder (*not* InterOp folder)."
|
||||
echo ""
|
||||
echo "Output arguments:"
|
||||
echo " --output_run_summary"
|
||||
echo " type: file, required parameter, output, file must exist"
|
||||
echo ""
|
||||
echo " --output_index_summary"
|
||||
echo " type: file, required parameter, output, file must exist"
|
||||
}
|
||||
|
||||
# initialise variables
|
||||
VIASH_MODE='run'
|
||||
@@ -470,10 +454,10 @@ tar -C /tmp/ --no-same-owner --no-same-permissions -xvf /tmp/interop.tar.gz && \
|
||||
mv /tmp/interop-1.3.1-Linux-GNU/bin/index-summary /tmp/interop-1.3.1-Linux-GNU/bin/summary /usr/local/bin/
|
||||
|
||||
LABEL org.opencontainers.image.description="Companion container for running component io interop_summary_to_csv"
|
||||
LABEL org.opencontainers.image.created="2025-03-04T13:21:45Z"
|
||||
LABEL org.opencontainers.image.created="2025-05-14T08:46:02Z"
|
||||
LABEL org.opencontainers.image.source="https://github.com/viash-hub/demultiplex"
|
||||
LABEL org.opencontainers.image.revision="18e092d169cc2704a2e4e705485d9231911d0484"
|
||||
LABEL org.opencontainers.image.version="v0.3.5"
|
||||
LABEL org.opencontainers.image.revision="cd2cfac18e14622e1a5d7a0d489f64c30e83dab2"
|
||||
LABEL org.opencontainers.image.version="v0.3.11"
|
||||
|
||||
VIASHDOCKER
|
||||
fi
|
||||
@@ -587,6 +571,48 @@ fi
|
||||
# initialise docker variables
|
||||
VIASH_DOCKER_RUN_ARGS=(-i --rm)
|
||||
|
||||
|
||||
# ViashHelp: Display helpful explanation about this executable
|
||||
function ViashHelp {
|
||||
echo "interop_summary_to_csv v0.3.11"
|
||||
echo ""
|
||||
echo "Input arguments:"
|
||||
echo " --input"
|
||||
echo " type: file, required parameter, file must exist"
|
||||
echo " Sequencing run folder (*not* InterOp folder)."
|
||||
echo ""
|
||||
echo "Output arguments:"
|
||||
echo " --output_run_summary"
|
||||
echo " type: file, required parameter, output, file must exist"
|
||||
echo ""
|
||||
echo " --output_index_summary"
|
||||
echo " type: file, required parameter, output, file must exist"
|
||||
echo ""
|
||||
echo "Viash built in Computational Requirements:"
|
||||
echo " ---cpus=INT"
|
||||
echo " Number of CPUs to use"
|
||||
echo " ---memory=STRING"
|
||||
echo " Amount of memory to use. Examples: 4GB, 3MiB."
|
||||
echo ""
|
||||
echo "Viash built in Docker:"
|
||||
echo " ---setup=STRATEGY"
|
||||
echo " Setup the docker container. Options are: alwaysbuild, alwayscachedbuild, ifneedbebuild, ifneedbecachedbuild, alwayspull, alwayspullelsebuild, alwayspullelsecachedbuild, ifneedbepull, ifneedbepullelsebuild, ifneedbepullelsecachedbuild, push, pushifnotpresent, donothing."
|
||||
echo " Default: ifneedbepullelsecachedbuild"
|
||||
echo " ---dockerfile"
|
||||
echo " Print the dockerfile to stdout."
|
||||
echo " ---docker_run_args=ARG"
|
||||
echo " Provide runtime arguments to Docker. See the documentation on \`docker run\` for more information."
|
||||
echo " ---docker_image_id"
|
||||
echo " Print the docker image id to stdout."
|
||||
echo " ---debug"
|
||||
echo " Enter the docker container for debugging purposes."
|
||||
echo ""
|
||||
echo "Viash built in Engines:"
|
||||
echo " ---engine=ENGINE_ID"
|
||||
echo " Specify the engine to use. Options are: docker, native."
|
||||
echo " Default: docker"
|
||||
}
|
||||
|
||||
# initialise array
|
||||
VIASH_POSITIONAL_ARGS=''
|
||||
|
||||
@@ -609,7 +635,7 @@ while [[ $# -gt 0 ]]; do
|
||||
shift 1
|
||||
;;
|
||||
--version)
|
||||
echo "interop_summary_to_csv v0.3.5"
|
||||
echo "interop_summary_to_csv v0.3.11"
|
||||
exit
|
||||
;;
|
||||
--input)
|
||||
@@ -733,7 +759,7 @@ if [[ "$VIASH_ENGINE_TYPE" == "docker" ]]; then
|
||||
|
||||
# determine docker image id
|
||||
if [[ "$VIASH_ENGINE_ID" == 'docker' ]]; then
|
||||
VIASH_DOCKER_IMAGE_ID='images.viash-hub.com/vsh/demultiplex/io/interop_summary_to_csv:v0.3.5'
|
||||
VIASH_DOCKER_IMAGE_ID='images.viash-hub.com/vsh/demultiplex/io/interop_summary_to_csv:v0.3.11'
|
||||
fi
|
||||
|
||||
# print dockerfile
|
||||
@@ -755,13 +781,13 @@ if [[ "$VIASH_ENGINE_TYPE" == "docker" ]]; then
|
||||
# build docker image
|
||||
elif [ "$VIASH_MODE" == "setup" ]; then
|
||||
ViashDockerSetup "$VIASH_DOCKER_IMAGE_ID" "$VIASH_SETUP_STRATEGY"
|
||||
ViashDockerCheckCommands "$VIASH_DOCKER_IMAGE_ID" 'ps' 'bash'
|
||||
ViashDockerCheckCommands "$VIASH_DOCKER_IMAGE_ID" 'summary' 'index-summary' 'ps' 'bash'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# check if docker image exists
|
||||
ViashDockerSetup "$VIASH_DOCKER_IMAGE_ID" ifneedbepullelsecachedbuild
|
||||
ViashDockerCheckCommands "$VIASH_DOCKER_IMAGE_ID" 'ps' 'bash'
|
||||
ViashDockerCheckCommands "$VIASH_DOCKER_IMAGE_ID" 'summary' 'index-summary' 'ps' 'bash'
|
||||
fi
|
||||
|
||||
# setting computational defaults
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: "publish"
|
||||
namespace: "io"
|
||||
version: "v0.3.5"
|
||||
version: "v0.3.11"
|
||||
argument_groups:
|
||||
- name: "Input arguments"
|
||||
arguments:
|
||||
@@ -44,6 +44,15 @@ argument_groups:
|
||||
direction: "input"
|
||||
multiple: false
|
||||
multiple_sep: ";"
|
||||
- type: "file"
|
||||
name: "--input_demultiplexer_logs"
|
||||
info: null
|
||||
must_exist: true
|
||||
create_parent: true
|
||||
required: true
|
||||
direction: "input"
|
||||
multiple: false
|
||||
multiple_sep: ";"
|
||||
- name: "Output arguments"
|
||||
arguments:
|
||||
- type: "file"
|
||||
@@ -90,6 +99,17 @@ argument_groups:
|
||||
direction: "output"
|
||||
multiple: false
|
||||
multiple_sep: ";"
|
||||
- type: "file"
|
||||
name: "--output_demultiplexer_logs"
|
||||
info: null
|
||||
default:
|
||||
- "demultiplexer_logs"
|
||||
must_exist: true
|
||||
create_parent: true
|
||||
required: false
|
||||
direction: "output"
|
||||
multiple: false
|
||||
multiple_sep: ";"
|
||||
resources:
|
||||
- type: "bash_script"
|
||||
path: "code.sh"
|
||||
@@ -100,6 +120,9 @@ resources:
|
||||
description: "Publish the processed results of the run"
|
||||
info: null
|
||||
status: "enabled"
|
||||
scope:
|
||||
image: "public"
|
||||
target: "public"
|
||||
requirements:
|
||||
commands:
|
||||
- "ps"
|
||||
@@ -178,7 +201,7 @@ engines:
|
||||
id: "docker"
|
||||
image: "debian:stable-slim"
|
||||
target_registry: "images.viash-hub.com"
|
||||
target_tag: "v0.3.5"
|
||||
target_tag: "v0.3.11"
|
||||
namespace_separator: "/"
|
||||
setup:
|
||||
- type: "apt"
|
||||
@@ -195,29 +218,29 @@ build_info:
|
||||
engine: "docker|native"
|
||||
output: "target/executable/io/publish"
|
||||
executable: "target/executable/io/publish/publish"
|
||||
viash_version: "0.9.0"
|
||||
git_commit: "18e092d169cc2704a2e4e705485d9231911d0484"
|
||||
viash_version: "0.9.4"
|
||||
git_commit: "cd2cfac18e14622e1a5d7a0d489f64c30e83dab2"
|
||||
git_remote: "https://github.com/viash-hub/demultiplex"
|
||||
git_tag: "v0.3.4-6-g18e092d"
|
||||
git_tag: "v0.3.10-5-gcd2cfac"
|
||||
package_config:
|
||||
name: "demultiplex"
|
||||
version: "v0.3.5"
|
||||
version: "v0.3.11"
|
||||
description: "Demultiplexing pipeline\n"
|
||||
info:
|
||||
test_resources:
|
||||
- path: "gs://viash-hub-test-data/demultiplex/v2/"
|
||||
- path: "gs://viash-hub-resources/demultiplex/v3"
|
||||
dest: "testData"
|
||||
viash_version: "0.9.0"
|
||||
viash_version: "0.9.4"
|
||||
source: "src"
|
||||
target: "target"
|
||||
config_mods:
|
||||
- ".requirements.commands := ['ps']\n.runners[.type == 'nextflow'].directives.tag\
|
||||
- ".requirements.commands += ['ps']\n.runners[.type == 'nextflow'].directives.tag\
|
||||
\ := '$id'\n.resources += {path: '/src/config/labels.config', dest: 'nextflow_labels.config'}\n\
|
||||
.runners[.type == 'nextflow'].config.script := 'includeConfig(\"nextflow_labels.config\"\
|
||||
)'\n"
|
||||
- ".engines += { type: \"native\" }"
|
||||
- ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'"
|
||||
- ".engines[.type == 'docker'].target_tag := 'v0.3.5'"
|
||||
- ".engines[.type == 'docker'].target_tag := 'v0.3.11'"
|
||||
keywords:
|
||||
- "bioinformatics"
|
||||
- "sequence"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# publish v0.3.5
|
||||
# publish v0.3.11
|
||||
#
|
||||
# This wrapper script is auto-generated by viash 0.9.0 and is thus a derivative
|
||||
# This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative
|
||||
# work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data
|
||||
# Intuitive.
|
||||
#
|
||||
@@ -169,46 +169,6 @@ VIASH_META_CONFIG="$VIASH_META_RESOURCES_DIR/.config.vsh.yaml"
|
||||
VIASH_META_TEMP_DIR="$VIASH_TEMP"
|
||||
|
||||
|
||||
# ViashHelp: Display helpful explanation about this executable
|
||||
function ViashHelp {
|
||||
echo "publish v0.3.5"
|
||||
echo ""
|
||||
echo "Publish the processed results of the run"
|
||||
echo ""
|
||||
echo "Input arguments:"
|
||||
echo " --input"
|
||||
echo " type: file, required parameter, file must exist"
|
||||
echo " Directory to write fastq data to"
|
||||
echo ""
|
||||
echo " --input_falco"
|
||||
echo " type: file, required parameter, multiple values allowed, file must exist"
|
||||
echo " Directory to write falco output to"
|
||||
echo ""
|
||||
echo " --input_multiqc"
|
||||
echo " type: file, required parameter, file must exist"
|
||||
echo " Location where to write the MultiQC report to."
|
||||
echo ""
|
||||
echo " --input_run_information"
|
||||
echo " type: file, required parameter, file must exist"
|
||||
echo " Location where to write the run information to."
|
||||
echo ""
|
||||
echo "Output arguments:"
|
||||
echo " --output"
|
||||
echo " type: file, output, file must exist"
|
||||
echo " default: fastq"
|
||||
echo ""
|
||||
echo " --output_falco"
|
||||
echo " type: file, output, file must exist"
|
||||
echo " default: qc/fastqc"
|
||||
echo ""
|
||||
echo " --output_multiqc"
|
||||
echo " type: file, output, file must exist"
|
||||
echo " default: qc/multiqc_report.html"
|
||||
echo ""
|
||||
echo " --output_run_information"
|
||||
echo " type: file, output, file must exist"
|
||||
echo " default: run_information.csv"
|
||||
}
|
||||
|
||||
# initialise variables
|
||||
VIASH_MODE='run'
|
||||
@@ -490,10 +450,10 @@ RUN apt-get update && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
LABEL org.opencontainers.image.description="Companion container for running component io publish"
|
||||
LABEL org.opencontainers.image.created="2025-03-04T13:21:46Z"
|
||||
LABEL org.opencontainers.image.created="2025-05-14T08:46:03Z"
|
||||
LABEL org.opencontainers.image.source="https://github.com/viash-hub/demultiplex"
|
||||
LABEL org.opencontainers.image.revision="18e092d169cc2704a2e4e705485d9231911d0484"
|
||||
LABEL org.opencontainers.image.version="v0.3.5"
|
||||
LABEL org.opencontainers.image.revision="cd2cfac18e14622e1a5d7a0d489f64c30e83dab2"
|
||||
LABEL org.opencontainers.image.version="v0.3.11"
|
||||
|
||||
VIASHDOCKER
|
||||
fi
|
||||
@@ -607,6 +567,79 @@ fi
|
||||
# initialise docker variables
|
||||
VIASH_DOCKER_RUN_ARGS=(-i --rm)
|
||||
|
||||
|
||||
# ViashHelp: Display helpful explanation about this executable
|
||||
function ViashHelp {
|
||||
echo "publish v0.3.11"
|
||||
echo ""
|
||||
echo "Publish the processed results of the run"
|
||||
echo ""
|
||||
echo "Input arguments:"
|
||||
echo " --input"
|
||||
echo " type: file, required parameter, file must exist"
|
||||
echo " Directory to write fastq data to"
|
||||
echo ""
|
||||
echo " --input_falco"
|
||||
echo " type: file, required parameter, multiple values allowed, file must exist"
|
||||
echo " Directory to write falco output to"
|
||||
echo ""
|
||||
echo " --input_multiqc"
|
||||
echo " type: file, required parameter, file must exist"
|
||||
echo " Location where to write the MultiQC report to."
|
||||
echo ""
|
||||
echo " --input_run_information"
|
||||
echo " type: file, required parameter, file must exist"
|
||||
echo " Location where to write the run information to."
|
||||
echo ""
|
||||
echo " --input_demultiplexer_logs"
|
||||
echo " type: file, required parameter, file must exist"
|
||||
echo ""
|
||||
echo "Output arguments:"
|
||||
echo " --output"
|
||||
echo " type: file, output, file must exist"
|
||||
echo " default: fastq"
|
||||
echo ""
|
||||
echo " --output_falco"
|
||||
echo " type: file, output, file must exist"
|
||||
echo " default: qc/fastqc"
|
||||
echo ""
|
||||
echo " --output_multiqc"
|
||||
echo " type: file, output, file must exist"
|
||||
echo " default: qc/multiqc_report.html"
|
||||
echo ""
|
||||
echo " --output_run_information"
|
||||
echo " type: file, output, file must exist"
|
||||
echo " default: run_information.csv"
|
||||
echo ""
|
||||
echo " --output_demultiplexer_logs"
|
||||
echo " type: file, output, file must exist"
|
||||
echo " default: demultiplexer_logs"
|
||||
echo ""
|
||||
echo "Viash built in Computational Requirements:"
|
||||
echo " ---cpus=INT"
|
||||
echo " Number of CPUs to use"
|
||||
echo " ---memory=STRING"
|
||||
echo " Amount of memory to use. Examples: 4GB, 3MiB."
|
||||
echo ""
|
||||
echo "Viash built in Docker:"
|
||||
echo " ---setup=STRATEGY"
|
||||
echo " Setup the docker container. Options are: alwaysbuild, alwayscachedbuild, ifneedbebuild, ifneedbecachedbuild, alwayspull, alwayspullelsebuild, alwayspullelsecachedbuild, ifneedbepull, ifneedbepullelsebuild, ifneedbepullelsecachedbuild, push, pushifnotpresent, donothing."
|
||||
echo " Default: ifneedbepullelsecachedbuild"
|
||||
echo " ---dockerfile"
|
||||
echo " Print the dockerfile to stdout."
|
||||
echo " ---docker_run_args=ARG"
|
||||
echo " Provide runtime arguments to Docker. See the documentation on \`docker run\` for more information."
|
||||
echo " ---docker_image_id"
|
||||
echo " Print the docker image id to stdout."
|
||||
echo " ---debug"
|
||||
echo " Enter the docker container for debugging purposes."
|
||||
echo ""
|
||||
echo "Viash built in Engines:"
|
||||
echo " ---engine=ENGINE_ID"
|
||||
echo " Specify the engine to use. Options are: docker, native."
|
||||
echo " Default: docker"
|
||||
}
|
||||
|
||||
# initialise array
|
||||
VIASH_POSITIONAL_ARGS=''
|
||||
|
||||
@@ -629,7 +662,7 @@ while [[ $# -gt 0 ]]; do
|
||||
shift 1
|
||||
;;
|
||||
--version)
|
||||
echo "publish v0.3.5"
|
||||
echo "publish v0.3.11"
|
||||
exit
|
||||
;;
|
||||
--input)
|
||||
@@ -682,6 +715,17 @@ while [[ $# -gt 0 ]]; do
|
||||
VIASH_PAR_INPUT_RUN_INFORMATION=$(ViashRemoveFlags "$1")
|
||||
shift 1
|
||||
;;
|
||||
--input_demultiplexer_logs)
|
||||
[ -n "$VIASH_PAR_INPUT_DEMULTIPLEXER_LOGS" ] && ViashError Bad arguments for option \'--input_demultiplexer_logs\': \'$VIASH_PAR_INPUT_DEMULTIPLEXER_LOGS\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
|
||||
VIASH_PAR_INPUT_DEMULTIPLEXER_LOGS="$2"
|
||||
[ $# -lt 2 ] && ViashError Not enough arguments passed to --input_demultiplexer_logs. Use "--help" to get more information on the parameters. && exit 1
|
||||
shift 2
|
||||
;;
|
||||
--input_demultiplexer_logs=*)
|
||||
[ -n "$VIASH_PAR_INPUT_DEMULTIPLEXER_LOGS" ] && ViashError Bad arguments for option \'--input_demultiplexer_logs=*\': \'$VIASH_PAR_INPUT_DEMULTIPLEXER_LOGS\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
|
||||
VIASH_PAR_INPUT_DEMULTIPLEXER_LOGS=$(ViashRemoveFlags "$1")
|
||||
shift 1
|
||||
;;
|
||||
--output)
|
||||
[ -n "$VIASH_PAR_OUTPUT" ] && ViashError Bad arguments for option \'--output\': \'$VIASH_PAR_OUTPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
|
||||
VIASH_PAR_OUTPUT="$2"
|
||||
@@ -726,6 +770,17 @@ while [[ $# -gt 0 ]]; do
|
||||
VIASH_PAR_OUTPUT_RUN_INFORMATION=$(ViashRemoveFlags "$1")
|
||||
shift 1
|
||||
;;
|
||||
--output_demultiplexer_logs)
|
||||
[ -n "$VIASH_PAR_OUTPUT_DEMULTIPLEXER_LOGS" ] && ViashError Bad arguments for option \'--output_demultiplexer_logs\': \'$VIASH_PAR_OUTPUT_DEMULTIPLEXER_LOGS\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
|
||||
VIASH_PAR_OUTPUT_DEMULTIPLEXER_LOGS="$2"
|
||||
[ $# -lt 2 ] && ViashError Not enough arguments passed to --output_demultiplexer_logs. Use "--help" to get more information on the parameters. && exit 1
|
||||
shift 2
|
||||
;;
|
||||
--output_demultiplexer_logs=*)
|
||||
[ -n "$VIASH_PAR_OUTPUT_DEMULTIPLEXER_LOGS" ] && ViashError Bad arguments for option \'--output_demultiplexer_logs=*\': \'$VIASH_PAR_OUTPUT_DEMULTIPLEXER_LOGS\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
|
||||
VIASH_PAR_OUTPUT_DEMULTIPLEXER_LOGS=$(ViashRemoveFlags "$1")
|
||||
shift 1
|
||||
;;
|
||||
---engine)
|
||||
VIASH_ENGINE_ID="$2"
|
||||
shift 2
|
||||
@@ -814,7 +869,7 @@ if [[ "$VIASH_ENGINE_TYPE" == "docker" ]]; then
|
||||
|
||||
# determine docker image id
|
||||
if [[ "$VIASH_ENGINE_ID" == 'docker' ]]; then
|
||||
VIASH_DOCKER_IMAGE_ID='images.viash-hub.com/vsh/demultiplex/io/publish:v0.3.5'
|
||||
VIASH_DOCKER_IMAGE_ID='images.viash-hub.com/vsh/demultiplex/io/publish:v0.3.11'
|
||||
fi
|
||||
|
||||
# print dockerfile
|
||||
@@ -914,6 +969,10 @@ if [ -z ${VIASH_PAR_INPUT_RUN_INFORMATION+x} ]; then
|
||||
ViashError '--input_run_information' is a required argument. Use "--help" to get more information on the parameters.
|
||||
exit 1
|
||||
fi
|
||||
if [ -z ${VIASH_PAR_INPUT_DEMULTIPLEXER_LOGS+x} ]; then
|
||||
ViashError '--input_demultiplexer_logs' is a required argument. Use "--help" to get more information on the parameters.
|
||||
exit 1
|
||||
fi
|
||||
if [ -z ${VIASH_META_NAME+x} ]; then
|
||||
ViashError 'name' is a required argument. Use "--help" to get more information on the parameters.
|
||||
exit 1
|
||||
@@ -952,6 +1011,9 @@ fi
|
||||
if [ -z ${VIASH_PAR_OUTPUT_RUN_INFORMATION+x} ]; then
|
||||
VIASH_PAR_OUTPUT_RUN_INFORMATION="run_information.csv"
|
||||
fi
|
||||
if [ -z ${VIASH_PAR_OUTPUT_DEMULTIPLEXER_LOGS+x} ]; then
|
||||
VIASH_PAR_OUTPUT_DEMULTIPLEXER_LOGS="demultiplexer_logs"
|
||||
fi
|
||||
|
||||
# check whether required files exist
|
||||
if [ ! -z "$VIASH_PAR_INPUT" ] && [ ! -e "$VIASH_PAR_INPUT" ]; then
|
||||
@@ -978,6 +1040,10 @@ if [ ! -z "$VIASH_PAR_INPUT_RUN_INFORMATION" ] && [ ! -e "$VIASH_PAR_INPUT_RUN_I
|
||||
ViashError "Input file '$VIASH_PAR_INPUT_RUN_INFORMATION' does not exist."
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -z "$VIASH_PAR_INPUT_DEMULTIPLEXER_LOGS" ] && [ ! -e "$VIASH_PAR_INPUT_DEMULTIPLEXER_LOGS" ]; then
|
||||
ViashError "Input file '$VIASH_PAR_INPUT_DEMULTIPLEXER_LOGS' does not exist."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# check whether parameters values are of the right type
|
||||
if [[ -n "$VIASH_META_CPUS" ]]; then
|
||||
@@ -1066,6 +1132,9 @@ fi
|
||||
if [ ! -z "$VIASH_PAR_OUTPUT_RUN_INFORMATION" ] && [ ! -d "$(dirname "$VIASH_PAR_OUTPUT_RUN_INFORMATION")" ]; then
|
||||
mkdir -p "$(dirname "$VIASH_PAR_OUTPUT_RUN_INFORMATION")"
|
||||
fi
|
||||
if [ ! -z "$VIASH_PAR_OUTPUT_DEMULTIPLEXER_LOGS" ] && [ ! -d "$(dirname "$VIASH_PAR_OUTPUT_DEMULTIPLEXER_LOGS")" ]; then
|
||||
mkdir -p "$(dirname "$VIASH_PAR_OUTPUT_DEMULTIPLEXER_LOGS")"
|
||||
fi
|
||||
|
||||
if [ "$VIASH_ENGINE_ID" == "native" ] ; then
|
||||
if [ "$VIASH_MODE" == "run" ]; then
|
||||
@@ -1102,6 +1171,10 @@ if [ ! -z "$VIASH_PAR_INPUT_RUN_INFORMATION" ]; then
|
||||
VIASH_DIRECTORY_MOUNTS+=( "$(ViashDockerAutodetectMountArg "$VIASH_PAR_INPUT_RUN_INFORMATION")" )
|
||||
VIASH_PAR_INPUT_RUN_INFORMATION=$(ViashDockerAutodetectMount "$VIASH_PAR_INPUT_RUN_INFORMATION")
|
||||
fi
|
||||
if [ ! -z "$VIASH_PAR_INPUT_DEMULTIPLEXER_LOGS" ]; then
|
||||
VIASH_DIRECTORY_MOUNTS+=( "$(ViashDockerAutodetectMountArg "$VIASH_PAR_INPUT_DEMULTIPLEXER_LOGS")" )
|
||||
VIASH_PAR_INPUT_DEMULTIPLEXER_LOGS=$(ViashDockerAutodetectMount "$VIASH_PAR_INPUT_DEMULTIPLEXER_LOGS")
|
||||
fi
|
||||
if [ ! -z "$VIASH_PAR_OUTPUT" ]; then
|
||||
VIASH_DIRECTORY_MOUNTS+=( "$(ViashDockerAutodetectMountArg "$VIASH_PAR_OUTPUT")" )
|
||||
VIASH_PAR_OUTPUT=$(ViashDockerAutodetectMount "$VIASH_PAR_OUTPUT")
|
||||
@@ -1122,6 +1195,11 @@ if [ ! -z "$VIASH_PAR_OUTPUT_RUN_INFORMATION" ]; then
|
||||
VIASH_PAR_OUTPUT_RUN_INFORMATION=$(ViashDockerAutodetectMount "$VIASH_PAR_OUTPUT_RUN_INFORMATION")
|
||||
VIASH_CHOWN_VARS+=( "$VIASH_PAR_OUTPUT_RUN_INFORMATION" )
|
||||
fi
|
||||
if [ ! -z "$VIASH_PAR_OUTPUT_DEMULTIPLEXER_LOGS" ]; then
|
||||
VIASH_DIRECTORY_MOUNTS+=( "$(ViashDockerAutodetectMountArg "$VIASH_PAR_OUTPUT_DEMULTIPLEXER_LOGS")" )
|
||||
VIASH_PAR_OUTPUT_DEMULTIPLEXER_LOGS=$(ViashDockerAutodetectMount "$VIASH_PAR_OUTPUT_DEMULTIPLEXER_LOGS")
|
||||
VIASH_CHOWN_VARS+=( "$VIASH_PAR_OUTPUT_DEMULTIPLEXER_LOGS" )
|
||||
fi
|
||||
if [ ! -z "$VIASH_META_RESOURCES_DIR" ]; then
|
||||
VIASH_DIRECTORY_MOUNTS+=( "$(ViashDockerAutodetectMountArg "$VIASH_META_RESOURCES_DIR")" )
|
||||
VIASH_META_RESOURCES_DIR=$(ViashDockerAutodetectMount "$VIASH_META_RESOURCES_DIR")
|
||||
@@ -1195,10 +1273,12 @@ $( if [ ! -z ${VIASH_PAR_INPUT+x} ]; then echo "${VIASH_PAR_INPUT}" | sed "s#'#'
|
||||
$( if [ ! -z ${VIASH_PAR_INPUT_FALCO+x} ]; then echo "${VIASH_PAR_INPUT_FALCO}" | sed "s#'#'\"'\"'#g;s#.*#par_input_falco='&'#" ; else echo "# par_input_falco="; fi )
|
||||
$( if [ ! -z ${VIASH_PAR_INPUT_MULTIQC+x} ]; then echo "${VIASH_PAR_INPUT_MULTIQC}" | sed "s#'#'\"'\"'#g;s#.*#par_input_multiqc='&'#" ; else echo "# par_input_multiqc="; fi )
|
||||
$( if [ ! -z ${VIASH_PAR_INPUT_RUN_INFORMATION+x} ]; then echo "${VIASH_PAR_INPUT_RUN_INFORMATION}" | sed "s#'#'\"'\"'#g;s#.*#par_input_run_information='&'#" ; else echo "# par_input_run_information="; fi )
|
||||
$( if [ ! -z ${VIASH_PAR_INPUT_DEMULTIPLEXER_LOGS+x} ]; then echo "${VIASH_PAR_INPUT_DEMULTIPLEXER_LOGS}" | sed "s#'#'\"'\"'#g;s#.*#par_input_demultiplexer_logs='&'#" ; else echo "# par_input_demultiplexer_logs="; fi )
|
||||
$( if [ ! -z ${VIASH_PAR_OUTPUT+x} ]; then echo "${VIASH_PAR_OUTPUT}" | sed "s#'#'\"'\"'#g;s#.*#par_output='&'#" ; else echo "# par_output="; fi )
|
||||
$( if [ ! -z ${VIASH_PAR_OUTPUT_FALCO+x} ]; then echo "${VIASH_PAR_OUTPUT_FALCO}" | sed "s#'#'\"'\"'#g;s#.*#par_output_falco='&'#" ; else echo "# par_output_falco="; fi )
|
||||
$( if [ ! -z ${VIASH_PAR_OUTPUT_MULTIQC+x} ]; then echo "${VIASH_PAR_OUTPUT_MULTIQC}" | sed "s#'#'\"'\"'#g;s#.*#par_output_multiqc='&'#" ; else echo "# par_output_multiqc="; fi )
|
||||
$( if [ ! -z ${VIASH_PAR_OUTPUT_RUN_INFORMATION+x} ]; then echo "${VIASH_PAR_OUTPUT_RUN_INFORMATION}" | sed "s#'#'\"'\"'#g;s#.*#par_output_run_information='&'#" ; else echo "# par_output_run_information="; fi )
|
||||
$( if [ ! -z ${VIASH_PAR_OUTPUT_DEMULTIPLEXER_LOGS+x} ]; then echo "${VIASH_PAR_OUTPUT_DEMULTIPLEXER_LOGS}" | sed "s#'#'\"'\"'#g;s#.*#par_output_demultiplexer_logs='&'#" ; else echo "# par_output_demultiplexer_logs="; fi )
|
||||
$( if [ ! -z ${VIASH_META_NAME+x} ]; then echo "${VIASH_META_NAME}" | sed "s#'#'\"'\"'#g;s#.*#meta_name='&'#" ; else echo "# meta_name="; fi )
|
||||
$( if [ ! -z ${VIASH_META_FUNCTIONALITY_NAME+x} ]; then echo "${VIASH_META_FUNCTIONALITY_NAME}" | sed "s#'#'\"'\"'#g;s#.*#meta_functionality_name='&'#" ; else echo "# meta_functionality_name="; fi )
|
||||
$( if [ ! -z ${VIASH_META_RESOURCES_DIR+x} ]; then echo "${VIASH_META_RESOURCES_DIR}" | sed "s#'#'\"'\"'#g;s#.*#meta_resources_dir='&'#" ; else echo "# meta_resources_dir="; fi )
|
||||
@@ -1226,6 +1306,7 @@ set -eo pipefail
|
||||
declare -A input_output_mapping=(["par_input"]="par_output"
|
||||
["par_input_multiqc"]="par_output_multiqc"
|
||||
["par_input_run_information"]="par_output_run_information"
|
||||
["par_input_demultiplexer_logs"]="par_output_demultiplexer_logs"
|
||||
)
|
||||
|
||||
for input_argument_name in "\${!input_output_mapping[@]}"
|
||||
@@ -1284,6 +1365,9 @@ if [[ "$VIASH_ENGINE_TYPE" == "docker" ]]; then
|
||||
if [ ! -z "$VIASH_PAR_INPUT_RUN_INFORMATION" ]; then
|
||||
VIASH_PAR_INPUT_RUN_INFORMATION=$(ViashDockerStripAutomount "$VIASH_PAR_INPUT_RUN_INFORMATION")
|
||||
fi
|
||||
if [ ! -z "$VIASH_PAR_INPUT_DEMULTIPLEXER_LOGS" ]; then
|
||||
VIASH_PAR_INPUT_DEMULTIPLEXER_LOGS=$(ViashDockerStripAutomount "$VIASH_PAR_INPUT_DEMULTIPLEXER_LOGS")
|
||||
fi
|
||||
if [ ! -z "$VIASH_PAR_OUTPUT" ]; then
|
||||
VIASH_PAR_OUTPUT=$(ViashDockerStripAutomount "$VIASH_PAR_OUTPUT")
|
||||
fi
|
||||
@@ -1296,6 +1380,9 @@ if [[ "$VIASH_ENGINE_TYPE" == "docker" ]]; then
|
||||
if [ ! -z "$VIASH_PAR_OUTPUT_RUN_INFORMATION" ]; then
|
||||
VIASH_PAR_OUTPUT_RUN_INFORMATION=$(ViashDockerStripAutomount "$VIASH_PAR_OUTPUT_RUN_INFORMATION")
|
||||
fi
|
||||
if [ ! -z "$VIASH_PAR_OUTPUT_DEMULTIPLEXER_LOGS" ]; then
|
||||
VIASH_PAR_OUTPUT_DEMULTIPLEXER_LOGS=$(ViashDockerStripAutomount "$VIASH_PAR_OUTPUT_DEMULTIPLEXER_LOGS")
|
||||
fi
|
||||
if [ ! -z "$VIASH_META_RESOURCES_DIR" ]; then
|
||||
VIASH_META_RESOURCES_DIR=$(ViashDockerStripAutomount "$VIASH_META_RESOURCES_DIR")
|
||||
fi
|
||||
@@ -1328,6 +1415,10 @@ if [ ! -z "$VIASH_PAR_OUTPUT_RUN_INFORMATION" ] && [ ! -e "$VIASH_PAR_OUTPUT_RUN
|
||||
ViashError "Output file '$VIASH_PAR_OUTPUT_RUN_INFORMATION' does not exist."
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -z "$VIASH_PAR_OUTPUT_DEMULTIPLEXER_LOGS" ] && [ ! -e "$VIASH_PAR_OUTPUT_DEMULTIPLEXER_LOGS" ]; then
|
||||
ViashError "Output file '$VIASH_PAR_OUTPUT_DEMULTIPLEXER_LOGS' does not exist."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: "untar"
|
||||
namespace: "io"
|
||||
version: "v0.3.5"
|
||||
version: "v0.3.11"
|
||||
argument_groups:
|
||||
- name: "Input arguments"
|
||||
arguments:
|
||||
@@ -57,6 +57,9 @@ test_resources:
|
||||
is_executable: true
|
||||
info: null
|
||||
status: "enabled"
|
||||
scope:
|
||||
image: "public"
|
||||
target: "public"
|
||||
requirements:
|
||||
commands:
|
||||
- "ps"
|
||||
@@ -135,7 +138,7 @@ engines:
|
||||
id: "docker"
|
||||
image: "debian:stable-slim"
|
||||
target_registry: "images.viash-hub.com"
|
||||
target_tag: "v0.3.5"
|
||||
target_tag: "v0.3.11"
|
||||
namespace_separator: "/"
|
||||
setup:
|
||||
- type: "apt"
|
||||
@@ -152,29 +155,29 @@ build_info:
|
||||
engine: "docker|native"
|
||||
output: "target/executable/io/untar"
|
||||
executable: "target/executable/io/untar/untar"
|
||||
viash_version: "0.9.0"
|
||||
git_commit: "18e092d169cc2704a2e4e705485d9231911d0484"
|
||||
viash_version: "0.9.4"
|
||||
git_commit: "cd2cfac18e14622e1a5d7a0d489f64c30e83dab2"
|
||||
git_remote: "https://github.com/viash-hub/demultiplex"
|
||||
git_tag: "v0.3.4-6-g18e092d"
|
||||
git_tag: "v0.3.10-5-gcd2cfac"
|
||||
package_config:
|
||||
name: "demultiplex"
|
||||
version: "v0.3.5"
|
||||
version: "v0.3.11"
|
||||
description: "Demultiplexing pipeline\n"
|
||||
info:
|
||||
test_resources:
|
||||
- path: "gs://viash-hub-test-data/demultiplex/v2/"
|
||||
- path: "gs://viash-hub-resources/demultiplex/v3"
|
||||
dest: "testData"
|
||||
viash_version: "0.9.0"
|
||||
viash_version: "0.9.4"
|
||||
source: "src"
|
||||
target: "target"
|
||||
config_mods:
|
||||
- ".requirements.commands := ['ps']\n.runners[.type == 'nextflow'].directives.tag\
|
||||
- ".requirements.commands += ['ps']\n.runners[.type == 'nextflow'].directives.tag\
|
||||
\ := '$id'\n.resources += {path: '/src/config/labels.config', dest: 'nextflow_labels.config'}\n\
|
||||
.runners[.type == 'nextflow'].config.script := 'includeConfig(\"nextflow_labels.config\"\
|
||||
)'\n"
|
||||
- ".engines += { type: \"native\" }"
|
||||
- ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'"
|
||||
- ".engines[.type == 'docker'].target_tag := 'v0.3.5'"
|
||||
- ".engines[.type == 'docker'].target_tag := 'v0.3.11'"
|
||||
keywords:
|
||||
- "bioinformatics"
|
||||
- "sequence"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# untar v0.3.5
|
||||
# untar v0.3.11
|
||||
#
|
||||
# This wrapper script is auto-generated by viash 0.9.0 and is thus a derivative
|
||||
# This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative
|
||||
# work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data
|
||||
# Intuitive.
|
||||
#
|
||||
@@ -169,32 +169,6 @@ VIASH_META_CONFIG="$VIASH_META_RESOURCES_DIR/.config.vsh.yaml"
|
||||
VIASH_META_TEMP_DIR="$VIASH_TEMP"
|
||||
|
||||
|
||||
# ViashHelp: Display helpful explanation about this executable
|
||||
function ViashHelp {
|
||||
echo "untar v0.3.5"
|
||||
echo ""
|
||||
echo "Unpack a .tar file. When the contents of the .tar file is just a single"
|
||||
echo "directory,"
|
||||
echo "put the contents of the directory into the output folder instead of that"
|
||||
echo "directory."
|
||||
echo ""
|
||||
echo "Input arguments:"
|
||||
echo " --input"
|
||||
echo " type: file, required parameter, file must exist"
|
||||
echo " Tarball file to be unpacked."
|
||||
echo ""
|
||||
echo "Output arguments:"
|
||||
echo " --output"
|
||||
echo " type: file, required parameter, output, file must exist"
|
||||
echo " Directory to write the contents of the .tar file to."
|
||||
echo ""
|
||||
echo "Other arguments:"
|
||||
echo " -e, --exclude"
|
||||
echo " type: string"
|
||||
echo " example: docs/figures"
|
||||
echo " Prevents any file or member whose name matches the shell wildcard"
|
||||
echo " (pattern) from being extracted."
|
||||
}
|
||||
|
||||
# initialise variables
|
||||
VIASH_MODE='run'
|
||||
@@ -476,10 +450,10 @@ RUN apt-get update && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
LABEL org.opencontainers.image.description="Companion container for running component io untar"
|
||||
LABEL org.opencontainers.image.created="2025-03-04T13:21:46Z"
|
||||
LABEL org.opencontainers.image.created="2025-05-14T08:46:02Z"
|
||||
LABEL org.opencontainers.image.source="https://github.com/viash-hub/demultiplex"
|
||||
LABEL org.opencontainers.image.revision="18e092d169cc2704a2e4e705485d9231911d0484"
|
||||
LABEL org.opencontainers.image.version="v0.3.5"
|
||||
LABEL org.opencontainers.image.revision="cd2cfac18e14622e1a5d7a0d489f64c30e83dab2"
|
||||
LABEL org.opencontainers.image.version="v0.3.11"
|
||||
|
||||
VIASHDOCKER
|
||||
fi
|
||||
@@ -593,6 +567,58 @@ fi
|
||||
# initialise docker variables
|
||||
VIASH_DOCKER_RUN_ARGS=(-i --rm)
|
||||
|
||||
|
||||
# ViashHelp: Display helpful explanation about this executable
|
||||
function ViashHelp {
|
||||
echo "untar v0.3.11"
|
||||
echo ""
|
||||
echo "Unpack a .tar file. When the contents of the .tar file is just a single"
|
||||
echo "directory,"
|
||||
echo "put the contents of the directory into the output folder instead of that"
|
||||
echo "directory."
|
||||
echo ""
|
||||
echo "Input arguments:"
|
||||
echo " --input"
|
||||
echo " type: file, required parameter, file must exist"
|
||||
echo " Tarball file to be unpacked."
|
||||
echo ""
|
||||
echo "Output arguments:"
|
||||
echo " --output"
|
||||
echo " type: file, required parameter, output, file must exist"
|
||||
echo " Directory to write the contents of the .tar file to."
|
||||
echo ""
|
||||
echo "Other arguments:"
|
||||
echo " -e, --exclude"
|
||||
echo " type: string"
|
||||
echo " example: docs/figures"
|
||||
echo " Prevents any file or member whose name matches the shell wildcard"
|
||||
echo " (pattern) from being extracted."
|
||||
echo ""
|
||||
echo "Viash built in Computational Requirements:"
|
||||
echo " ---cpus=INT"
|
||||
echo " Number of CPUs to use"
|
||||
echo " ---memory=STRING"
|
||||
echo " Amount of memory to use. Examples: 4GB, 3MiB."
|
||||
echo ""
|
||||
echo "Viash built in Docker:"
|
||||
echo " ---setup=STRATEGY"
|
||||
echo " Setup the docker container. Options are: alwaysbuild, alwayscachedbuild, ifneedbebuild, ifneedbecachedbuild, alwayspull, alwayspullelsebuild, alwayspullelsecachedbuild, ifneedbepull, ifneedbepullelsebuild, ifneedbepullelsecachedbuild, push, pushifnotpresent, donothing."
|
||||
echo " Default: ifneedbepullelsecachedbuild"
|
||||
echo " ---dockerfile"
|
||||
echo " Print the dockerfile to stdout."
|
||||
echo " ---docker_run_args=ARG"
|
||||
echo " Provide runtime arguments to Docker. See the documentation on \`docker run\` for more information."
|
||||
echo " ---docker_image_id"
|
||||
echo " Print the docker image id to stdout."
|
||||
echo " ---debug"
|
||||
echo " Enter the docker container for debugging purposes."
|
||||
echo ""
|
||||
echo "Viash built in Engines:"
|
||||
echo " ---engine=ENGINE_ID"
|
||||
echo " Specify the engine to use. Options are: docker, native."
|
||||
echo " Default: docker"
|
||||
}
|
||||
|
||||
# initialise array
|
||||
VIASH_POSITIONAL_ARGS=''
|
||||
|
||||
@@ -615,7 +641,7 @@ while [[ $# -gt 0 ]]; do
|
||||
shift 1
|
||||
;;
|
||||
--version)
|
||||
echo "untar v0.3.5"
|
||||
echo "untar v0.3.11"
|
||||
exit
|
||||
;;
|
||||
--input)
|
||||
@@ -745,7 +771,7 @@ if [[ "$VIASH_ENGINE_TYPE" == "docker" ]]; then
|
||||
|
||||
# determine docker image id
|
||||
if [[ "$VIASH_ENGINE_ID" == 'docker' ]]; then
|
||||
VIASH_DOCKER_IMAGE_ID='images.viash-hub.com/vsh/demultiplex/io/untar:v0.3.5'
|
||||
VIASH_DOCKER_IMAGE_ID='images.viash-hub.com/vsh/demultiplex/io/untar:v0.3.11'
|
||||
fi
|
||||
|
||||
# print dockerfile
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: "combine_samples"
|
||||
namespace: "dataflow"
|
||||
version: "v0.3.5"
|
||||
version: "v0.3.11"
|
||||
argument_groups:
|
||||
- name: "Input arguments"
|
||||
arguments:
|
||||
@@ -80,6 +80,9 @@ description: "Combine fastq files from across samples into one event with a list
|
||||
\ fastq files per orientation."
|
||||
info: null
|
||||
status: "enabled"
|
||||
scope:
|
||||
image: "public"
|
||||
target: "public"
|
||||
requirements:
|
||||
commands:
|
||||
- "ps"
|
||||
@@ -161,29 +164,29 @@ build_info:
|
||||
engine: "native|native"
|
||||
output: "target/nextflow/dataflow/combine_samples"
|
||||
executable: "target/nextflow/dataflow/combine_samples/main.nf"
|
||||
viash_version: "0.9.0"
|
||||
git_commit: "18e092d169cc2704a2e4e705485d9231911d0484"
|
||||
viash_version: "0.9.4"
|
||||
git_commit: "cd2cfac18e14622e1a5d7a0d489f64c30e83dab2"
|
||||
git_remote: "https://github.com/viash-hub/demultiplex"
|
||||
git_tag: "v0.3.4-6-g18e092d"
|
||||
git_tag: "v0.3.10-5-gcd2cfac"
|
||||
package_config:
|
||||
name: "demultiplex"
|
||||
version: "v0.3.5"
|
||||
version: "v0.3.11"
|
||||
description: "Demultiplexing pipeline\n"
|
||||
info:
|
||||
test_resources:
|
||||
- path: "gs://viash-hub-test-data/demultiplex/v2/"
|
||||
- path: "gs://viash-hub-resources/demultiplex/v3"
|
||||
dest: "testData"
|
||||
viash_version: "0.9.0"
|
||||
viash_version: "0.9.4"
|
||||
source: "src"
|
||||
target: "target"
|
||||
config_mods:
|
||||
- ".requirements.commands := ['ps']\n.runners[.type == 'nextflow'].directives.tag\
|
||||
- ".requirements.commands += ['ps']\n.runners[.type == 'nextflow'].directives.tag\
|
||||
\ := '$id'\n.resources += {path: '/src/config/labels.config', dest: 'nextflow_labels.config'}\n\
|
||||
.runners[.type == 'nextflow'].config.script := 'includeConfig(\"nextflow_labels.config\"\
|
||||
)'\n"
|
||||
- ".engines += { type: \"native\" }"
|
||||
- ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'"
|
||||
- ".engines[.type == 'docker'].target_tag := 'v0.3.5'"
|
||||
- ".engines[.type == 'docker'].target_tag := 'v0.3.11'"
|
||||
keywords:
|
||||
- "bioinformatics"
|
||||
- "sequence"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// combine_samples v0.3.5
|
||||
// combine_samples v0.3.11
|
||||
//
|
||||
// This wrapper script is auto-generated by viash 0.9.0 and is thus a derivative
|
||||
// This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative
|
||||
// work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data
|
||||
// Intuitive.
|
||||
//
|
||||
@@ -82,64 +82,56 @@ def _checkArgumentType(String stage, Map par, Object value, String errorIdentifi
|
||||
foundClass = "List[${e.foundClass}]"
|
||||
}
|
||||
} else if (par.type == "string") {
|
||||
// cast to string if need be
|
||||
// cast to string if need be. only cast if the value is a GString
|
||||
if (value instanceof GString) {
|
||||
value = value.toString()
|
||||
value = value as String
|
||||
}
|
||||
expectedClass = value instanceof String ? null : "String"
|
||||
} else if (par.type == "integer") {
|
||||
// cast to integer if need be
|
||||
if (value instanceof String) {
|
||||
if (value !instanceof Integer) {
|
||||
try {
|
||||
value = value.toInteger()
|
||||
value = value as Integer
|
||||
} catch (NumberFormatException e) {
|
||||
// do nothing
|
||||
expectedClass = "Integer"
|
||||
}
|
||||
}
|
||||
if (value instanceof java.math.BigInteger) {
|
||||
value = value.intValue()
|
||||
}
|
||||
expectedClass = value instanceof Integer ? null : "Integer"
|
||||
} else if (par.type == "long") {
|
||||
// cast to long if need be
|
||||
if (value instanceof String) {
|
||||
if (value !instanceof Long) {
|
||||
try {
|
||||
value = value.toLong()
|
||||
value = value as Long
|
||||
} catch (NumberFormatException e) {
|
||||
// do nothing
|
||||
expectedClass = "Long"
|
||||
}
|
||||
}
|
||||
if (value instanceof Integer) {
|
||||
value = value.toLong()
|
||||
}
|
||||
expectedClass = value instanceof Long ? null : "Long"
|
||||
} else if (par.type == "double") {
|
||||
// cast to double if need be
|
||||
if (value instanceof String) {
|
||||
if (value !instanceof Double) {
|
||||
try {
|
||||
value = value.toDouble()
|
||||
value = value as Double
|
||||
} catch (NumberFormatException e) {
|
||||
// do nothing
|
||||
expectedClass = "Double"
|
||||
}
|
||||
}
|
||||
if (value instanceof java.math.BigDecimal) {
|
||||
value = value.doubleValue()
|
||||
} else if (par.type == "float") {
|
||||
// cast to float if need be
|
||||
if (value !instanceof Float) {
|
||||
try {
|
||||
value = value as Float
|
||||
} catch (NumberFormatException e) {
|
||||
expectedClass = "Float"
|
||||
}
|
||||
}
|
||||
if (value instanceof Float) {
|
||||
value = value.toDouble()
|
||||
}
|
||||
expectedClass = value instanceof Double ? null : "Double"
|
||||
} else if (par.type == "boolean" | par.type == "boolean_true" | par.type == "boolean_false") {
|
||||
// cast to boolean if need be
|
||||
if (value instanceof String) {
|
||||
def valueLower = value.toLowerCase()
|
||||
if (valueLower == "true") {
|
||||
value = true
|
||||
} else if (valueLower == "false") {
|
||||
value = false
|
||||
if (value !instanceof Boolean) {
|
||||
try {
|
||||
value = value as Boolean
|
||||
} catch (Exception e) {
|
||||
expectedClass = "Boolean"
|
||||
}
|
||||
}
|
||||
expectedClass = value instanceof Boolean ? null : "Boolean"
|
||||
} else if (par.type == "file" && (par.direction == "input" || stage == "output")) {
|
||||
// cast to path if need be
|
||||
if (value instanceof String) {
|
||||
@@ -151,10 +143,13 @@ def _checkArgumentType(String stage, Map par, Object value, String errorIdentifi
|
||||
expectedClass = value instanceof Path ? null : "Path"
|
||||
} else if (par.type == "file" && stage == "input" && par.direction == "output") {
|
||||
// cast to string if need be
|
||||
if (value instanceof GString) {
|
||||
value = value.toString()
|
||||
if (value !instanceof String) {
|
||||
try {
|
||||
value = value as String
|
||||
} catch (Exception e) {
|
||||
expectedClass = "String"
|
||||
}
|
||||
}
|
||||
expectedClass = value instanceof String ? null : "String"
|
||||
} else {
|
||||
// didn't find a match for par.type
|
||||
expectedClass = par.type
|
||||
@@ -173,7 +168,7 @@ def _checkArgumentType(String stage, Map par, Object value, String errorIdentifi
|
||||
Map _processInputValues(Map inputs, Map config, String id, String key) {
|
||||
if (!workflow.stubRun) {
|
||||
config.allArguments.each { arg ->
|
||||
if (arg.required) {
|
||||
if (arg.required && arg.direction == "input") {
|
||||
assert inputs.containsKey(arg.plainName) && inputs.get(arg.plainName) != null :
|
||||
"Error in module '${key}' id '${id}': required input argument '${arg.plainName}' is missing"
|
||||
}
|
||||
@@ -192,15 +187,8 @@ Map _processInputValues(Map inputs, Map config, String id, String key) {
|
||||
}
|
||||
|
||||
// helper file: 'src/main/resources/io/viash/runners/nextflow/arguments/_processOutputValues.nf'
|
||||
Map _processOutputValues(Map outputs, Map config, String id, String key) {
|
||||
Map _checkValidOutputArgument(Map outputs, Map config, String id, String key) {
|
||||
if (!workflow.stubRun) {
|
||||
config.allArguments.each { arg ->
|
||||
if (arg.direction == "output" && arg.required) {
|
||||
assert outputs.containsKey(arg.plainName) && outputs.get(arg.plainName) != null :
|
||||
"Error in module '${key}' id '${id}': required output argument '${arg.plainName}' is missing"
|
||||
}
|
||||
}
|
||||
|
||||
outputs = outputs.collectEntries { name, value ->
|
||||
def par = config.allArguments.find { it.plainName == name && it.direction == "output" }
|
||||
assert par != null : "Error in module '${key}' id '${id}': '${name}' is not a valid output argument"
|
||||
@@ -213,6 +201,16 @@ Map _processOutputValues(Map outputs, Map config, String id, String key) {
|
||||
return outputs
|
||||
}
|
||||
|
||||
void _checkAllRequiredOuputsPresent(Map outputs, Map config, String id, String key) {
|
||||
if (!workflow.stubRun) {
|
||||
config.allArguments.each { arg ->
|
||||
if (arg.direction == "output" && arg.required) {
|
||||
assert outputs.containsKey(arg.plainName) && outputs.get(arg.plainName) != null :
|
||||
"Error in module '${key}' id '${id}': required output argument '${arg.plainName}' is missing"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// helper file: 'src/main/resources/io/viash/runners/nextflow/channel/IDChecker.nf'
|
||||
class IDChecker {
|
||||
final def items = [] as Set
|
||||
@@ -1666,6 +1664,162 @@ def joinStates(Closure apply_) {
|
||||
}
|
||||
return joinStatesWf
|
||||
}
|
||||
// helper file: 'src/main/resources/io/viash/runners/nextflow/states/publishFiles.nf'
|
||||
def publishFiles(Map args) {
|
||||
def key_ = args.get("key")
|
||||
|
||||
assert key_ != null : "publishFiles: key must be specified"
|
||||
|
||||
workflow publishFilesWf {
|
||||
take: input_ch
|
||||
main:
|
||||
input_ch
|
||||
| map { tup ->
|
||||
def id_ = tup[0]
|
||||
def state_ = tup[1]
|
||||
|
||||
// the input files and the target output filenames
|
||||
def inputoutputFilenames_ = collectInputOutputPaths(state_, id_ + "." + key_).transpose()
|
||||
def inputFiles_ = inputoutputFilenames_[0]
|
||||
def outputFilenames_ = inputoutputFilenames_[1]
|
||||
|
||||
[id_, inputFiles_, outputFilenames_]
|
||||
}
|
||||
| publishFilesProc
|
||||
emit: input_ch
|
||||
}
|
||||
return publishFilesWf
|
||||
}
|
||||
|
||||
process publishFilesProc {
|
||||
// todo: check publishpath?
|
||||
publishDir path: "${getPublishDir()}/", mode: "copy"
|
||||
tag "$id"
|
||||
input:
|
||||
tuple val(id), path(inputFiles, stageAs: "_inputfile?/*"), val(outputFiles)
|
||||
output:
|
||||
tuple val(id), path{outputFiles}
|
||||
script:
|
||||
def copyCommands = [
|
||||
inputFiles instanceof List ? inputFiles : [inputFiles],
|
||||
outputFiles instanceof List ? outputFiles : [outputFiles]
|
||||
]
|
||||
.transpose()
|
||||
.collectMany{infile, outfile ->
|
||||
if (infile.toString() != outfile.toString()) {
|
||||
[
|
||||
"[ -d \"\$(dirname '${outfile.toString()}')\" ] || mkdir -p \"\$(dirname '${outfile.toString()}')\"",
|
||||
"cp -r '${infile.toString()}' '${outfile.toString()}'"
|
||||
]
|
||||
} else {
|
||||
// no need to copy if infile is the same as outfile
|
||||
[]
|
||||
}
|
||||
}
|
||||
"""
|
||||
echo "Copying output files to destination folder"
|
||||
${copyCommands.join("\n ")}
|
||||
"""
|
||||
}
|
||||
|
||||
|
||||
// this assumes that the state contains no other values other than those specified in the config
|
||||
def publishFilesByConfig(Map args) {
|
||||
def config = args.get("config")
|
||||
assert config != null : "publishFilesByConfig: config must be specified"
|
||||
|
||||
def key_ = args.get("key", config.name)
|
||||
assert key_ != null : "publishFilesByConfig: key must be specified"
|
||||
|
||||
workflow publishFilesSimpleWf {
|
||||
take: input_ch
|
||||
main:
|
||||
input_ch
|
||||
| map { tup ->
|
||||
def id_ = tup[0]
|
||||
def state_ = tup[1] // e.g. [output: new File("myoutput.h5ad"), k: 10]
|
||||
def origState_ = tup[2] // e.g. [output: '$id.$key.foo.h5ad']
|
||||
|
||||
|
||||
// the processed state is a list of [key, value, inputPath, outputFilename] tuples, where
|
||||
// - key is a String
|
||||
// - value is any object that can be serialized to a Yaml (so a String/Integer/Long/Double/Boolean, a List, a Map, or a Path)
|
||||
// - inputPath is a List[Path]
|
||||
// - outputFilename is a List[String]
|
||||
// - (inputPath, outputFilename) are the files that will be copied from src to dest (relative to the state.yaml)
|
||||
def processedState =
|
||||
config.allArguments
|
||||
.findAll { it.direction == "output" }
|
||||
.collectMany { par ->
|
||||
def plainName_ = par.plainName
|
||||
// if the state does not contain the key, it's an
|
||||
// optional argument for which the component did
|
||||
// not generate any output OR multiple channels were emitted
|
||||
// and the output was just not added to using the channel
|
||||
// that is now being parsed
|
||||
if (!state_.containsKey(plainName_)) {
|
||||
return []
|
||||
}
|
||||
def value = state_[plainName_]
|
||||
// if the parameter is not a file, it should be stored
|
||||
// in the state as-is, but is not something that needs
|
||||
// to be copied from the source path to the dest path
|
||||
if (par.type != "file") {
|
||||
return [[inputPath: [], outputFilename: []]]
|
||||
}
|
||||
// if the orig state does not contain this filename,
|
||||
// it's an optional argument for which the user specified
|
||||
// that it should not be returned as a state
|
||||
if (!origState_.containsKey(plainName_)) {
|
||||
return []
|
||||
}
|
||||
def filenameTemplate = origState_[plainName_]
|
||||
// if the pararameter is multiple: true, fetch the template
|
||||
if (par.multiple && filenameTemplate instanceof List) {
|
||||
filenameTemplate = filenameTemplate[0]
|
||||
}
|
||||
// instantiate the template
|
||||
def filename = filenameTemplate
|
||||
.replaceAll('\\$id', id_)
|
||||
.replaceAll('\\$\\{id\\}', id_)
|
||||
.replaceAll('\\$key', key_)
|
||||
.replaceAll('\\$\\{key\\}', key_)
|
||||
if (par.multiple) {
|
||||
// if the parameter is multiple: true, the filename
|
||||
// should contain a wildcard '*' that is replaced with
|
||||
// the index of the file
|
||||
assert filename.contains("*") : "Module '${key_}' id '${id_}': Multiple output files specified, but no wildcard '*' in the filename: ${filename}"
|
||||
def outputPerFile = value.withIndex().collect{ val, ix ->
|
||||
def filename_ix = filename.replace("*", ix.toString())
|
||||
def inputPath = val instanceof File ? val.toPath() : val
|
||||
[inputPath: inputPath, outputFilename: filename_ix]
|
||||
}
|
||||
def transposedOutputs = ["inputPath", "outputFilename"].collectEntries{ key ->
|
||||
[key, outputPerFile.collect{dic -> dic[key]}]
|
||||
}
|
||||
return [[key: plainName_] + transposedOutputs]
|
||||
} else {
|
||||
def value_ = java.nio.file.Paths.get(filename)
|
||||
def inputPath = value instanceof File ? value.toPath() : value
|
||||
return [[inputPath: [inputPath], outputFilename: [filename]]]
|
||||
}
|
||||
}
|
||||
|
||||
def inputPaths = processedState.collectMany{it.inputPath}
|
||||
def outputFilenames = processedState.collectMany{it.outputFilename}
|
||||
|
||||
|
||||
[id_, inputPaths, outputFilenames]
|
||||
}
|
||||
| publishFilesProc
|
||||
emit: input_ch
|
||||
}
|
||||
return publishFilesSimpleWf
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// helper file: 'src/main/resources/io/viash/runners/nextflow/states/publishStates.nf'
|
||||
def collectFiles(obj) {
|
||||
if (obj instanceof java.io.File || obj instanceof Path) {
|
||||
@@ -1723,8 +1877,6 @@ def publishStates(Map args) {
|
||||
|
||||
// the input files and the target output filenames
|
||||
def inputoutputFilenames_ = collectInputOutputPaths(state_, id_ + "." + key_).transpose()
|
||||
def inputFiles_ = inputoutputFilenames_[0]
|
||||
def outputFilenames_ = inputoutputFilenames_[1]
|
||||
|
||||
def yamlFilename = yamlTemplate_
|
||||
.replaceAll('\\$id', id_)
|
||||
@@ -1737,7 +1889,7 @@ def publishStates(Map args) {
|
||||
// convert state to yaml blob
|
||||
def yamlBlob_ = toRelativeTaggedYamlBlob([id: id_] + state_, java.nio.file.Paths.get(yamlFilename))
|
||||
|
||||
[id_, yamlBlob_, yamlFilename, inputFiles_, outputFilenames_]
|
||||
[id_, yamlBlob_, yamlFilename]
|
||||
}
|
||||
| publishStatesProc
|
||||
emit: input_ch
|
||||
@@ -1749,33 +1901,17 @@ process publishStatesProc {
|
||||
publishDir path: "${getPublishDir()}/", mode: "copy"
|
||||
tag "$id"
|
||||
input:
|
||||
tuple val(id), val(yamlBlob), val(yamlFile), path(inputFiles, stageAs: "_inputfile?/*"), val(outputFiles)
|
||||
tuple val(id), val(yamlBlob), val(yamlFile)
|
||||
output:
|
||||
tuple val(id), path{[yamlFile] + outputFiles}
|
||||
tuple val(id), path{[yamlFile]}
|
||||
script:
|
||||
def copyCommands = [
|
||||
inputFiles instanceof List ? inputFiles : [inputFiles],
|
||||
outputFiles instanceof List ? outputFiles : [outputFiles]
|
||||
]
|
||||
.transpose()
|
||||
.collectMany{infile, outfile ->
|
||||
if (infile.toString() != outfile.toString()) {
|
||||
[
|
||||
"[ -d \"\$(dirname '${outfile.toString()}')\" ] || mkdir -p \"\$(dirname '${outfile.toString()}')\"",
|
||||
"cp -r '${infile.toString()}' '${outfile.toString()}'"
|
||||
]
|
||||
} else {
|
||||
// no need to copy if infile is the same as outfile
|
||||
[]
|
||||
}
|
||||
}
|
||||
"""
|
||||
mkdir -p "\$(dirname '${yamlFile}')"
|
||||
echo "Storing state as yaml"
|
||||
echo '${yamlBlob}' > '${yamlFile}'
|
||||
echo "Copying output files to destination folder"
|
||||
${copyCommands.join("\n ")}
|
||||
"""
|
||||
mkdir -p "\$(dirname '${yamlFile}')"
|
||||
echo "Storing state as yaml"
|
||||
cat > '${yamlFile}' << HERE
|
||||
${yamlBlob}
|
||||
HERE
|
||||
"""
|
||||
}
|
||||
|
||||
|
||||
@@ -1806,13 +1942,10 @@ def publishStatesByConfig(Map args) {
|
||||
.replaceAll('\\$\\{key\\}', key_)
|
||||
def yamlDir = java.nio.file.Paths.get(yamlFilename).getParent()
|
||||
|
||||
// the processed state is a list of [key, value, inputPath, outputFilename] tuples, where
|
||||
// the processed state is a list of [key, value] tuples, where
|
||||
// - key is a String
|
||||
// - value is any object that can be serialized to a Yaml (so a String/Integer/Long/Double/Boolean, a List, a Map, or a Path)
|
||||
// - inputPath is a List[Path]
|
||||
// - outputFilename is a List[String]
|
||||
// - (key, value) are the tuples that will be saved to the state.yaml file
|
||||
// - (inputPath, outputFilename) are the files that will be copied from src to dest (relative to the state.yaml)
|
||||
def processedState =
|
||||
config.allArguments
|
||||
.findAll { it.direction == "output" }
|
||||
@@ -1829,7 +1962,7 @@ def publishStatesByConfig(Map args) {
|
||||
// in the state as-is, but is not something that needs
|
||||
// to be copied from the source path to the dest path
|
||||
if (par.type != "file") {
|
||||
return [[key: plainName_, value: value, inputPath: [], outputFilename: []]]
|
||||
return [[key: plainName_, value: value]]
|
||||
}
|
||||
// if the orig state does not contain this filename,
|
||||
// it's an optional argument for which the user specified
|
||||
@@ -1860,13 +1993,9 @@ def publishStatesByConfig(Map args) {
|
||||
if (yamlDir != null) {
|
||||
value_ = yamlDir.relativize(value_)
|
||||
}
|
||||
def inputPath = val instanceof File ? val.toPath() : val
|
||||
[value: value_, inputPath: inputPath, outputFilename: filename_ix]
|
||||
return value_
|
||||
}
|
||||
def transposedOutputs = ["value", "inputPath", "outputFilename"].collectEntries{ key ->
|
||||
[key, outputPerFile.collect{dic -> dic[key]}]
|
||||
}
|
||||
return [[key: plainName_] + transposedOutputs]
|
||||
return [["key": plainName_, "value": outputPerFile]]
|
||||
} else {
|
||||
def value_ = java.nio.file.Paths.get(filename)
|
||||
// if id contains a slash
|
||||
@@ -1874,18 +2003,17 @@ def publishStatesByConfig(Map args) {
|
||||
value_ = yamlDir.relativize(value_)
|
||||
}
|
||||
def inputPath = value instanceof File ? value.toPath() : value
|
||||
return [[key: plainName_, value: value_, inputPath: [inputPath], outputFilename: [filename]]]
|
||||
return [["key": plainName_, value: value_]]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def updatedState_ = processedState.collectEntries{[it.key, it.value]}
|
||||
def inputPaths = processedState.collectMany{it.inputPath}
|
||||
def outputFilenames = processedState.collectMany{it.outputFilename}
|
||||
|
||||
// convert state to yaml blob
|
||||
def yamlBlob_ = toTaggedYamlBlob([id: id_] + updatedState_)
|
||||
|
||||
[id_, yamlBlob_, yamlFilename, inputPaths, outputFilenames]
|
||||
[id_, yamlBlob_, yamlFilename]
|
||||
}
|
||||
| publishStatesProc
|
||||
emit: input_ch
|
||||
@@ -2559,7 +2687,8 @@ def _debug(workflowArgs, debugKey) {
|
||||
def workflowFactory(Map args, Map defaultWfArgs, Map meta) {
|
||||
def workflowArgs = processWorkflowArgs(args, defaultWfArgs, meta)
|
||||
def key_ = workflowArgs["key"]
|
||||
|
||||
def multipleArgs = meta.config.allArguments.findAll{ it.multiple }.collect{it.plainName}
|
||||
|
||||
workflow workflowInstance {
|
||||
take: input_
|
||||
|
||||
@@ -2716,12 +2845,36 @@ def workflowFactory(Map args, Map defaultWfArgs, Map meta) {
|
||||
}
|
||||
|
||||
// TODO: move some of the _meta.join_id wrangling to the safeJoin() function.
|
||||
def chInitialOutput = chArgsWithDefaults
|
||||
def chInitialOutputMulti = chArgsWithDefaults
|
||||
| _debug(workflowArgs, "processed")
|
||||
// run workflow
|
||||
| innerWorkflowFactory(workflowArgs)
|
||||
// check output tuple
|
||||
| map { id_, output_ ->
|
||||
def chInitialOutputList = chInitialOutputMulti instanceof List ? chInitialOutputMulti : [chInitialOutputMulti]
|
||||
assert chInitialOutputList.size() > 0: "should have emitted at least one output channel"
|
||||
// Add a channel ID to the events, which designates the channel the event was emitted from as a running number
|
||||
// This number is used to sort the events later when the events are gathered from across the channels.
|
||||
def chInitialOutputListWithIndexedEvents = chInitialOutputList.withIndex().collect{channel, channelIndex ->
|
||||
def newChannel = channel
|
||||
| map {tuple ->
|
||||
assert tuple instanceof List :
|
||||
"Error in module '${key_}': element in output channel should be a tuple [id, data, ...otherargs...]\n" +
|
||||
" Example: [\"id\", [input: file('foo.txt'), arg: 10]].\n" +
|
||||
" Expected class: List. Found: tuple.getClass() is ${tuple.getClass()}"
|
||||
|
||||
def newEvent = [channelIndex] + tuple
|
||||
return newEvent
|
||||
}
|
||||
return newChannel
|
||||
}
|
||||
// Put the events into 1 channel, cover case where there is only one channel is emitted
|
||||
def chInitialOutput = chInitialOutputList.size() > 1 ? \
|
||||
chInitialOutputListWithIndexedEvents[0].mix(*chInitialOutputListWithIndexedEvents.tail()) : \
|
||||
chInitialOutputListWithIndexedEvents[0]
|
||||
def chInitialOutputProcessed = chInitialOutput
|
||||
| map { tuple ->
|
||||
def channelId = tuple[0]
|
||||
def id_ = tuple[1]
|
||||
def output_ = tuple[2]
|
||||
|
||||
// see if output map contains metadata
|
||||
def meta_ =
|
||||
@@ -2734,19 +2887,94 @@ def workflowFactory(Map args, Map defaultWfArgs, Map meta) {
|
||||
output_ = output_.findAll{k, v -> k != "_meta"}
|
||||
|
||||
// check value types
|
||||
output_ = _processOutputValues(output_, meta.config, id_, key_)
|
||||
output_ = _checkValidOutputArgument(output_, meta.config, id_, key_)
|
||||
|
||||
// simplify output if need be
|
||||
if (workflowArgs.auto.simplifyOutput && output_.size() == 1) {
|
||||
output_ = output_.values()[0]
|
||||
}
|
||||
|
||||
[join_id, id_, output_]
|
||||
[join_id, channelId, id_, output_]
|
||||
}
|
||||
// | view{"chInitialOutput: ${it.take(3)}"}
|
||||
|
||||
// join the output [prev_id, channel_id, new_id, output] with the previous state [prev_id, state, ...]
|
||||
def chPublishWithPreviousState = safeJoin(chInitialOutputProcessed, chRunFiltered, key_)
|
||||
// input tuple format: [join_id, channel_id, id, output, prev_state, ...]
|
||||
// output tuple format: [join_id, channel_id, id, new_state, ...]
|
||||
| map{ tup ->
|
||||
def new_state = workflowArgs.toState(tup.drop(2).take(3))
|
||||
tup.take(3) + [new_state] + tup.drop(5)
|
||||
}
|
||||
if (workflowArgs.auto.publish == "state") {
|
||||
def chPublishFiles = chPublishWithPreviousState
|
||||
// input tuple format: [join_id, channel_id, id, new_state, ...]
|
||||
// output tuple format: [join_id, channel_id, id, new_state]
|
||||
| map{ tup ->
|
||||
tup.take(4)
|
||||
}
|
||||
|
||||
safeJoin(chPublishFiles, chArgsWithDefaults, key_)
|
||||
// input tuple format: [join_id, channel_id, id, new_state, orig_state, ...]
|
||||
// output tuple format: [id, new_state, orig_state]
|
||||
| map { tup ->
|
||||
tup.drop(2).take(3)
|
||||
}
|
||||
| publishFilesByConfig(key: key_, config: meta.config)
|
||||
}
|
||||
// Join the state from the events that were emitted from different channels
|
||||
def chJoined = chInitialOutputProcessed
|
||||
| map {tuple ->
|
||||
def join_id = tuple[0]
|
||||
def channel_id = tuple[1]
|
||||
def id = tuple[2]
|
||||
def other = tuple.drop(3)
|
||||
// Below, groupTuple is used to join the events. To make sure resuming a workflow
|
||||
// keeps working, the output state must be deterministic. This means the state needs to be
|
||||
// sorted with groupTuple's has a 'sort' argument. This argument can be set to 'hash',
|
||||
// but hashing the state when it is large can be problematic in terms of performance.
|
||||
// Therefore, a custom comparator function is provided. We add the channel ID to the
|
||||
// states so that we can use the channel ID to sort the items.
|
||||
def stateWithChannelID = [[channel_id] * other.size(), other].transpose()
|
||||
// A comparator that is provided to groupTuple's 'sort' argument is applied
|
||||
// to all elements of the event tuple (that is not the 'id'). The comparator
|
||||
// closure that is used below expects the input to be List. So the join_id and
|
||||
// channel_id must also be wrapped in a list.
|
||||
[[join_id], [channel_id], id] + stateWithChannelID
|
||||
}
|
||||
| groupTuple(by: 2, sort: {a, b -> a[0] <=> b[0]}, size: chInitialOutputList.size(), remainder: true)
|
||||
| map {join_ids, _, id, statesWithChannelID ->
|
||||
// Remove the channel IDs from the states
|
||||
def states = statesWithChannelID.collect{it[1]}
|
||||
def newJoinId = join_ids.flatten().unique{a, b -> a <=> b}
|
||||
assert newJoinId.size() == 1: "Multiple events were emitted for '$id'."
|
||||
def newJoinIdUnique = newJoinId[0]
|
||||
|
||||
// Merge the states from the different channels
|
||||
def newState = states.inject([:]){ old_state, state_to_add ->
|
||||
return old_state + state_to_add.collectEntries{k, v ->
|
||||
if (!multipleArgs.contains(k)) {
|
||||
// if the key is not a multiple argument, we expect only one value
|
||||
if (old_state.containsKey(k)) {
|
||||
assert old_state[k] == v : "ID $id: multiple entries for argument $k were emitted."
|
||||
}
|
||||
[k, v]
|
||||
} else {
|
||||
// if the key is a multiple argument, append the different values into one list
|
||||
def prevValue = old_state.getOrDefault(k, [])
|
||||
def prevValueAsList = prevValue instanceof List ? prevValue : [prevValue]
|
||||
[k, prevValueAsList + v]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_checkAllRequiredOuputsPresent(newState, meta.config, id, key_)
|
||||
|
||||
// simplify output if need be
|
||||
if (workflowArgs.auto.simplifyOutput && newState.size() == 1) {
|
||||
newState = newState.values()[0]
|
||||
}
|
||||
|
||||
return [newJoinIdUnique, id, newState]
|
||||
}
|
||||
|
||||
// join the output [prev_id, new_id, output] with the previous state [prev_id, state, ...]
|
||||
def chNewState = safeJoin(chInitialOutput, chRunFiltered, key_)
|
||||
def chNewState = safeJoin(chJoined, chRunFiltered, key_)
|
||||
// input tuple format: [join_id, id, output, prev_state, ...]
|
||||
// output tuple format: [join_id, id, new_state, ...]
|
||||
| map{ tup ->
|
||||
@@ -2755,23 +2983,21 @@ def workflowFactory(Map args, Map defaultWfArgs, Map meta) {
|
||||
}
|
||||
|
||||
if (workflowArgs.auto.publish == "state") {
|
||||
def chPublish = chNewState
|
||||
def chPublishStates = chNewState
|
||||
// input tuple format: [join_id, id, new_state, ...]
|
||||
// output tuple format: [join_id, id, new_state]
|
||||
| map{ tup ->
|
||||
tup.take(3)
|
||||
}
|
||||
|
||||
safeJoin(chPublish, chArgsWithDefaults, key_)
|
||||
safeJoin(chPublishStates, chArgsWithDefaults, key_)
|
||||
// input tuple format: [join_id, id, new_state, orig_state, ...]
|
||||
// output tuple format: [id, new_state, orig_state]
|
||||
| map { tup ->
|
||||
tup.drop(1).take(3)
|
||||
}
|
||||
}
|
||||
| publishStatesByConfig(key: key_, config: meta.config)
|
||||
}
|
||||
|
||||
// remove join_id and meta
|
||||
chReturn = chNewState
|
||||
| map { tup ->
|
||||
// input tuple format: [join_id, id, new_state, ...]
|
||||
@@ -2806,7 +3032,7 @@ meta = [
|
||||
"config": processConfig(readJsonBlob('''{
|
||||
"name" : "combine_samples",
|
||||
"namespace" : "dataflow",
|
||||
"version" : "v0.3.5",
|
||||
"version" : "v0.3.11",
|
||||
"argument_groups" : [
|
||||
{
|
||||
"name" : "Input arguments",
|
||||
@@ -2903,6 +3129,10 @@ meta = [
|
||||
],
|
||||
"description" : "Combine fastq files from across samples into one event with a list of fastq files per orientation.",
|
||||
"status" : "enabled",
|
||||
"scope" : {
|
||||
"image" : "public",
|
||||
"target" : "public"
|
||||
},
|
||||
"requirements" : {
|
||||
"commands" : [
|
||||
"ps"
|
||||
@@ -2999,31 +3229,31 @@ meta = [
|
||||
"runner" : "nextflow",
|
||||
"engine" : "native|native",
|
||||
"output" : "target/nextflow/dataflow/combine_samples",
|
||||
"viash_version" : "0.9.0",
|
||||
"git_commit" : "18e092d169cc2704a2e4e705485d9231911d0484",
|
||||
"viash_version" : "0.9.4",
|
||||
"git_commit" : "cd2cfac18e14622e1a5d7a0d489f64c30e83dab2",
|
||||
"git_remote" : "https://github.com/viash-hub/demultiplex",
|
||||
"git_tag" : "v0.3.4-6-g18e092d"
|
||||
"git_tag" : "v0.3.10-5-gcd2cfac"
|
||||
},
|
||||
"package_config" : {
|
||||
"name" : "demultiplex",
|
||||
"version" : "v0.3.5",
|
||||
"version" : "v0.3.11",
|
||||
"description" : "Demultiplexing pipeline\n",
|
||||
"info" : {
|
||||
"test_resources" : [
|
||||
{
|
||||
"path" : "gs://viash-hub-test-data/demultiplex/v2/",
|
||||
"path" : "gs://viash-hub-resources/demultiplex/v3",
|
||||
"dest" : "testData"
|
||||
}
|
||||
]
|
||||
},
|
||||
"viash_version" : "0.9.0",
|
||||
"viash_version" : "0.9.4",
|
||||
"source" : "src",
|
||||
"target" : "target",
|
||||
"config_mods" : [
|
||||
".requirements.commands := ['ps']\n.runners[.type == 'nextflow'].directives.tag := '$id'\n.resources += {path: '/src/config/labels.config', dest: 'nextflow_labels.config'}\n.runners[.type == 'nextflow'].config.script := 'includeConfig(\\"nextflow_labels.config\\")'\n",
|
||||
".requirements.commands += ['ps']\n.runners[.type == 'nextflow'].directives.tag := '$id'\n.resources += {path: '/src/config/labels.config', dest: 'nextflow_labels.config'}\n.runners[.type == 'nextflow'].config.script := 'includeConfig(\\"nextflow_labels.config\\")'\n",
|
||||
".engines += { type: \\"native\\" }",
|
||||
".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'",
|
||||
".engines[.type == 'docker'].target_tag := 'v0.3.5'"
|
||||
".engines[.type == 'docker'].target_tag := 'v0.3.11'"
|
||||
],
|
||||
"keywords" : [
|
||||
"bioinformatics",
|
||||
|
||||
@@ -2,7 +2,7 @@ manifest {
|
||||
name = 'dataflow/combine_samples'
|
||||
mainScript = 'main.nf'
|
||||
nextflowVersion = '!>=20.12.1-edge'
|
||||
version = 'v0.3.5'
|
||||
version = 'v0.3.11'
|
||||
description = 'Combine fastq files from across samples into one event with a list of fastq files per orientation.'
|
||||
}
|
||||
|
||||
|
||||
@@ -67,10 +67,10 @@
|
||||
"output_forward": {
|
||||
"type":
|
||||
"string",
|
||||
"description": "Type: List of `file`, required, default: `$id.$key.output_forward_*.output_forward_*`, multiple_sep: `\";\"`. ",
|
||||
"help_text": "Type: List of `file`, required, default: `$id.$key.output_forward_*.output_forward_*`, multiple_sep: `\";\"`. "
|
||||
"description": "Type: List of `file`, required, default: `$id.$key.output_forward_*`, multiple_sep: `\";\"`. ",
|
||||
"help_text": "Type: List of `file`, required, default: `$id.$key.output_forward_*`, multiple_sep: `\";\"`. "
|
||||
,
|
||||
"default":"$id.$key.output_forward_*.output_forward_*"
|
||||
"default":"$id.$key.output_forward_*"
|
||||
}
|
||||
|
||||
|
||||
@@ -78,10 +78,10 @@
|
||||
"output_reverse": {
|
||||
"type":
|
||||
"string",
|
||||
"description": "Type: List of `file`, default: `$id.$key.output_reverse_*.output_reverse_*`, multiple_sep: `\";\"`. ",
|
||||
"help_text": "Type: List of `file`, default: `$id.$key.output_reverse_*.output_reverse_*`, multiple_sep: `\";\"`. "
|
||||
"description": "Type: List of `file`, default: `$id.$key.output_reverse_*`, multiple_sep: `\";\"`. ",
|
||||
"help_text": "Type: List of `file`, default: `$id.$key.output_reverse_*`, multiple_sep: `\";\"`. "
|
||||
,
|
||||
"default":"$id.$key.output_reverse_*.output_reverse_*"
|
||||
"default":"$id.$key.output_reverse_*"
|
||||
}
|
||||
|
||||
|
||||
@@ -89,10 +89,10 @@
|
||||
"output_falco": {
|
||||
"type":
|
||||
"string",
|
||||
"description": "Type: List of `file`, required, default: `$id.$key.output_falco_*.output_falco_*`, multiple_sep: `\";\"`. ",
|
||||
"help_text": "Type: List of `file`, required, default: `$id.$key.output_falco_*.output_falco_*`, multiple_sep: `\";\"`. "
|
||||
"description": "Type: List of `file`, required, default: `$id.$key.output_falco_*`, multiple_sep: `\";\"`. ",
|
||||
"help_text": "Type: List of `file`, required, default: `$id.$key.output_falco_*`, multiple_sep: `\";\"`. "
|
||||
,
|
||||
"default":"$id.$key.output_falco_*.output_falco_*"
|
||||
"default":"$id.$key.output_falco_*"
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: "gather_fastqs_and_validate"
|
||||
namespace: "dataflow"
|
||||
version: "v0.3.5"
|
||||
version: "v0.3.11"
|
||||
argument_groups:
|
||||
- name: "Input arguments"
|
||||
arguments:
|
||||
@@ -56,6 +56,9 @@ description: "From a directory containing fastq files, gather the files per samp
|
||||
\ \nand validate according to the contents of the sample sheet.\n"
|
||||
info: null
|
||||
status: "enabled"
|
||||
scope:
|
||||
image: "public"
|
||||
target: "public"
|
||||
requirements:
|
||||
commands:
|
||||
- "ps"
|
||||
@@ -137,29 +140,29 @@ build_info:
|
||||
engine: "native|native"
|
||||
output: "target/nextflow/dataflow/gather_fastqs_and_validate"
|
||||
executable: "target/nextflow/dataflow/gather_fastqs_and_validate/main.nf"
|
||||
viash_version: "0.9.0"
|
||||
git_commit: "18e092d169cc2704a2e4e705485d9231911d0484"
|
||||
viash_version: "0.9.4"
|
||||
git_commit: "cd2cfac18e14622e1a5d7a0d489f64c30e83dab2"
|
||||
git_remote: "https://github.com/viash-hub/demultiplex"
|
||||
git_tag: "v0.3.4-6-g18e092d"
|
||||
git_tag: "v0.3.10-5-gcd2cfac"
|
||||
package_config:
|
||||
name: "demultiplex"
|
||||
version: "v0.3.5"
|
||||
version: "v0.3.11"
|
||||
description: "Demultiplexing pipeline\n"
|
||||
info:
|
||||
test_resources:
|
||||
- path: "gs://viash-hub-test-data/demultiplex/v2/"
|
||||
- path: "gs://viash-hub-resources/demultiplex/v3"
|
||||
dest: "testData"
|
||||
viash_version: "0.9.0"
|
||||
viash_version: "0.9.4"
|
||||
source: "src"
|
||||
target: "target"
|
||||
config_mods:
|
||||
- ".requirements.commands := ['ps']\n.runners[.type == 'nextflow'].directives.tag\
|
||||
- ".requirements.commands += ['ps']\n.runners[.type == 'nextflow'].directives.tag\
|
||||
\ := '$id'\n.resources += {path: '/src/config/labels.config', dest: 'nextflow_labels.config'}\n\
|
||||
.runners[.type == 'nextflow'].config.script := 'includeConfig(\"nextflow_labels.config\"\
|
||||
)'\n"
|
||||
- ".engines += { type: \"native\" }"
|
||||
- ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'"
|
||||
- ".engines[.type == 'docker'].target_tag := 'v0.3.5'"
|
||||
- ".engines[.type == 'docker'].target_tag := 'v0.3.11'"
|
||||
keywords:
|
||||
- "bioinformatics"
|
||||
- "sequence"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// gather_fastqs_and_validate v0.3.5
|
||||
// gather_fastqs_and_validate v0.3.11
|
||||
//
|
||||
// This wrapper script is auto-generated by viash 0.9.0 and is thus a derivative
|
||||
// This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative
|
||||
// work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data
|
||||
// Intuitive.
|
||||
//
|
||||
@@ -82,64 +82,56 @@ def _checkArgumentType(String stage, Map par, Object value, String errorIdentifi
|
||||
foundClass = "List[${e.foundClass}]"
|
||||
}
|
||||
} else if (par.type == "string") {
|
||||
// cast to string if need be
|
||||
// cast to string if need be. only cast if the value is a GString
|
||||
if (value instanceof GString) {
|
||||
value = value.toString()
|
||||
value = value as String
|
||||
}
|
||||
expectedClass = value instanceof String ? null : "String"
|
||||
} else if (par.type == "integer") {
|
||||
// cast to integer if need be
|
||||
if (value instanceof String) {
|
||||
if (value !instanceof Integer) {
|
||||
try {
|
||||
value = value.toInteger()
|
||||
value = value as Integer
|
||||
} catch (NumberFormatException e) {
|
||||
// do nothing
|
||||
expectedClass = "Integer"
|
||||
}
|
||||
}
|
||||
if (value instanceof java.math.BigInteger) {
|
||||
value = value.intValue()
|
||||
}
|
||||
expectedClass = value instanceof Integer ? null : "Integer"
|
||||
} else if (par.type == "long") {
|
||||
// cast to long if need be
|
||||
if (value instanceof String) {
|
||||
if (value !instanceof Long) {
|
||||
try {
|
||||
value = value.toLong()
|
||||
value = value as Long
|
||||
} catch (NumberFormatException e) {
|
||||
// do nothing
|
||||
expectedClass = "Long"
|
||||
}
|
||||
}
|
||||
if (value instanceof Integer) {
|
||||
value = value.toLong()
|
||||
}
|
||||
expectedClass = value instanceof Long ? null : "Long"
|
||||
} else if (par.type == "double") {
|
||||
// cast to double if need be
|
||||
if (value instanceof String) {
|
||||
if (value !instanceof Double) {
|
||||
try {
|
||||
value = value.toDouble()
|
||||
value = value as Double
|
||||
} catch (NumberFormatException e) {
|
||||
// do nothing
|
||||
expectedClass = "Double"
|
||||
}
|
||||
}
|
||||
if (value instanceof java.math.BigDecimal) {
|
||||
value = value.doubleValue()
|
||||
} else if (par.type == "float") {
|
||||
// cast to float if need be
|
||||
if (value !instanceof Float) {
|
||||
try {
|
||||
value = value as Float
|
||||
} catch (NumberFormatException e) {
|
||||
expectedClass = "Float"
|
||||
}
|
||||
}
|
||||
if (value instanceof Float) {
|
||||
value = value.toDouble()
|
||||
}
|
||||
expectedClass = value instanceof Double ? null : "Double"
|
||||
} else if (par.type == "boolean" | par.type == "boolean_true" | par.type == "boolean_false") {
|
||||
// cast to boolean if need be
|
||||
if (value instanceof String) {
|
||||
def valueLower = value.toLowerCase()
|
||||
if (valueLower == "true") {
|
||||
value = true
|
||||
} else if (valueLower == "false") {
|
||||
value = false
|
||||
if (value !instanceof Boolean) {
|
||||
try {
|
||||
value = value as Boolean
|
||||
} catch (Exception e) {
|
||||
expectedClass = "Boolean"
|
||||
}
|
||||
}
|
||||
expectedClass = value instanceof Boolean ? null : "Boolean"
|
||||
} else if (par.type == "file" && (par.direction == "input" || stage == "output")) {
|
||||
// cast to path if need be
|
||||
if (value instanceof String) {
|
||||
@@ -151,10 +143,13 @@ def _checkArgumentType(String stage, Map par, Object value, String errorIdentifi
|
||||
expectedClass = value instanceof Path ? null : "Path"
|
||||
} else if (par.type == "file" && stage == "input" && par.direction == "output") {
|
||||
// cast to string if need be
|
||||
if (value instanceof GString) {
|
||||
value = value.toString()
|
||||
if (value !instanceof String) {
|
||||
try {
|
||||
value = value as String
|
||||
} catch (Exception e) {
|
||||
expectedClass = "String"
|
||||
}
|
||||
}
|
||||
expectedClass = value instanceof String ? null : "String"
|
||||
} else {
|
||||
// didn't find a match for par.type
|
||||
expectedClass = par.type
|
||||
@@ -173,7 +168,7 @@ def _checkArgumentType(String stage, Map par, Object value, String errorIdentifi
|
||||
Map _processInputValues(Map inputs, Map config, String id, String key) {
|
||||
if (!workflow.stubRun) {
|
||||
config.allArguments.each { arg ->
|
||||
if (arg.required) {
|
||||
if (arg.required && arg.direction == "input") {
|
||||
assert inputs.containsKey(arg.plainName) && inputs.get(arg.plainName) != null :
|
||||
"Error in module '${key}' id '${id}': required input argument '${arg.plainName}' is missing"
|
||||
}
|
||||
@@ -192,15 +187,8 @@ Map _processInputValues(Map inputs, Map config, String id, String key) {
|
||||
}
|
||||
|
||||
// helper file: 'src/main/resources/io/viash/runners/nextflow/arguments/_processOutputValues.nf'
|
||||
Map _processOutputValues(Map outputs, Map config, String id, String key) {
|
||||
Map _checkValidOutputArgument(Map outputs, Map config, String id, String key) {
|
||||
if (!workflow.stubRun) {
|
||||
config.allArguments.each { arg ->
|
||||
if (arg.direction == "output" && arg.required) {
|
||||
assert outputs.containsKey(arg.plainName) && outputs.get(arg.plainName) != null :
|
||||
"Error in module '${key}' id '${id}': required output argument '${arg.plainName}' is missing"
|
||||
}
|
||||
}
|
||||
|
||||
outputs = outputs.collectEntries { name, value ->
|
||||
def par = config.allArguments.find { it.plainName == name && it.direction == "output" }
|
||||
assert par != null : "Error in module '${key}' id '${id}': '${name}' is not a valid output argument"
|
||||
@@ -213,6 +201,16 @@ Map _processOutputValues(Map outputs, Map config, String id, String key) {
|
||||
return outputs
|
||||
}
|
||||
|
||||
void _checkAllRequiredOuputsPresent(Map outputs, Map config, String id, String key) {
|
||||
if (!workflow.stubRun) {
|
||||
config.allArguments.each { arg ->
|
||||
if (arg.direction == "output" && arg.required) {
|
||||
assert outputs.containsKey(arg.plainName) && outputs.get(arg.plainName) != null :
|
||||
"Error in module '${key}' id '${id}': required output argument '${arg.plainName}' is missing"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// helper file: 'src/main/resources/io/viash/runners/nextflow/channel/IDChecker.nf'
|
||||
class IDChecker {
|
||||
final def items = [] as Set
|
||||
@@ -1666,6 +1664,162 @@ def joinStates(Closure apply_) {
|
||||
}
|
||||
return joinStatesWf
|
||||
}
|
||||
// helper file: 'src/main/resources/io/viash/runners/nextflow/states/publishFiles.nf'
|
||||
def publishFiles(Map args) {
|
||||
def key_ = args.get("key")
|
||||
|
||||
assert key_ != null : "publishFiles: key must be specified"
|
||||
|
||||
workflow publishFilesWf {
|
||||
take: input_ch
|
||||
main:
|
||||
input_ch
|
||||
| map { tup ->
|
||||
def id_ = tup[0]
|
||||
def state_ = tup[1]
|
||||
|
||||
// the input files and the target output filenames
|
||||
def inputoutputFilenames_ = collectInputOutputPaths(state_, id_ + "." + key_).transpose()
|
||||
def inputFiles_ = inputoutputFilenames_[0]
|
||||
def outputFilenames_ = inputoutputFilenames_[1]
|
||||
|
||||
[id_, inputFiles_, outputFilenames_]
|
||||
}
|
||||
| publishFilesProc
|
||||
emit: input_ch
|
||||
}
|
||||
return publishFilesWf
|
||||
}
|
||||
|
||||
process publishFilesProc {
|
||||
// todo: check publishpath?
|
||||
publishDir path: "${getPublishDir()}/", mode: "copy"
|
||||
tag "$id"
|
||||
input:
|
||||
tuple val(id), path(inputFiles, stageAs: "_inputfile?/*"), val(outputFiles)
|
||||
output:
|
||||
tuple val(id), path{outputFiles}
|
||||
script:
|
||||
def copyCommands = [
|
||||
inputFiles instanceof List ? inputFiles : [inputFiles],
|
||||
outputFiles instanceof List ? outputFiles : [outputFiles]
|
||||
]
|
||||
.transpose()
|
||||
.collectMany{infile, outfile ->
|
||||
if (infile.toString() != outfile.toString()) {
|
||||
[
|
||||
"[ -d \"\$(dirname '${outfile.toString()}')\" ] || mkdir -p \"\$(dirname '${outfile.toString()}')\"",
|
||||
"cp -r '${infile.toString()}' '${outfile.toString()}'"
|
||||
]
|
||||
} else {
|
||||
// no need to copy if infile is the same as outfile
|
||||
[]
|
||||
}
|
||||
}
|
||||
"""
|
||||
echo "Copying output files to destination folder"
|
||||
${copyCommands.join("\n ")}
|
||||
"""
|
||||
}
|
||||
|
||||
|
||||
// this assumes that the state contains no other values other than those specified in the config
|
||||
def publishFilesByConfig(Map args) {
|
||||
def config = args.get("config")
|
||||
assert config != null : "publishFilesByConfig: config must be specified"
|
||||
|
||||
def key_ = args.get("key", config.name)
|
||||
assert key_ != null : "publishFilesByConfig: key must be specified"
|
||||
|
||||
workflow publishFilesSimpleWf {
|
||||
take: input_ch
|
||||
main:
|
||||
input_ch
|
||||
| map { tup ->
|
||||
def id_ = tup[0]
|
||||
def state_ = tup[1] // e.g. [output: new File("myoutput.h5ad"), k: 10]
|
||||
def origState_ = tup[2] // e.g. [output: '$id.$key.foo.h5ad']
|
||||
|
||||
|
||||
// the processed state is a list of [key, value, inputPath, outputFilename] tuples, where
|
||||
// - key is a String
|
||||
// - value is any object that can be serialized to a Yaml (so a String/Integer/Long/Double/Boolean, a List, a Map, or a Path)
|
||||
// - inputPath is a List[Path]
|
||||
// - outputFilename is a List[String]
|
||||
// - (inputPath, outputFilename) are the files that will be copied from src to dest (relative to the state.yaml)
|
||||
def processedState =
|
||||
config.allArguments
|
||||
.findAll { it.direction == "output" }
|
||||
.collectMany { par ->
|
||||
def plainName_ = par.plainName
|
||||
// if the state does not contain the key, it's an
|
||||
// optional argument for which the component did
|
||||
// not generate any output OR multiple channels were emitted
|
||||
// and the output was just not added to using the channel
|
||||
// that is now being parsed
|
||||
if (!state_.containsKey(plainName_)) {
|
||||
return []
|
||||
}
|
||||
def value = state_[plainName_]
|
||||
// if the parameter is not a file, it should be stored
|
||||
// in the state as-is, but is not something that needs
|
||||
// to be copied from the source path to the dest path
|
||||
if (par.type != "file") {
|
||||
return [[inputPath: [], outputFilename: []]]
|
||||
}
|
||||
// if the orig state does not contain this filename,
|
||||
// it's an optional argument for which the user specified
|
||||
// that it should not be returned as a state
|
||||
if (!origState_.containsKey(plainName_)) {
|
||||
return []
|
||||
}
|
||||
def filenameTemplate = origState_[plainName_]
|
||||
// if the pararameter is multiple: true, fetch the template
|
||||
if (par.multiple && filenameTemplate instanceof List) {
|
||||
filenameTemplate = filenameTemplate[0]
|
||||
}
|
||||
// instantiate the template
|
||||
def filename = filenameTemplate
|
||||
.replaceAll('\\$id', id_)
|
||||
.replaceAll('\\$\\{id\\}', id_)
|
||||
.replaceAll('\\$key', key_)
|
||||
.replaceAll('\\$\\{key\\}', key_)
|
||||
if (par.multiple) {
|
||||
// if the parameter is multiple: true, the filename
|
||||
// should contain a wildcard '*' that is replaced with
|
||||
// the index of the file
|
||||
assert filename.contains("*") : "Module '${key_}' id '${id_}': Multiple output files specified, but no wildcard '*' in the filename: ${filename}"
|
||||
def outputPerFile = value.withIndex().collect{ val, ix ->
|
||||
def filename_ix = filename.replace("*", ix.toString())
|
||||
def inputPath = val instanceof File ? val.toPath() : val
|
||||
[inputPath: inputPath, outputFilename: filename_ix]
|
||||
}
|
||||
def transposedOutputs = ["inputPath", "outputFilename"].collectEntries{ key ->
|
||||
[key, outputPerFile.collect{dic -> dic[key]}]
|
||||
}
|
||||
return [[key: plainName_] + transposedOutputs]
|
||||
} else {
|
||||
def value_ = java.nio.file.Paths.get(filename)
|
||||
def inputPath = value instanceof File ? value.toPath() : value
|
||||
return [[inputPath: [inputPath], outputFilename: [filename]]]
|
||||
}
|
||||
}
|
||||
|
||||
def inputPaths = processedState.collectMany{it.inputPath}
|
||||
def outputFilenames = processedState.collectMany{it.outputFilename}
|
||||
|
||||
|
||||
[id_, inputPaths, outputFilenames]
|
||||
}
|
||||
| publishFilesProc
|
||||
emit: input_ch
|
||||
}
|
||||
return publishFilesSimpleWf
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// helper file: 'src/main/resources/io/viash/runners/nextflow/states/publishStates.nf'
|
||||
def collectFiles(obj) {
|
||||
if (obj instanceof java.io.File || obj instanceof Path) {
|
||||
@@ -1723,8 +1877,6 @@ def publishStates(Map args) {
|
||||
|
||||
// the input files and the target output filenames
|
||||
def inputoutputFilenames_ = collectInputOutputPaths(state_, id_ + "." + key_).transpose()
|
||||
def inputFiles_ = inputoutputFilenames_[0]
|
||||
def outputFilenames_ = inputoutputFilenames_[1]
|
||||
|
||||
def yamlFilename = yamlTemplate_
|
||||
.replaceAll('\\$id', id_)
|
||||
@@ -1737,7 +1889,7 @@ def publishStates(Map args) {
|
||||
// convert state to yaml blob
|
||||
def yamlBlob_ = toRelativeTaggedYamlBlob([id: id_] + state_, java.nio.file.Paths.get(yamlFilename))
|
||||
|
||||
[id_, yamlBlob_, yamlFilename, inputFiles_, outputFilenames_]
|
||||
[id_, yamlBlob_, yamlFilename]
|
||||
}
|
||||
| publishStatesProc
|
||||
emit: input_ch
|
||||
@@ -1749,33 +1901,17 @@ process publishStatesProc {
|
||||
publishDir path: "${getPublishDir()}/", mode: "copy"
|
||||
tag "$id"
|
||||
input:
|
||||
tuple val(id), val(yamlBlob), val(yamlFile), path(inputFiles, stageAs: "_inputfile?/*"), val(outputFiles)
|
||||
tuple val(id), val(yamlBlob), val(yamlFile)
|
||||
output:
|
||||
tuple val(id), path{[yamlFile] + outputFiles}
|
||||
tuple val(id), path{[yamlFile]}
|
||||
script:
|
||||
def copyCommands = [
|
||||
inputFiles instanceof List ? inputFiles : [inputFiles],
|
||||
outputFiles instanceof List ? outputFiles : [outputFiles]
|
||||
]
|
||||
.transpose()
|
||||
.collectMany{infile, outfile ->
|
||||
if (infile.toString() != outfile.toString()) {
|
||||
[
|
||||
"[ -d \"\$(dirname '${outfile.toString()}')\" ] || mkdir -p \"\$(dirname '${outfile.toString()}')\"",
|
||||
"cp -r '${infile.toString()}' '${outfile.toString()}'"
|
||||
]
|
||||
} else {
|
||||
// no need to copy if infile is the same as outfile
|
||||
[]
|
||||
}
|
||||
}
|
||||
"""
|
||||
mkdir -p "\$(dirname '${yamlFile}')"
|
||||
echo "Storing state as yaml"
|
||||
echo '${yamlBlob}' > '${yamlFile}'
|
||||
echo "Copying output files to destination folder"
|
||||
${copyCommands.join("\n ")}
|
||||
"""
|
||||
mkdir -p "\$(dirname '${yamlFile}')"
|
||||
echo "Storing state as yaml"
|
||||
cat > '${yamlFile}' << HERE
|
||||
${yamlBlob}
|
||||
HERE
|
||||
"""
|
||||
}
|
||||
|
||||
|
||||
@@ -1806,13 +1942,10 @@ def publishStatesByConfig(Map args) {
|
||||
.replaceAll('\\$\\{key\\}', key_)
|
||||
def yamlDir = java.nio.file.Paths.get(yamlFilename).getParent()
|
||||
|
||||
// the processed state is a list of [key, value, inputPath, outputFilename] tuples, where
|
||||
// the processed state is a list of [key, value] tuples, where
|
||||
// - key is a String
|
||||
// - value is any object that can be serialized to a Yaml (so a String/Integer/Long/Double/Boolean, a List, a Map, or a Path)
|
||||
// - inputPath is a List[Path]
|
||||
// - outputFilename is a List[String]
|
||||
// - (key, value) are the tuples that will be saved to the state.yaml file
|
||||
// - (inputPath, outputFilename) are the files that will be copied from src to dest (relative to the state.yaml)
|
||||
def processedState =
|
||||
config.allArguments
|
||||
.findAll { it.direction == "output" }
|
||||
@@ -1829,7 +1962,7 @@ def publishStatesByConfig(Map args) {
|
||||
// in the state as-is, but is not something that needs
|
||||
// to be copied from the source path to the dest path
|
||||
if (par.type != "file") {
|
||||
return [[key: plainName_, value: value, inputPath: [], outputFilename: []]]
|
||||
return [[key: plainName_, value: value]]
|
||||
}
|
||||
// if the orig state does not contain this filename,
|
||||
// it's an optional argument for which the user specified
|
||||
@@ -1860,13 +1993,9 @@ def publishStatesByConfig(Map args) {
|
||||
if (yamlDir != null) {
|
||||
value_ = yamlDir.relativize(value_)
|
||||
}
|
||||
def inputPath = val instanceof File ? val.toPath() : val
|
||||
[value: value_, inputPath: inputPath, outputFilename: filename_ix]
|
||||
return value_
|
||||
}
|
||||
def transposedOutputs = ["value", "inputPath", "outputFilename"].collectEntries{ key ->
|
||||
[key, outputPerFile.collect{dic -> dic[key]}]
|
||||
}
|
||||
return [[key: plainName_] + transposedOutputs]
|
||||
return [["key": plainName_, "value": outputPerFile]]
|
||||
} else {
|
||||
def value_ = java.nio.file.Paths.get(filename)
|
||||
// if id contains a slash
|
||||
@@ -1874,18 +2003,17 @@ def publishStatesByConfig(Map args) {
|
||||
value_ = yamlDir.relativize(value_)
|
||||
}
|
||||
def inputPath = value instanceof File ? value.toPath() : value
|
||||
return [[key: plainName_, value: value_, inputPath: [inputPath], outputFilename: [filename]]]
|
||||
return [["key": plainName_, value: value_]]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def updatedState_ = processedState.collectEntries{[it.key, it.value]}
|
||||
def inputPaths = processedState.collectMany{it.inputPath}
|
||||
def outputFilenames = processedState.collectMany{it.outputFilename}
|
||||
|
||||
// convert state to yaml blob
|
||||
def yamlBlob_ = toTaggedYamlBlob([id: id_] + updatedState_)
|
||||
|
||||
[id_, yamlBlob_, yamlFilename, inputPaths, outputFilenames]
|
||||
[id_, yamlBlob_, yamlFilename]
|
||||
}
|
||||
| publishStatesProc
|
||||
emit: input_ch
|
||||
@@ -2559,7 +2687,8 @@ def _debug(workflowArgs, debugKey) {
|
||||
def workflowFactory(Map args, Map defaultWfArgs, Map meta) {
|
||||
def workflowArgs = processWorkflowArgs(args, defaultWfArgs, meta)
|
||||
def key_ = workflowArgs["key"]
|
||||
|
||||
def multipleArgs = meta.config.allArguments.findAll{ it.multiple }.collect{it.plainName}
|
||||
|
||||
workflow workflowInstance {
|
||||
take: input_
|
||||
|
||||
@@ -2716,12 +2845,36 @@ def workflowFactory(Map args, Map defaultWfArgs, Map meta) {
|
||||
}
|
||||
|
||||
// TODO: move some of the _meta.join_id wrangling to the safeJoin() function.
|
||||
def chInitialOutput = chArgsWithDefaults
|
||||
def chInitialOutputMulti = chArgsWithDefaults
|
||||
| _debug(workflowArgs, "processed")
|
||||
// run workflow
|
||||
| innerWorkflowFactory(workflowArgs)
|
||||
// check output tuple
|
||||
| map { id_, output_ ->
|
||||
def chInitialOutputList = chInitialOutputMulti instanceof List ? chInitialOutputMulti : [chInitialOutputMulti]
|
||||
assert chInitialOutputList.size() > 0: "should have emitted at least one output channel"
|
||||
// Add a channel ID to the events, which designates the channel the event was emitted from as a running number
|
||||
// This number is used to sort the events later when the events are gathered from across the channels.
|
||||
def chInitialOutputListWithIndexedEvents = chInitialOutputList.withIndex().collect{channel, channelIndex ->
|
||||
def newChannel = channel
|
||||
| map {tuple ->
|
||||
assert tuple instanceof List :
|
||||
"Error in module '${key_}': element in output channel should be a tuple [id, data, ...otherargs...]\n" +
|
||||
" Example: [\"id\", [input: file('foo.txt'), arg: 10]].\n" +
|
||||
" Expected class: List. Found: tuple.getClass() is ${tuple.getClass()}"
|
||||
|
||||
def newEvent = [channelIndex] + tuple
|
||||
return newEvent
|
||||
}
|
||||
return newChannel
|
||||
}
|
||||
// Put the events into 1 channel, cover case where there is only one channel is emitted
|
||||
def chInitialOutput = chInitialOutputList.size() > 1 ? \
|
||||
chInitialOutputListWithIndexedEvents[0].mix(*chInitialOutputListWithIndexedEvents.tail()) : \
|
||||
chInitialOutputListWithIndexedEvents[0]
|
||||
def chInitialOutputProcessed = chInitialOutput
|
||||
| map { tuple ->
|
||||
def channelId = tuple[0]
|
||||
def id_ = tuple[1]
|
||||
def output_ = tuple[2]
|
||||
|
||||
// see if output map contains metadata
|
||||
def meta_ =
|
||||
@@ -2734,19 +2887,94 @@ def workflowFactory(Map args, Map defaultWfArgs, Map meta) {
|
||||
output_ = output_.findAll{k, v -> k != "_meta"}
|
||||
|
||||
// check value types
|
||||
output_ = _processOutputValues(output_, meta.config, id_, key_)
|
||||
output_ = _checkValidOutputArgument(output_, meta.config, id_, key_)
|
||||
|
||||
// simplify output if need be
|
||||
if (workflowArgs.auto.simplifyOutput && output_.size() == 1) {
|
||||
output_ = output_.values()[0]
|
||||
}
|
||||
|
||||
[join_id, id_, output_]
|
||||
[join_id, channelId, id_, output_]
|
||||
}
|
||||
// | view{"chInitialOutput: ${it.take(3)}"}
|
||||
|
||||
// join the output [prev_id, channel_id, new_id, output] with the previous state [prev_id, state, ...]
|
||||
def chPublishWithPreviousState = safeJoin(chInitialOutputProcessed, chRunFiltered, key_)
|
||||
// input tuple format: [join_id, channel_id, id, output, prev_state, ...]
|
||||
// output tuple format: [join_id, channel_id, id, new_state, ...]
|
||||
| map{ tup ->
|
||||
def new_state = workflowArgs.toState(tup.drop(2).take(3))
|
||||
tup.take(3) + [new_state] + tup.drop(5)
|
||||
}
|
||||
if (workflowArgs.auto.publish == "state") {
|
||||
def chPublishFiles = chPublishWithPreviousState
|
||||
// input tuple format: [join_id, channel_id, id, new_state, ...]
|
||||
// output tuple format: [join_id, channel_id, id, new_state]
|
||||
| map{ tup ->
|
||||
tup.take(4)
|
||||
}
|
||||
|
||||
safeJoin(chPublishFiles, chArgsWithDefaults, key_)
|
||||
// input tuple format: [join_id, channel_id, id, new_state, orig_state, ...]
|
||||
// output tuple format: [id, new_state, orig_state]
|
||||
| map { tup ->
|
||||
tup.drop(2).take(3)
|
||||
}
|
||||
| publishFilesByConfig(key: key_, config: meta.config)
|
||||
}
|
||||
// Join the state from the events that were emitted from different channels
|
||||
def chJoined = chInitialOutputProcessed
|
||||
| map {tuple ->
|
||||
def join_id = tuple[0]
|
||||
def channel_id = tuple[1]
|
||||
def id = tuple[2]
|
||||
def other = tuple.drop(3)
|
||||
// Below, groupTuple is used to join the events. To make sure resuming a workflow
|
||||
// keeps working, the output state must be deterministic. This means the state needs to be
|
||||
// sorted with groupTuple's has a 'sort' argument. This argument can be set to 'hash',
|
||||
// but hashing the state when it is large can be problematic in terms of performance.
|
||||
// Therefore, a custom comparator function is provided. We add the channel ID to the
|
||||
// states so that we can use the channel ID to sort the items.
|
||||
def stateWithChannelID = [[channel_id] * other.size(), other].transpose()
|
||||
// A comparator that is provided to groupTuple's 'sort' argument is applied
|
||||
// to all elements of the event tuple (that is not the 'id'). The comparator
|
||||
// closure that is used below expects the input to be List. So the join_id and
|
||||
// channel_id must also be wrapped in a list.
|
||||
[[join_id], [channel_id], id] + stateWithChannelID
|
||||
}
|
||||
| groupTuple(by: 2, sort: {a, b -> a[0] <=> b[0]}, size: chInitialOutputList.size(), remainder: true)
|
||||
| map {join_ids, _, id, statesWithChannelID ->
|
||||
// Remove the channel IDs from the states
|
||||
def states = statesWithChannelID.collect{it[1]}
|
||||
def newJoinId = join_ids.flatten().unique{a, b -> a <=> b}
|
||||
assert newJoinId.size() == 1: "Multiple events were emitted for '$id'."
|
||||
def newJoinIdUnique = newJoinId[0]
|
||||
|
||||
// Merge the states from the different channels
|
||||
def newState = states.inject([:]){ old_state, state_to_add ->
|
||||
return old_state + state_to_add.collectEntries{k, v ->
|
||||
if (!multipleArgs.contains(k)) {
|
||||
// if the key is not a multiple argument, we expect only one value
|
||||
if (old_state.containsKey(k)) {
|
||||
assert old_state[k] == v : "ID $id: multiple entries for argument $k were emitted."
|
||||
}
|
||||
[k, v]
|
||||
} else {
|
||||
// if the key is a multiple argument, append the different values into one list
|
||||
def prevValue = old_state.getOrDefault(k, [])
|
||||
def prevValueAsList = prevValue instanceof List ? prevValue : [prevValue]
|
||||
[k, prevValueAsList + v]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_checkAllRequiredOuputsPresent(newState, meta.config, id, key_)
|
||||
|
||||
// simplify output if need be
|
||||
if (workflowArgs.auto.simplifyOutput && newState.size() == 1) {
|
||||
newState = newState.values()[0]
|
||||
}
|
||||
|
||||
return [newJoinIdUnique, id, newState]
|
||||
}
|
||||
|
||||
// join the output [prev_id, new_id, output] with the previous state [prev_id, state, ...]
|
||||
def chNewState = safeJoin(chInitialOutput, chRunFiltered, key_)
|
||||
def chNewState = safeJoin(chJoined, chRunFiltered, key_)
|
||||
// input tuple format: [join_id, id, output, prev_state, ...]
|
||||
// output tuple format: [join_id, id, new_state, ...]
|
||||
| map{ tup ->
|
||||
@@ -2755,23 +2983,21 @@ def workflowFactory(Map args, Map defaultWfArgs, Map meta) {
|
||||
}
|
||||
|
||||
if (workflowArgs.auto.publish == "state") {
|
||||
def chPublish = chNewState
|
||||
def chPublishStates = chNewState
|
||||
// input tuple format: [join_id, id, new_state, ...]
|
||||
// output tuple format: [join_id, id, new_state]
|
||||
| map{ tup ->
|
||||
tup.take(3)
|
||||
}
|
||||
|
||||
safeJoin(chPublish, chArgsWithDefaults, key_)
|
||||
safeJoin(chPublishStates, chArgsWithDefaults, key_)
|
||||
// input tuple format: [join_id, id, new_state, orig_state, ...]
|
||||
// output tuple format: [id, new_state, orig_state]
|
||||
| map { tup ->
|
||||
tup.drop(1).take(3)
|
||||
}
|
||||
}
|
||||
| publishStatesByConfig(key: key_, config: meta.config)
|
||||
}
|
||||
|
||||
// remove join_id and meta
|
||||
chReturn = chNewState
|
||||
| map { tup ->
|
||||
// input tuple format: [join_id, id, new_state, ...]
|
||||
@@ -2806,7 +3032,7 @@ meta = [
|
||||
"config": processConfig(readJsonBlob('''{
|
||||
"name" : "gather_fastqs_and_validate",
|
||||
"namespace" : "dataflow",
|
||||
"version" : "v0.3.5",
|
||||
"version" : "v0.3.11",
|
||||
"argument_groups" : [
|
||||
{
|
||||
"name" : "Input arguments",
|
||||
@@ -2876,6 +3102,10 @@ meta = [
|
||||
],
|
||||
"description" : "From a directory containing fastq files, gather the files per sample \nand validate according to the contents of the sample sheet.\n",
|
||||
"status" : "enabled",
|
||||
"scope" : {
|
||||
"image" : "public",
|
||||
"target" : "public"
|
||||
},
|
||||
"requirements" : {
|
||||
"commands" : [
|
||||
"ps"
|
||||
@@ -2972,31 +3202,31 @@ meta = [
|
||||
"runner" : "nextflow",
|
||||
"engine" : "native|native",
|
||||
"output" : "target/nextflow/dataflow/gather_fastqs_and_validate",
|
||||
"viash_version" : "0.9.0",
|
||||
"git_commit" : "18e092d169cc2704a2e4e705485d9231911d0484",
|
||||
"viash_version" : "0.9.4",
|
||||
"git_commit" : "cd2cfac18e14622e1a5d7a0d489f64c30e83dab2",
|
||||
"git_remote" : "https://github.com/viash-hub/demultiplex",
|
||||
"git_tag" : "v0.3.4-6-g18e092d"
|
||||
"git_tag" : "v0.3.10-5-gcd2cfac"
|
||||
},
|
||||
"package_config" : {
|
||||
"name" : "demultiplex",
|
||||
"version" : "v0.3.5",
|
||||
"version" : "v0.3.11",
|
||||
"description" : "Demultiplexing pipeline\n",
|
||||
"info" : {
|
||||
"test_resources" : [
|
||||
{
|
||||
"path" : "gs://viash-hub-test-data/demultiplex/v2/",
|
||||
"path" : "gs://viash-hub-resources/demultiplex/v3",
|
||||
"dest" : "testData"
|
||||
}
|
||||
]
|
||||
},
|
||||
"viash_version" : "0.9.0",
|
||||
"viash_version" : "0.9.4",
|
||||
"source" : "src",
|
||||
"target" : "target",
|
||||
"config_mods" : [
|
||||
".requirements.commands := ['ps']\n.runners[.type == 'nextflow'].directives.tag := '$id'\n.resources += {path: '/src/config/labels.config', dest: 'nextflow_labels.config'}\n.runners[.type == 'nextflow'].config.script := 'includeConfig(\\"nextflow_labels.config\\")'\n",
|
||||
".requirements.commands += ['ps']\n.runners[.type == 'nextflow'].directives.tag := '$id'\n.resources += {path: '/src/config/labels.config', dest: 'nextflow_labels.config'}\n.runners[.type == 'nextflow'].config.script := 'includeConfig(\\"nextflow_labels.config\\")'\n",
|
||||
".engines += { type: \\"native\\" }",
|
||||
".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'",
|
||||
".engines[.type == 'docker'].target_tag := 'v0.3.5'"
|
||||
".engines[.type == 'docker'].target_tag := 'v0.3.11'"
|
||||
],
|
||||
"keywords" : [
|
||||
"bioinformatics",
|
||||
@@ -3019,6 +3249,29 @@ meta = [
|
||||
|
||||
// inner workflow
|
||||
// user-provided Nextflow code
|
||||
import java.util.zip.GZIPInputStream
|
||||
import java.nio.file.Files
|
||||
import java.io.BufferedInputStream
|
||||
|
||||
def is_empty(file_to_check){
|
||||
/*
|
||||
Checks if a file has content
|
||||
*/
|
||||
if (file_to_check.size() == 0) {
|
||||
return true
|
||||
}
|
||||
def input_stream = Files.newInputStream(file_to_check)
|
||||
def gzInputStream
|
||||
try {
|
||||
gzInputStream = new GZIPInputStream(new BufferedInputStream(input_stream))
|
||||
} catch (java.io.EOFException ex) {
|
||||
// This is not a gzipfile...
|
||||
return false
|
||||
}
|
||||
def read_one_byte = gzInputStream.read()
|
||||
return read_one_byte == -1
|
||||
}
|
||||
|
||||
workflow run_wf {
|
||||
take:
|
||||
input_ch
|
||||
@@ -3038,8 +3291,8 @@ workflow run_wf {
|
||||
println "Processing run information file ${sample_sheet}"
|
||||
csv_lines = sample_sheet.splitCsv(header: false, sep: ',')
|
||||
csv_lines.any { csv_items ->
|
||||
if (csv_items.isEmpty()) {
|
||||
// skip empty line
|
||||
if (csv_items.isEmpty() || csv_items[0].startsWith("#")) {
|
||||
// skip empty or commented line
|
||||
return
|
||||
}
|
||||
def possible_header = csv_items[0]
|
||||
@@ -3051,8 +3304,8 @@ workflow run_wf {
|
||||
return true
|
||||
}
|
||||
// [Data], [BCLConvert_Data] for illumina
|
||||
// [Samples] for Element Biosciences
|
||||
if (header in ["Data", "Samples", "BCLConvert_Data"]) {
|
||||
// [Samples] or sometimes [SAMPLES] for Element Biosciences
|
||||
if (header.toLowerCase() in ["data", "samples", "bclconvert_data"]) {
|
||||
println "Found header [${header}], start parsing."
|
||||
start_parsing = true
|
||||
return
|
||||
@@ -3099,6 +3352,9 @@ workflow run_wf {
|
||||
"Found forward: ${forward_fastq} and reverse: ${reverse_fastq}."
|
||||
println "Found ${forward_fastq.size()} forward and ${reverse_fastq.size()} reverse " +
|
||||
"fastq files for sample ${sample_id}"
|
||||
|
||||
assert forward_fastq.every{!is_empty(it)} && reverse_fastq.every{!is_empty(it)}:
|
||||
"A fastq file for sample '${sample_id}' appears to be empty!"
|
||||
def fastqs_state = [
|
||||
"fastq_forward": forward_fastq,
|
||||
"fastq_reverse": reverse_fastq,
|
||||
|
||||
@@ -2,7 +2,7 @@ manifest {
|
||||
name = 'dataflow/gather_fastqs_and_validate'
|
||||
mainScript = 'main.nf'
|
||||
nextflowVersion = '!>=20.12.1-edge'
|
||||
version = 'v0.3.5'
|
||||
version = 'v0.3.11'
|
||||
description = 'From a directory containing fastq files, gather the files per sample \nand validate according to the contents of the sample sheet.\n'
|
||||
}
|
||||
|
||||
|
||||
@@ -47,10 +47,10 @@
|
||||
"fastq_forward": {
|
||||
"type":
|
||||
"string",
|
||||
"description": "Type: List of `file`, required, default: `$id.$key.fastq_forward_*.fastq_forward_*`, multiple_sep: `\";\"`. ",
|
||||
"help_text": "Type: List of `file`, required, default: `$id.$key.fastq_forward_*.fastq_forward_*`, multiple_sep: `\";\"`. "
|
||||
"description": "Type: List of `file`, required, default: `$id.$key.fastq_forward_*`, multiple_sep: `\";\"`. ",
|
||||
"help_text": "Type: List of `file`, required, default: `$id.$key.fastq_forward_*`, multiple_sep: `\";\"`. "
|
||||
,
|
||||
"default":"$id.$key.fastq_forward_*.fastq_forward_*"
|
||||
"default":"$id.$key.fastq_forward_*"
|
||||
}
|
||||
|
||||
|
||||
@@ -58,10 +58,10 @@
|
||||
"fastq_reverse": {
|
||||
"type":
|
||||
"string",
|
||||
"description": "Type: List of `file`, default: `$id.$key.fastq_reverse_*.fastq_reverse_*`, multiple_sep: `\";\"`. ",
|
||||
"help_text": "Type: List of `file`, default: `$id.$key.fastq_reverse_*.fastq_reverse_*`, multiple_sep: `\";\"`. "
|
||||
"description": "Type: List of `file`, default: `$id.$key.fastq_reverse_*`, multiple_sep: `\";\"`. ",
|
||||
"help_text": "Type: List of `file`, default: `$id.$key.fastq_reverse_*`, multiple_sep: `\";\"`. "
|
||||
,
|
||||
"default":"$id.$key.fastq_reverse_*.fastq_reverse_*"
|
||||
"default":"$id.$key.fastq_reverse_*"
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: "demultiplex"
|
||||
version: "v0.3.5"
|
||||
version: "v0.3.11"
|
||||
argument_groups:
|
||||
- name: "Input arguments"
|
||||
arguments:
|
||||
@@ -96,6 +96,17 @@ argument_groups:
|
||||
direction: "output"
|
||||
multiple: false
|
||||
multiple_sep: ";"
|
||||
- type: "file"
|
||||
name: "--demultiplexer_logs"
|
||||
info: null
|
||||
default:
|
||||
- "$id/demultiplexer_logs"
|
||||
must_exist: true
|
||||
create_parent: true
|
||||
required: true
|
||||
direction: "output"
|
||||
multiple: false
|
||||
multiple_sep: ";"
|
||||
- name: "Other arguments"
|
||||
arguments:
|
||||
- type: "boolean_true"
|
||||
@@ -124,6 +135,9 @@ test_resources:
|
||||
entrypoint: "test_bases2fastq"
|
||||
info: null
|
||||
status: "enabled"
|
||||
scope:
|
||||
image: "public"
|
||||
target: "public"
|
||||
requirements:
|
||||
commands:
|
||||
- "ps"
|
||||
@@ -243,10 +257,10 @@ build_info:
|
||||
engine: "native|native"
|
||||
output: "target/nextflow/demultiplex"
|
||||
executable: "target/nextflow/demultiplex/main.nf"
|
||||
viash_version: "0.9.0"
|
||||
git_commit: "18e092d169cc2704a2e4e705485d9231911d0484"
|
||||
viash_version: "0.9.4"
|
||||
git_commit: "cd2cfac18e14622e1a5d7a0d489f64c30e83dab2"
|
||||
git_remote: "https://github.com/viash-hub/demultiplex"
|
||||
git_tag: "v0.3.4-6-g18e092d"
|
||||
git_tag: "v0.3.10-5-gcd2cfac"
|
||||
dependencies:
|
||||
- "target/nextflow/io/untar"
|
||||
- "target/nextflow/dataflow/gather_fastqs_and_validate"
|
||||
@@ -258,23 +272,23 @@ build_info:
|
||||
- "target/dependencies/vsh/vsh/biobox/v0.3.0/nextflow/multiqc"
|
||||
package_config:
|
||||
name: "demultiplex"
|
||||
version: "v0.3.5"
|
||||
version: "v0.3.11"
|
||||
description: "Demultiplexing pipeline\n"
|
||||
info:
|
||||
test_resources:
|
||||
- path: "gs://viash-hub-test-data/demultiplex/v2/"
|
||||
- path: "gs://viash-hub-resources/demultiplex/v3"
|
||||
dest: "testData"
|
||||
viash_version: "0.9.0"
|
||||
viash_version: "0.9.4"
|
||||
source: "src"
|
||||
target: "target"
|
||||
config_mods:
|
||||
- ".requirements.commands := ['ps']\n.runners[.type == 'nextflow'].directives.tag\
|
||||
- ".requirements.commands += ['ps']\n.runners[.type == 'nextflow'].directives.tag\
|
||||
\ := '$id'\n.resources += {path: '/src/config/labels.config', dest: 'nextflow_labels.config'}\n\
|
||||
.runners[.type == 'nextflow'].config.script := 'includeConfig(\"nextflow_labels.config\"\
|
||||
)'\n"
|
||||
- ".engines += { type: \"native\" }"
|
||||
- ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'"
|
||||
- ".engines[.type == 'docker'].target_tag := 'v0.3.5'"
|
||||
- ".engines[.type == 'docker'].target_tag := 'v0.3.11'"
|
||||
keywords:
|
||||
- "bioinformatics"
|
||||
- "sequence"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// demultiplex v0.3.5
|
||||
// demultiplex v0.3.11
|
||||
//
|
||||
// This wrapper script is auto-generated by viash 0.9.0 and is thus a derivative
|
||||
// This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative
|
||||
// work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data
|
||||
// Intuitive.
|
||||
//
|
||||
@@ -82,64 +82,56 @@ def _checkArgumentType(String stage, Map par, Object value, String errorIdentifi
|
||||
foundClass = "List[${e.foundClass}]"
|
||||
}
|
||||
} else if (par.type == "string") {
|
||||
// cast to string if need be
|
||||
// cast to string if need be. only cast if the value is a GString
|
||||
if (value instanceof GString) {
|
||||
value = value.toString()
|
||||
value = value as String
|
||||
}
|
||||
expectedClass = value instanceof String ? null : "String"
|
||||
} else if (par.type == "integer") {
|
||||
// cast to integer if need be
|
||||
if (value instanceof String) {
|
||||
if (value !instanceof Integer) {
|
||||
try {
|
||||
value = value.toInteger()
|
||||
value = value as Integer
|
||||
} catch (NumberFormatException e) {
|
||||
// do nothing
|
||||
expectedClass = "Integer"
|
||||
}
|
||||
}
|
||||
if (value instanceof java.math.BigInteger) {
|
||||
value = value.intValue()
|
||||
}
|
||||
expectedClass = value instanceof Integer ? null : "Integer"
|
||||
} else if (par.type == "long") {
|
||||
// cast to long if need be
|
||||
if (value instanceof String) {
|
||||
if (value !instanceof Long) {
|
||||
try {
|
||||
value = value.toLong()
|
||||
value = value as Long
|
||||
} catch (NumberFormatException e) {
|
||||
// do nothing
|
||||
expectedClass = "Long"
|
||||
}
|
||||
}
|
||||
if (value instanceof Integer) {
|
||||
value = value.toLong()
|
||||
}
|
||||
expectedClass = value instanceof Long ? null : "Long"
|
||||
} else if (par.type == "double") {
|
||||
// cast to double if need be
|
||||
if (value instanceof String) {
|
||||
if (value !instanceof Double) {
|
||||
try {
|
||||
value = value.toDouble()
|
||||
value = value as Double
|
||||
} catch (NumberFormatException e) {
|
||||
// do nothing
|
||||
expectedClass = "Double"
|
||||
}
|
||||
}
|
||||
if (value instanceof java.math.BigDecimal) {
|
||||
value = value.doubleValue()
|
||||
} else if (par.type == "float") {
|
||||
// cast to float if need be
|
||||
if (value !instanceof Float) {
|
||||
try {
|
||||
value = value as Float
|
||||
} catch (NumberFormatException e) {
|
||||
expectedClass = "Float"
|
||||
}
|
||||
}
|
||||
if (value instanceof Float) {
|
||||
value = value.toDouble()
|
||||
}
|
||||
expectedClass = value instanceof Double ? null : "Double"
|
||||
} else if (par.type == "boolean" | par.type == "boolean_true" | par.type == "boolean_false") {
|
||||
// cast to boolean if need be
|
||||
if (value instanceof String) {
|
||||
def valueLower = value.toLowerCase()
|
||||
if (valueLower == "true") {
|
||||
value = true
|
||||
} else if (valueLower == "false") {
|
||||
value = false
|
||||
if (value !instanceof Boolean) {
|
||||
try {
|
||||
value = value as Boolean
|
||||
} catch (Exception e) {
|
||||
expectedClass = "Boolean"
|
||||
}
|
||||
}
|
||||
expectedClass = value instanceof Boolean ? null : "Boolean"
|
||||
} else if (par.type == "file" && (par.direction == "input" || stage == "output")) {
|
||||
// cast to path if need be
|
||||
if (value instanceof String) {
|
||||
@@ -151,10 +143,13 @@ def _checkArgumentType(String stage, Map par, Object value, String errorIdentifi
|
||||
expectedClass = value instanceof Path ? null : "Path"
|
||||
} else if (par.type == "file" && stage == "input" && par.direction == "output") {
|
||||
// cast to string if need be
|
||||
if (value instanceof GString) {
|
||||
value = value.toString()
|
||||
if (value !instanceof String) {
|
||||
try {
|
||||
value = value as String
|
||||
} catch (Exception e) {
|
||||
expectedClass = "String"
|
||||
}
|
||||
}
|
||||
expectedClass = value instanceof String ? null : "String"
|
||||
} else {
|
||||
// didn't find a match for par.type
|
||||
expectedClass = par.type
|
||||
@@ -173,7 +168,7 @@ def _checkArgumentType(String stage, Map par, Object value, String errorIdentifi
|
||||
Map _processInputValues(Map inputs, Map config, String id, String key) {
|
||||
if (!workflow.stubRun) {
|
||||
config.allArguments.each { arg ->
|
||||
if (arg.required) {
|
||||
if (arg.required && arg.direction == "input") {
|
||||
assert inputs.containsKey(arg.plainName) && inputs.get(arg.plainName) != null :
|
||||
"Error in module '${key}' id '${id}': required input argument '${arg.plainName}' is missing"
|
||||
}
|
||||
@@ -192,15 +187,8 @@ Map _processInputValues(Map inputs, Map config, String id, String key) {
|
||||
}
|
||||
|
||||
// helper file: 'src/main/resources/io/viash/runners/nextflow/arguments/_processOutputValues.nf'
|
||||
Map _processOutputValues(Map outputs, Map config, String id, String key) {
|
||||
Map _checkValidOutputArgument(Map outputs, Map config, String id, String key) {
|
||||
if (!workflow.stubRun) {
|
||||
config.allArguments.each { arg ->
|
||||
if (arg.direction == "output" && arg.required) {
|
||||
assert outputs.containsKey(arg.plainName) && outputs.get(arg.plainName) != null :
|
||||
"Error in module '${key}' id '${id}': required output argument '${arg.plainName}' is missing"
|
||||
}
|
||||
}
|
||||
|
||||
outputs = outputs.collectEntries { name, value ->
|
||||
def par = config.allArguments.find { it.plainName == name && it.direction == "output" }
|
||||
assert par != null : "Error in module '${key}' id '${id}': '${name}' is not a valid output argument"
|
||||
@@ -213,6 +201,16 @@ Map _processOutputValues(Map outputs, Map config, String id, String key) {
|
||||
return outputs
|
||||
}
|
||||
|
||||
void _checkAllRequiredOuputsPresent(Map outputs, Map config, String id, String key) {
|
||||
if (!workflow.stubRun) {
|
||||
config.allArguments.each { arg ->
|
||||
if (arg.direction == "output" && arg.required) {
|
||||
assert outputs.containsKey(arg.plainName) && outputs.get(arg.plainName) != null :
|
||||
"Error in module '${key}' id '${id}': required output argument '${arg.plainName}' is missing"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// helper file: 'src/main/resources/io/viash/runners/nextflow/channel/IDChecker.nf'
|
||||
class IDChecker {
|
||||
final def items = [] as Set
|
||||
@@ -1666,6 +1664,162 @@ def joinStates(Closure apply_) {
|
||||
}
|
||||
return joinStatesWf
|
||||
}
|
||||
// helper file: 'src/main/resources/io/viash/runners/nextflow/states/publishFiles.nf'
|
||||
def publishFiles(Map args) {
|
||||
def key_ = args.get("key")
|
||||
|
||||
assert key_ != null : "publishFiles: key must be specified"
|
||||
|
||||
workflow publishFilesWf {
|
||||
take: input_ch
|
||||
main:
|
||||
input_ch
|
||||
| map { tup ->
|
||||
def id_ = tup[0]
|
||||
def state_ = tup[1]
|
||||
|
||||
// the input files and the target output filenames
|
||||
def inputoutputFilenames_ = collectInputOutputPaths(state_, id_ + "." + key_).transpose()
|
||||
def inputFiles_ = inputoutputFilenames_[0]
|
||||
def outputFilenames_ = inputoutputFilenames_[1]
|
||||
|
||||
[id_, inputFiles_, outputFilenames_]
|
||||
}
|
||||
| publishFilesProc
|
||||
emit: input_ch
|
||||
}
|
||||
return publishFilesWf
|
||||
}
|
||||
|
||||
process publishFilesProc {
|
||||
// todo: check publishpath?
|
||||
publishDir path: "${getPublishDir()}/", mode: "copy"
|
||||
tag "$id"
|
||||
input:
|
||||
tuple val(id), path(inputFiles, stageAs: "_inputfile?/*"), val(outputFiles)
|
||||
output:
|
||||
tuple val(id), path{outputFiles}
|
||||
script:
|
||||
def copyCommands = [
|
||||
inputFiles instanceof List ? inputFiles : [inputFiles],
|
||||
outputFiles instanceof List ? outputFiles : [outputFiles]
|
||||
]
|
||||
.transpose()
|
||||
.collectMany{infile, outfile ->
|
||||
if (infile.toString() != outfile.toString()) {
|
||||
[
|
||||
"[ -d \"\$(dirname '${outfile.toString()}')\" ] || mkdir -p \"\$(dirname '${outfile.toString()}')\"",
|
||||
"cp -r '${infile.toString()}' '${outfile.toString()}'"
|
||||
]
|
||||
} else {
|
||||
// no need to copy if infile is the same as outfile
|
||||
[]
|
||||
}
|
||||
}
|
||||
"""
|
||||
echo "Copying output files to destination folder"
|
||||
${copyCommands.join("\n ")}
|
||||
"""
|
||||
}
|
||||
|
||||
|
||||
// this assumes that the state contains no other values other than those specified in the config
|
||||
def publishFilesByConfig(Map args) {
|
||||
def config = args.get("config")
|
||||
assert config != null : "publishFilesByConfig: config must be specified"
|
||||
|
||||
def key_ = args.get("key", config.name)
|
||||
assert key_ != null : "publishFilesByConfig: key must be specified"
|
||||
|
||||
workflow publishFilesSimpleWf {
|
||||
take: input_ch
|
||||
main:
|
||||
input_ch
|
||||
| map { tup ->
|
||||
def id_ = tup[0]
|
||||
def state_ = tup[1] // e.g. [output: new File("myoutput.h5ad"), k: 10]
|
||||
def origState_ = tup[2] // e.g. [output: '$id.$key.foo.h5ad']
|
||||
|
||||
|
||||
// the processed state is a list of [key, value, inputPath, outputFilename] tuples, where
|
||||
// - key is a String
|
||||
// - value is any object that can be serialized to a Yaml (so a String/Integer/Long/Double/Boolean, a List, a Map, or a Path)
|
||||
// - inputPath is a List[Path]
|
||||
// - outputFilename is a List[String]
|
||||
// - (inputPath, outputFilename) are the files that will be copied from src to dest (relative to the state.yaml)
|
||||
def processedState =
|
||||
config.allArguments
|
||||
.findAll { it.direction == "output" }
|
||||
.collectMany { par ->
|
||||
def plainName_ = par.plainName
|
||||
// if the state does not contain the key, it's an
|
||||
// optional argument for which the component did
|
||||
// not generate any output OR multiple channels were emitted
|
||||
// and the output was just not added to using the channel
|
||||
// that is now being parsed
|
||||
if (!state_.containsKey(plainName_)) {
|
||||
return []
|
||||
}
|
||||
def value = state_[plainName_]
|
||||
// if the parameter is not a file, it should be stored
|
||||
// in the state as-is, but is not something that needs
|
||||
// to be copied from the source path to the dest path
|
||||
if (par.type != "file") {
|
||||
return [[inputPath: [], outputFilename: []]]
|
||||
}
|
||||
// if the orig state does not contain this filename,
|
||||
// it's an optional argument for which the user specified
|
||||
// that it should not be returned as a state
|
||||
if (!origState_.containsKey(plainName_)) {
|
||||
return []
|
||||
}
|
||||
def filenameTemplate = origState_[plainName_]
|
||||
// if the pararameter is multiple: true, fetch the template
|
||||
if (par.multiple && filenameTemplate instanceof List) {
|
||||
filenameTemplate = filenameTemplate[0]
|
||||
}
|
||||
// instantiate the template
|
||||
def filename = filenameTemplate
|
||||
.replaceAll('\\$id', id_)
|
||||
.replaceAll('\\$\\{id\\}', id_)
|
||||
.replaceAll('\\$key', key_)
|
||||
.replaceAll('\\$\\{key\\}', key_)
|
||||
if (par.multiple) {
|
||||
// if the parameter is multiple: true, the filename
|
||||
// should contain a wildcard '*' that is replaced with
|
||||
// the index of the file
|
||||
assert filename.contains("*") : "Module '${key_}' id '${id_}': Multiple output files specified, but no wildcard '*' in the filename: ${filename}"
|
||||
def outputPerFile = value.withIndex().collect{ val, ix ->
|
||||
def filename_ix = filename.replace("*", ix.toString())
|
||||
def inputPath = val instanceof File ? val.toPath() : val
|
||||
[inputPath: inputPath, outputFilename: filename_ix]
|
||||
}
|
||||
def transposedOutputs = ["inputPath", "outputFilename"].collectEntries{ key ->
|
||||
[key, outputPerFile.collect{dic -> dic[key]}]
|
||||
}
|
||||
return [[key: plainName_] + transposedOutputs]
|
||||
} else {
|
||||
def value_ = java.nio.file.Paths.get(filename)
|
||||
def inputPath = value instanceof File ? value.toPath() : value
|
||||
return [[inputPath: [inputPath], outputFilename: [filename]]]
|
||||
}
|
||||
}
|
||||
|
||||
def inputPaths = processedState.collectMany{it.inputPath}
|
||||
def outputFilenames = processedState.collectMany{it.outputFilename}
|
||||
|
||||
|
||||
[id_, inputPaths, outputFilenames]
|
||||
}
|
||||
| publishFilesProc
|
||||
emit: input_ch
|
||||
}
|
||||
return publishFilesSimpleWf
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// helper file: 'src/main/resources/io/viash/runners/nextflow/states/publishStates.nf'
|
||||
def collectFiles(obj) {
|
||||
if (obj instanceof java.io.File || obj instanceof Path) {
|
||||
@@ -1723,8 +1877,6 @@ def publishStates(Map args) {
|
||||
|
||||
// the input files and the target output filenames
|
||||
def inputoutputFilenames_ = collectInputOutputPaths(state_, id_ + "." + key_).transpose()
|
||||
def inputFiles_ = inputoutputFilenames_[0]
|
||||
def outputFilenames_ = inputoutputFilenames_[1]
|
||||
|
||||
def yamlFilename = yamlTemplate_
|
||||
.replaceAll('\\$id', id_)
|
||||
@@ -1737,7 +1889,7 @@ def publishStates(Map args) {
|
||||
// convert state to yaml blob
|
||||
def yamlBlob_ = toRelativeTaggedYamlBlob([id: id_] + state_, java.nio.file.Paths.get(yamlFilename))
|
||||
|
||||
[id_, yamlBlob_, yamlFilename, inputFiles_, outputFilenames_]
|
||||
[id_, yamlBlob_, yamlFilename]
|
||||
}
|
||||
| publishStatesProc
|
||||
emit: input_ch
|
||||
@@ -1749,33 +1901,17 @@ process publishStatesProc {
|
||||
publishDir path: "${getPublishDir()}/", mode: "copy"
|
||||
tag "$id"
|
||||
input:
|
||||
tuple val(id), val(yamlBlob), val(yamlFile), path(inputFiles, stageAs: "_inputfile?/*"), val(outputFiles)
|
||||
tuple val(id), val(yamlBlob), val(yamlFile)
|
||||
output:
|
||||
tuple val(id), path{[yamlFile] + outputFiles}
|
||||
tuple val(id), path{[yamlFile]}
|
||||
script:
|
||||
def copyCommands = [
|
||||
inputFiles instanceof List ? inputFiles : [inputFiles],
|
||||
outputFiles instanceof List ? outputFiles : [outputFiles]
|
||||
]
|
||||
.transpose()
|
||||
.collectMany{infile, outfile ->
|
||||
if (infile.toString() != outfile.toString()) {
|
||||
[
|
||||
"[ -d \"\$(dirname '${outfile.toString()}')\" ] || mkdir -p \"\$(dirname '${outfile.toString()}')\"",
|
||||
"cp -r '${infile.toString()}' '${outfile.toString()}'"
|
||||
]
|
||||
} else {
|
||||
// no need to copy if infile is the same as outfile
|
||||
[]
|
||||
}
|
||||
}
|
||||
"""
|
||||
mkdir -p "\$(dirname '${yamlFile}')"
|
||||
echo "Storing state as yaml"
|
||||
echo '${yamlBlob}' > '${yamlFile}'
|
||||
echo "Copying output files to destination folder"
|
||||
${copyCommands.join("\n ")}
|
||||
"""
|
||||
mkdir -p "\$(dirname '${yamlFile}')"
|
||||
echo "Storing state as yaml"
|
||||
cat > '${yamlFile}' << HERE
|
||||
${yamlBlob}
|
||||
HERE
|
||||
"""
|
||||
}
|
||||
|
||||
|
||||
@@ -1806,13 +1942,10 @@ def publishStatesByConfig(Map args) {
|
||||
.replaceAll('\\$\\{key\\}', key_)
|
||||
def yamlDir = java.nio.file.Paths.get(yamlFilename).getParent()
|
||||
|
||||
// the processed state is a list of [key, value, inputPath, outputFilename] tuples, where
|
||||
// the processed state is a list of [key, value] tuples, where
|
||||
// - key is a String
|
||||
// - value is any object that can be serialized to a Yaml (so a String/Integer/Long/Double/Boolean, a List, a Map, or a Path)
|
||||
// - inputPath is a List[Path]
|
||||
// - outputFilename is a List[String]
|
||||
// - (key, value) are the tuples that will be saved to the state.yaml file
|
||||
// - (inputPath, outputFilename) are the files that will be copied from src to dest (relative to the state.yaml)
|
||||
def processedState =
|
||||
config.allArguments
|
||||
.findAll { it.direction == "output" }
|
||||
@@ -1829,7 +1962,7 @@ def publishStatesByConfig(Map args) {
|
||||
// in the state as-is, but is not something that needs
|
||||
// to be copied from the source path to the dest path
|
||||
if (par.type != "file") {
|
||||
return [[key: plainName_, value: value, inputPath: [], outputFilename: []]]
|
||||
return [[key: plainName_, value: value]]
|
||||
}
|
||||
// if the orig state does not contain this filename,
|
||||
// it's an optional argument for which the user specified
|
||||
@@ -1860,13 +1993,9 @@ def publishStatesByConfig(Map args) {
|
||||
if (yamlDir != null) {
|
||||
value_ = yamlDir.relativize(value_)
|
||||
}
|
||||
def inputPath = val instanceof File ? val.toPath() : val
|
||||
[value: value_, inputPath: inputPath, outputFilename: filename_ix]
|
||||
return value_
|
||||
}
|
||||
def transposedOutputs = ["value", "inputPath", "outputFilename"].collectEntries{ key ->
|
||||
[key, outputPerFile.collect{dic -> dic[key]}]
|
||||
}
|
||||
return [[key: plainName_] + transposedOutputs]
|
||||
return [["key": plainName_, "value": outputPerFile]]
|
||||
} else {
|
||||
def value_ = java.nio.file.Paths.get(filename)
|
||||
// if id contains a slash
|
||||
@@ -1874,18 +2003,17 @@ def publishStatesByConfig(Map args) {
|
||||
value_ = yamlDir.relativize(value_)
|
||||
}
|
||||
def inputPath = value instanceof File ? value.toPath() : value
|
||||
return [[key: plainName_, value: value_, inputPath: [inputPath], outputFilename: [filename]]]
|
||||
return [["key": plainName_, value: value_]]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def updatedState_ = processedState.collectEntries{[it.key, it.value]}
|
||||
def inputPaths = processedState.collectMany{it.inputPath}
|
||||
def outputFilenames = processedState.collectMany{it.outputFilename}
|
||||
|
||||
// convert state to yaml blob
|
||||
def yamlBlob_ = toTaggedYamlBlob([id: id_] + updatedState_)
|
||||
|
||||
[id_, yamlBlob_, yamlFilename, inputPaths, outputFilenames]
|
||||
[id_, yamlBlob_, yamlFilename]
|
||||
}
|
||||
| publishStatesProc
|
||||
emit: input_ch
|
||||
@@ -2559,7 +2687,8 @@ def _debug(workflowArgs, debugKey) {
|
||||
def workflowFactory(Map args, Map defaultWfArgs, Map meta) {
|
||||
def workflowArgs = processWorkflowArgs(args, defaultWfArgs, meta)
|
||||
def key_ = workflowArgs["key"]
|
||||
|
||||
def multipleArgs = meta.config.allArguments.findAll{ it.multiple }.collect{it.plainName}
|
||||
|
||||
workflow workflowInstance {
|
||||
take: input_
|
||||
|
||||
@@ -2716,12 +2845,36 @@ def workflowFactory(Map args, Map defaultWfArgs, Map meta) {
|
||||
}
|
||||
|
||||
// TODO: move some of the _meta.join_id wrangling to the safeJoin() function.
|
||||
def chInitialOutput = chArgsWithDefaults
|
||||
def chInitialOutputMulti = chArgsWithDefaults
|
||||
| _debug(workflowArgs, "processed")
|
||||
// run workflow
|
||||
| innerWorkflowFactory(workflowArgs)
|
||||
// check output tuple
|
||||
| map { id_, output_ ->
|
||||
def chInitialOutputList = chInitialOutputMulti instanceof List ? chInitialOutputMulti : [chInitialOutputMulti]
|
||||
assert chInitialOutputList.size() > 0: "should have emitted at least one output channel"
|
||||
// Add a channel ID to the events, which designates the channel the event was emitted from as a running number
|
||||
// This number is used to sort the events later when the events are gathered from across the channels.
|
||||
def chInitialOutputListWithIndexedEvents = chInitialOutputList.withIndex().collect{channel, channelIndex ->
|
||||
def newChannel = channel
|
||||
| map {tuple ->
|
||||
assert tuple instanceof List :
|
||||
"Error in module '${key_}': element in output channel should be a tuple [id, data, ...otherargs...]\n" +
|
||||
" Example: [\"id\", [input: file('foo.txt'), arg: 10]].\n" +
|
||||
" Expected class: List. Found: tuple.getClass() is ${tuple.getClass()}"
|
||||
|
||||
def newEvent = [channelIndex] + tuple
|
||||
return newEvent
|
||||
}
|
||||
return newChannel
|
||||
}
|
||||
// Put the events into 1 channel, cover case where there is only one channel is emitted
|
||||
def chInitialOutput = chInitialOutputList.size() > 1 ? \
|
||||
chInitialOutputListWithIndexedEvents[0].mix(*chInitialOutputListWithIndexedEvents.tail()) : \
|
||||
chInitialOutputListWithIndexedEvents[0]
|
||||
def chInitialOutputProcessed = chInitialOutput
|
||||
| map { tuple ->
|
||||
def channelId = tuple[0]
|
||||
def id_ = tuple[1]
|
||||
def output_ = tuple[2]
|
||||
|
||||
// see if output map contains metadata
|
||||
def meta_ =
|
||||
@@ -2734,19 +2887,94 @@ def workflowFactory(Map args, Map defaultWfArgs, Map meta) {
|
||||
output_ = output_.findAll{k, v -> k != "_meta"}
|
||||
|
||||
// check value types
|
||||
output_ = _processOutputValues(output_, meta.config, id_, key_)
|
||||
output_ = _checkValidOutputArgument(output_, meta.config, id_, key_)
|
||||
|
||||
// simplify output if need be
|
||||
if (workflowArgs.auto.simplifyOutput && output_.size() == 1) {
|
||||
output_ = output_.values()[0]
|
||||
}
|
||||
|
||||
[join_id, id_, output_]
|
||||
[join_id, channelId, id_, output_]
|
||||
}
|
||||
// | view{"chInitialOutput: ${it.take(3)}"}
|
||||
|
||||
// join the output [prev_id, channel_id, new_id, output] with the previous state [prev_id, state, ...]
|
||||
def chPublishWithPreviousState = safeJoin(chInitialOutputProcessed, chRunFiltered, key_)
|
||||
// input tuple format: [join_id, channel_id, id, output, prev_state, ...]
|
||||
// output tuple format: [join_id, channel_id, id, new_state, ...]
|
||||
| map{ tup ->
|
||||
def new_state = workflowArgs.toState(tup.drop(2).take(3))
|
||||
tup.take(3) + [new_state] + tup.drop(5)
|
||||
}
|
||||
if (workflowArgs.auto.publish == "state") {
|
||||
def chPublishFiles = chPublishWithPreviousState
|
||||
// input tuple format: [join_id, channel_id, id, new_state, ...]
|
||||
// output tuple format: [join_id, channel_id, id, new_state]
|
||||
| map{ tup ->
|
||||
tup.take(4)
|
||||
}
|
||||
|
||||
safeJoin(chPublishFiles, chArgsWithDefaults, key_)
|
||||
// input tuple format: [join_id, channel_id, id, new_state, orig_state, ...]
|
||||
// output tuple format: [id, new_state, orig_state]
|
||||
| map { tup ->
|
||||
tup.drop(2).take(3)
|
||||
}
|
||||
| publishFilesByConfig(key: key_, config: meta.config)
|
||||
}
|
||||
// Join the state from the events that were emitted from different channels
|
||||
def chJoined = chInitialOutputProcessed
|
||||
| map {tuple ->
|
||||
def join_id = tuple[0]
|
||||
def channel_id = tuple[1]
|
||||
def id = tuple[2]
|
||||
def other = tuple.drop(3)
|
||||
// Below, groupTuple is used to join the events. To make sure resuming a workflow
|
||||
// keeps working, the output state must be deterministic. This means the state needs to be
|
||||
// sorted with groupTuple's has a 'sort' argument. This argument can be set to 'hash',
|
||||
// but hashing the state when it is large can be problematic in terms of performance.
|
||||
// Therefore, a custom comparator function is provided. We add the channel ID to the
|
||||
// states so that we can use the channel ID to sort the items.
|
||||
def stateWithChannelID = [[channel_id] * other.size(), other].transpose()
|
||||
// A comparator that is provided to groupTuple's 'sort' argument is applied
|
||||
// to all elements of the event tuple (that is not the 'id'). The comparator
|
||||
// closure that is used below expects the input to be List. So the join_id and
|
||||
// channel_id must also be wrapped in a list.
|
||||
[[join_id], [channel_id], id] + stateWithChannelID
|
||||
}
|
||||
| groupTuple(by: 2, sort: {a, b -> a[0] <=> b[0]}, size: chInitialOutputList.size(), remainder: true)
|
||||
| map {join_ids, _, id, statesWithChannelID ->
|
||||
// Remove the channel IDs from the states
|
||||
def states = statesWithChannelID.collect{it[1]}
|
||||
def newJoinId = join_ids.flatten().unique{a, b -> a <=> b}
|
||||
assert newJoinId.size() == 1: "Multiple events were emitted for '$id'."
|
||||
def newJoinIdUnique = newJoinId[0]
|
||||
|
||||
// Merge the states from the different channels
|
||||
def newState = states.inject([:]){ old_state, state_to_add ->
|
||||
return old_state + state_to_add.collectEntries{k, v ->
|
||||
if (!multipleArgs.contains(k)) {
|
||||
// if the key is not a multiple argument, we expect only one value
|
||||
if (old_state.containsKey(k)) {
|
||||
assert old_state[k] == v : "ID $id: multiple entries for argument $k were emitted."
|
||||
}
|
||||
[k, v]
|
||||
} else {
|
||||
// if the key is a multiple argument, append the different values into one list
|
||||
def prevValue = old_state.getOrDefault(k, [])
|
||||
def prevValueAsList = prevValue instanceof List ? prevValue : [prevValue]
|
||||
[k, prevValueAsList + v]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_checkAllRequiredOuputsPresent(newState, meta.config, id, key_)
|
||||
|
||||
// simplify output if need be
|
||||
if (workflowArgs.auto.simplifyOutput && newState.size() == 1) {
|
||||
newState = newState.values()[0]
|
||||
}
|
||||
|
||||
return [newJoinIdUnique, id, newState]
|
||||
}
|
||||
|
||||
// join the output [prev_id, new_id, output] with the previous state [prev_id, state, ...]
|
||||
def chNewState = safeJoin(chInitialOutput, chRunFiltered, key_)
|
||||
def chNewState = safeJoin(chJoined, chRunFiltered, key_)
|
||||
// input tuple format: [join_id, id, output, prev_state, ...]
|
||||
// output tuple format: [join_id, id, new_state, ...]
|
||||
| map{ tup ->
|
||||
@@ -2755,23 +2983,21 @@ def workflowFactory(Map args, Map defaultWfArgs, Map meta) {
|
||||
}
|
||||
|
||||
if (workflowArgs.auto.publish == "state") {
|
||||
def chPublish = chNewState
|
||||
def chPublishStates = chNewState
|
||||
// input tuple format: [join_id, id, new_state, ...]
|
||||
// output tuple format: [join_id, id, new_state]
|
||||
| map{ tup ->
|
||||
tup.take(3)
|
||||
}
|
||||
|
||||
safeJoin(chPublish, chArgsWithDefaults, key_)
|
||||
safeJoin(chPublishStates, chArgsWithDefaults, key_)
|
||||
// input tuple format: [join_id, id, new_state, orig_state, ...]
|
||||
// output tuple format: [id, new_state, orig_state]
|
||||
| map { tup ->
|
||||
tup.drop(1).take(3)
|
||||
}
|
||||
}
|
||||
| publishStatesByConfig(key: key_, config: meta.config)
|
||||
}
|
||||
|
||||
// remove join_id and meta
|
||||
chReturn = chNewState
|
||||
| map { tup ->
|
||||
// input tuple format: [join_id, id, new_state, ...]
|
||||
@@ -2805,7 +3031,7 @@ meta = [
|
||||
"resources_dir": moduleDir.toRealPath().normalize(),
|
||||
"config": processConfig(readJsonBlob('''{
|
||||
"name" : "demultiplex",
|
||||
"version" : "v0.3.5",
|
||||
"version" : "v0.3.11",
|
||||
"argument_groups" : [
|
||||
{
|
||||
"name" : "Input arguments",
|
||||
@@ -2913,6 +3139,19 @@ meta = [
|
||||
"direction" : "output",
|
||||
"multiple" : false,
|
||||
"multiple_sep" : ";"
|
||||
},
|
||||
{
|
||||
"type" : "file",
|
||||
"name" : "--demultiplexer_logs",
|
||||
"default" : [
|
||||
"$id/demultiplexer_logs"
|
||||
],
|
||||
"must_exist" : true,
|
||||
"create_parent" : true,
|
||||
"required" : true,
|
||||
"direction" : "output",
|
||||
"multiple" : false,
|
||||
"multiple_sep" : ";"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -2957,6 +3196,10 @@ meta = [
|
||||
}
|
||||
],
|
||||
"status" : "enabled",
|
||||
"scope" : {
|
||||
"image" : "public",
|
||||
"target" : "public"
|
||||
},
|
||||
"requirements" : {
|
||||
"commands" : [
|
||||
"ps"
|
||||
@@ -3119,31 +3362,31 @@ meta = [
|
||||
"runner" : "nextflow",
|
||||
"engine" : "native|native",
|
||||
"output" : "target/nextflow/demultiplex",
|
||||
"viash_version" : "0.9.0",
|
||||
"git_commit" : "18e092d169cc2704a2e4e705485d9231911d0484",
|
||||
"viash_version" : "0.9.4",
|
||||
"git_commit" : "cd2cfac18e14622e1a5d7a0d489f64c30e83dab2",
|
||||
"git_remote" : "https://github.com/viash-hub/demultiplex",
|
||||
"git_tag" : "v0.3.4-6-g18e092d"
|
||||
"git_tag" : "v0.3.10-5-gcd2cfac"
|
||||
},
|
||||
"package_config" : {
|
||||
"name" : "demultiplex",
|
||||
"version" : "v0.3.5",
|
||||
"version" : "v0.3.11",
|
||||
"description" : "Demultiplexing pipeline\n",
|
||||
"info" : {
|
||||
"test_resources" : [
|
||||
{
|
||||
"path" : "gs://viash-hub-test-data/demultiplex/v2/",
|
||||
"path" : "gs://viash-hub-resources/demultiplex/v3",
|
||||
"dest" : "testData"
|
||||
}
|
||||
]
|
||||
},
|
||||
"viash_version" : "0.9.0",
|
||||
"viash_version" : "0.9.4",
|
||||
"source" : "src",
|
||||
"target" : "target",
|
||||
"config_mods" : [
|
||||
".requirements.commands := ['ps']\n.runners[.type == 'nextflow'].directives.tag := '$id'\n.resources += {path: '/src/config/labels.config', dest: 'nextflow_labels.config'}\n.runners[.type == 'nextflow'].config.script := 'includeConfig(\\"nextflow_labels.config\\")'\n",
|
||||
".requirements.commands += ['ps']\n.runners[.type == 'nextflow'].directives.tag := '$id'\n.resources += {path: '/src/config/labels.config', dest: 'nextflow_labels.config'}\n.runners[.type == 'nextflow'].config.script := 'includeConfig(\\"nextflow_labels.config\\")'\n",
|
||||
".engines += { type: \\"native\\" }",
|
||||
".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'",
|
||||
".engines[.type == 'docker'].target_tag := 'v0.3.5'"
|
||||
".engines[.type == 'docker'].target_tag := 'v0.3.11'"
|
||||
],
|
||||
"keywords" : [
|
||||
"bioinformatics",
|
||||
@@ -3300,14 +3543,15 @@ workflow run_wf {
|
||||
bcl_input_directory: state.input,
|
||||
sample_sheet: state.run_information,
|
||||
output_directory: state.output,
|
||||
reports: "reports",
|
||||
logs: "logs"
|
||||
reports: state.demultiplexer_logs,
|
||||
logs: state.demultiplexer_logs,
|
||||
]
|
||||
},
|
||||
toState: {id, result, state ->
|
||||
def toAdd = [
|
||||
"output_demultiplexer" : result.output_directory,
|
||||
"run_id": id,
|
||||
"demultiplexer_logs": result.reports,
|
||||
]
|
||||
def newState = state + toAdd
|
||||
return newState
|
||||
@@ -3317,11 +3561,15 @@ workflow run_wf {
|
||||
| bases2fastq.run(
|
||||
runIf: {id, state -> state.demultiplexer in ["bases2fastq"]},
|
||||
directives: [label: ["highmem", "midcpu"]],
|
||||
fromState: [
|
||||
"analysis_directory": "input",
|
||||
"run_manifest": "run_information",
|
||||
"output_directory": "output",
|
||||
],
|
||||
fromState: { id, state ->
|
||||
[
|
||||
"analysis_directory": state.input,
|
||||
"run_manifest": state.run_information,
|
||||
"output_directory": state.output,
|
||||
"report": state.demultiplexer_logs + "/report.html",
|
||||
"logs": state.demultiplexer_logs,
|
||||
]
|
||||
},
|
||||
args: [
|
||||
"no_projects": true, // Do not put output files in a subfolder for project
|
||||
//"split_lanes": true,
|
||||
@@ -3332,6 +3580,8 @@ workflow run_wf {
|
||||
def toAdd = [
|
||||
"output_demultiplexer" : result.output_directory,
|
||||
"run_id": id,
|
||||
"demultiplexer_logs": result.logs,
|
||||
|
||||
]
|
||||
def newState = state + toAdd
|
||||
return newState
|
||||
@@ -3401,6 +3651,7 @@ workflow run_wf {
|
||||
state + [ "output_multiqc" : result.output_report ]
|
||||
}
|
||||
)
|
||||
|
||||
| setState(
|
||||
[
|
||||
//"_meta": "_meta",
|
||||
@@ -3408,6 +3659,7 @@ workflow run_wf {
|
||||
"output_falco": "output_falco",
|
||||
"output_multiqc": "output_multiqc",
|
||||
"output_run_information": "run_information",
|
||||
"demultiplexer_logs": "demultiplexer_logs"
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ manifest {
|
||||
name = 'demultiplex'
|
||||
mainScript = 'main.nf'
|
||||
nextflowVersion = '!>=20.12.1-edge'
|
||||
version = 'v0.3.5'
|
||||
version = 'v0.3.11'
|
||||
description = 'Demultiplexing of raw sequencing data'
|
||||
}
|
||||
|
||||
|
||||
@@ -69,10 +69,10 @@
|
||||
"output": {
|
||||
"type":
|
||||
"string",
|
||||
"description": "Type: `file`, default: `$id.$key.output.output`. Directory to write fastq data to",
|
||||
"help_text": "Type: `file`, default: `$id.$key.output.output`. Directory to write fastq data to"
|
||||
"description": "Type: `file`, default: `$id/fastq`. Directory to write fastq data to",
|
||||
"help_text": "Type: `file`, default: `$id/fastq`. Directory to write fastq data to"
|
||||
,
|
||||
"default":"$id.$key.output.output"
|
||||
"default":"$id/fastq"
|
||||
}
|
||||
|
||||
|
||||
@@ -80,10 +80,10 @@
|
||||
"output_falco": {
|
||||
"type":
|
||||
"string",
|
||||
"description": "Type: List of `file`, default: `$id.$key.output_falco_*.output_falco_*`, multiple_sep: `\";\"`. Directory to write falco output to",
|
||||
"help_text": "Type: List of `file`, default: `$id.$key.output_falco_*.output_falco_*`, multiple_sep: `\";\"`. Directory to write falco output to"
|
||||
"description": "Type: List of `file`, default: `$id/qc/fastqc`, multiple_sep: `\";\"`. Directory to write falco output to",
|
||||
"help_text": "Type: List of `file`, default: `$id/qc/fastqc`, multiple_sep: `\";\"`. Directory to write falco output to"
|
||||
,
|
||||
"default":"$id.$key.output_falco_*.output_falco_*"
|
||||
"default":"$id/qc/fastqc"
|
||||
}
|
||||
|
||||
|
||||
@@ -91,10 +91,10 @@
|
||||
"output_multiqc": {
|
||||
"type":
|
||||
"string",
|
||||
"description": "Type: `file`, default: `$id.$key.output_multiqc.html`. Directory to write falco output to",
|
||||
"help_text": "Type: `file`, default: `$id.$key.output_multiqc.html`. Directory to write falco output to"
|
||||
"description": "Type: `file`, default: `$id/qc/multiqc_report.html`. Directory to write falco output to",
|
||||
"help_text": "Type: `file`, default: `$id/qc/multiqc_report.html`. Directory to write falco output to"
|
||||
,
|
||||
"default":"$id.$key.output_multiqc.html"
|
||||
"default":"$id/qc/multiqc_report.html"
|
||||
}
|
||||
|
||||
|
||||
@@ -102,10 +102,21 @@
|
||||
"output_run_information": {
|
||||
"type":
|
||||
"string",
|
||||
"description": "Type: `file`, required, default: `$id.$key.output_run_information.csv`. ",
|
||||
"help_text": "Type: `file`, required, default: `$id.$key.output_run_information.csv`. "
|
||||
"description": "Type: `file`, required, default: `$id/run_information.csv`. ",
|
||||
"help_text": "Type: `file`, required, default: `$id/run_information.csv`. "
|
||||
,
|
||||
"default":"$id.$key.output_run_information.csv"
|
||||
"default":"$id/run_information.csv"
|
||||
}
|
||||
|
||||
|
||||
,
|
||||
"demultiplexer_logs": {
|
||||
"type":
|
||||
"string",
|
||||
"description": "Type: `file`, required, default: `$id/demultiplexer_logs`. ",
|
||||
"help_text": "Type: `file`, required, default: `$id/demultiplexer_logs`. "
|
||||
,
|
||||
"default":"$id/demultiplexer_logs"
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: "interop_summary_to_csv"
|
||||
namespace: "io"
|
||||
version: "v0.3.5"
|
||||
version: "v0.3.11"
|
||||
argument_groups:
|
||||
- name: "Input arguments"
|
||||
arguments:
|
||||
@@ -41,10 +41,21 @@ resources:
|
||||
- type: "file"
|
||||
path: "nextflow_labels.config"
|
||||
dest: "nextflow_labels.config"
|
||||
test_resources:
|
||||
- type: "bash_script"
|
||||
path: "test.sh"
|
||||
is_executable: true
|
||||
- type: "file"
|
||||
path: "iseq-DI"
|
||||
info: null
|
||||
status: "enabled"
|
||||
scope:
|
||||
image: "public"
|
||||
target: "public"
|
||||
requirements:
|
||||
commands:
|
||||
- "summary"
|
||||
- "index-summary"
|
||||
- "ps"
|
||||
license: "MIT"
|
||||
links:
|
||||
@@ -121,7 +132,7 @@ engines:
|
||||
id: "docker"
|
||||
image: "debian:stable-slim"
|
||||
target_registry: "images.viash-hub.com"
|
||||
target_tag: "v0.3.5"
|
||||
target_tag: "v0.3.11"
|
||||
namespace_separator: "/"
|
||||
setup:
|
||||
- type: "apt"
|
||||
@@ -145,29 +156,29 @@ build_info:
|
||||
engine: "docker|native"
|
||||
output: "target/nextflow/io/interop_summary_to_csv"
|
||||
executable: "target/nextflow/io/interop_summary_to_csv/main.nf"
|
||||
viash_version: "0.9.0"
|
||||
git_commit: "18e092d169cc2704a2e4e705485d9231911d0484"
|
||||
viash_version: "0.9.4"
|
||||
git_commit: "cd2cfac18e14622e1a5d7a0d489f64c30e83dab2"
|
||||
git_remote: "https://github.com/viash-hub/demultiplex"
|
||||
git_tag: "v0.3.4-6-g18e092d"
|
||||
git_tag: "v0.3.10-5-gcd2cfac"
|
||||
package_config:
|
||||
name: "demultiplex"
|
||||
version: "v0.3.5"
|
||||
version: "v0.3.11"
|
||||
description: "Demultiplexing pipeline\n"
|
||||
info:
|
||||
test_resources:
|
||||
- path: "gs://viash-hub-test-data/demultiplex/v2/"
|
||||
- path: "gs://viash-hub-resources/demultiplex/v3"
|
||||
dest: "testData"
|
||||
viash_version: "0.9.0"
|
||||
viash_version: "0.9.4"
|
||||
source: "src"
|
||||
target: "target"
|
||||
config_mods:
|
||||
- ".requirements.commands := ['ps']\n.runners[.type == 'nextflow'].directives.tag\
|
||||
- ".requirements.commands += ['ps']\n.runners[.type == 'nextflow'].directives.tag\
|
||||
\ := '$id'\n.resources += {path: '/src/config/labels.config', dest: 'nextflow_labels.config'}\n\
|
||||
.runners[.type == 'nextflow'].config.script := 'includeConfig(\"nextflow_labels.config\"\
|
||||
)'\n"
|
||||
- ".engines += { type: \"native\" }"
|
||||
- ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'"
|
||||
- ".engines[.type == 'docker'].target_tag := 'v0.3.5'"
|
||||
- ".engines[.type == 'docker'].target_tag := 'v0.3.11'"
|
||||
keywords:
|
||||
- "bioinformatics"
|
||||
- "sequence"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// interop_summary_to_csv v0.3.5
|
||||
// interop_summary_to_csv v0.3.11
|
||||
//
|
||||
// This wrapper script is auto-generated by viash 0.9.0 and is thus a derivative
|
||||
// This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative
|
||||
// work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data
|
||||
// Intuitive.
|
||||
//
|
||||
@@ -82,64 +82,56 @@ def _checkArgumentType(String stage, Map par, Object value, String errorIdentifi
|
||||
foundClass = "List[${e.foundClass}]"
|
||||
}
|
||||
} else if (par.type == "string") {
|
||||
// cast to string if need be
|
||||
// cast to string if need be. only cast if the value is a GString
|
||||
if (value instanceof GString) {
|
||||
value = value.toString()
|
||||
value = value as String
|
||||
}
|
||||
expectedClass = value instanceof String ? null : "String"
|
||||
} else if (par.type == "integer") {
|
||||
// cast to integer if need be
|
||||
if (value instanceof String) {
|
||||
if (value !instanceof Integer) {
|
||||
try {
|
||||
value = value.toInteger()
|
||||
value = value as Integer
|
||||
} catch (NumberFormatException e) {
|
||||
// do nothing
|
||||
expectedClass = "Integer"
|
||||
}
|
||||
}
|
||||
if (value instanceof java.math.BigInteger) {
|
||||
value = value.intValue()
|
||||
}
|
||||
expectedClass = value instanceof Integer ? null : "Integer"
|
||||
} else if (par.type == "long") {
|
||||
// cast to long if need be
|
||||
if (value instanceof String) {
|
||||
if (value !instanceof Long) {
|
||||
try {
|
||||
value = value.toLong()
|
||||
value = value as Long
|
||||
} catch (NumberFormatException e) {
|
||||
// do nothing
|
||||
expectedClass = "Long"
|
||||
}
|
||||
}
|
||||
if (value instanceof Integer) {
|
||||
value = value.toLong()
|
||||
}
|
||||
expectedClass = value instanceof Long ? null : "Long"
|
||||
} else if (par.type == "double") {
|
||||
// cast to double if need be
|
||||
if (value instanceof String) {
|
||||
if (value !instanceof Double) {
|
||||
try {
|
||||
value = value.toDouble()
|
||||
value = value as Double
|
||||
} catch (NumberFormatException e) {
|
||||
// do nothing
|
||||
expectedClass = "Double"
|
||||
}
|
||||
}
|
||||
if (value instanceof java.math.BigDecimal) {
|
||||
value = value.doubleValue()
|
||||
} else if (par.type == "float") {
|
||||
// cast to float if need be
|
||||
if (value !instanceof Float) {
|
||||
try {
|
||||
value = value as Float
|
||||
} catch (NumberFormatException e) {
|
||||
expectedClass = "Float"
|
||||
}
|
||||
}
|
||||
if (value instanceof Float) {
|
||||
value = value.toDouble()
|
||||
}
|
||||
expectedClass = value instanceof Double ? null : "Double"
|
||||
} else if (par.type == "boolean" | par.type == "boolean_true" | par.type == "boolean_false") {
|
||||
// cast to boolean if need be
|
||||
if (value instanceof String) {
|
||||
def valueLower = value.toLowerCase()
|
||||
if (valueLower == "true") {
|
||||
value = true
|
||||
} else if (valueLower == "false") {
|
||||
value = false
|
||||
if (value !instanceof Boolean) {
|
||||
try {
|
||||
value = value as Boolean
|
||||
} catch (Exception e) {
|
||||
expectedClass = "Boolean"
|
||||
}
|
||||
}
|
||||
expectedClass = value instanceof Boolean ? null : "Boolean"
|
||||
} else if (par.type == "file" && (par.direction == "input" || stage == "output")) {
|
||||
// cast to path if need be
|
||||
if (value instanceof String) {
|
||||
@@ -151,10 +143,13 @@ def _checkArgumentType(String stage, Map par, Object value, String errorIdentifi
|
||||
expectedClass = value instanceof Path ? null : "Path"
|
||||
} else if (par.type == "file" && stage == "input" && par.direction == "output") {
|
||||
// cast to string if need be
|
||||
if (value instanceof GString) {
|
||||
value = value.toString()
|
||||
if (value !instanceof String) {
|
||||
try {
|
||||
value = value as String
|
||||
} catch (Exception e) {
|
||||
expectedClass = "String"
|
||||
}
|
||||
}
|
||||
expectedClass = value instanceof String ? null : "String"
|
||||
} else {
|
||||
// didn't find a match for par.type
|
||||
expectedClass = par.type
|
||||
@@ -173,7 +168,7 @@ def _checkArgumentType(String stage, Map par, Object value, String errorIdentifi
|
||||
Map _processInputValues(Map inputs, Map config, String id, String key) {
|
||||
if (!workflow.stubRun) {
|
||||
config.allArguments.each { arg ->
|
||||
if (arg.required) {
|
||||
if (arg.required && arg.direction == "input") {
|
||||
assert inputs.containsKey(arg.plainName) && inputs.get(arg.plainName) != null :
|
||||
"Error in module '${key}' id '${id}': required input argument '${arg.plainName}' is missing"
|
||||
}
|
||||
@@ -192,15 +187,8 @@ Map _processInputValues(Map inputs, Map config, String id, String key) {
|
||||
}
|
||||
|
||||
// helper file: 'src/main/resources/io/viash/runners/nextflow/arguments/_processOutputValues.nf'
|
||||
Map _processOutputValues(Map outputs, Map config, String id, String key) {
|
||||
Map _checkValidOutputArgument(Map outputs, Map config, String id, String key) {
|
||||
if (!workflow.stubRun) {
|
||||
config.allArguments.each { arg ->
|
||||
if (arg.direction == "output" && arg.required) {
|
||||
assert outputs.containsKey(arg.plainName) && outputs.get(arg.plainName) != null :
|
||||
"Error in module '${key}' id '${id}': required output argument '${arg.plainName}' is missing"
|
||||
}
|
||||
}
|
||||
|
||||
outputs = outputs.collectEntries { name, value ->
|
||||
def par = config.allArguments.find { it.plainName == name && it.direction == "output" }
|
||||
assert par != null : "Error in module '${key}' id '${id}': '${name}' is not a valid output argument"
|
||||
@@ -213,6 +201,16 @@ Map _processOutputValues(Map outputs, Map config, String id, String key) {
|
||||
return outputs
|
||||
}
|
||||
|
||||
void _checkAllRequiredOuputsPresent(Map outputs, Map config, String id, String key) {
|
||||
if (!workflow.stubRun) {
|
||||
config.allArguments.each { arg ->
|
||||
if (arg.direction == "output" && arg.required) {
|
||||
assert outputs.containsKey(arg.plainName) && outputs.get(arg.plainName) != null :
|
||||
"Error in module '${key}' id '${id}': required output argument '${arg.plainName}' is missing"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// helper file: 'src/main/resources/io/viash/runners/nextflow/channel/IDChecker.nf'
|
||||
class IDChecker {
|
||||
final def items = [] as Set
|
||||
@@ -1666,6 +1664,162 @@ def joinStates(Closure apply_) {
|
||||
}
|
||||
return joinStatesWf
|
||||
}
|
||||
// helper file: 'src/main/resources/io/viash/runners/nextflow/states/publishFiles.nf'
|
||||
def publishFiles(Map args) {
|
||||
def key_ = args.get("key")
|
||||
|
||||
assert key_ != null : "publishFiles: key must be specified"
|
||||
|
||||
workflow publishFilesWf {
|
||||
take: input_ch
|
||||
main:
|
||||
input_ch
|
||||
| map { tup ->
|
||||
def id_ = tup[0]
|
||||
def state_ = tup[1]
|
||||
|
||||
// the input files and the target output filenames
|
||||
def inputoutputFilenames_ = collectInputOutputPaths(state_, id_ + "." + key_).transpose()
|
||||
def inputFiles_ = inputoutputFilenames_[0]
|
||||
def outputFilenames_ = inputoutputFilenames_[1]
|
||||
|
||||
[id_, inputFiles_, outputFilenames_]
|
||||
}
|
||||
| publishFilesProc
|
||||
emit: input_ch
|
||||
}
|
||||
return publishFilesWf
|
||||
}
|
||||
|
||||
process publishFilesProc {
|
||||
// todo: check publishpath?
|
||||
publishDir path: "${getPublishDir()}/", mode: "copy"
|
||||
tag "$id"
|
||||
input:
|
||||
tuple val(id), path(inputFiles, stageAs: "_inputfile?/*"), val(outputFiles)
|
||||
output:
|
||||
tuple val(id), path{outputFiles}
|
||||
script:
|
||||
def copyCommands = [
|
||||
inputFiles instanceof List ? inputFiles : [inputFiles],
|
||||
outputFiles instanceof List ? outputFiles : [outputFiles]
|
||||
]
|
||||
.transpose()
|
||||
.collectMany{infile, outfile ->
|
||||
if (infile.toString() != outfile.toString()) {
|
||||
[
|
||||
"[ -d \"\$(dirname '${outfile.toString()}')\" ] || mkdir -p \"\$(dirname '${outfile.toString()}')\"",
|
||||
"cp -r '${infile.toString()}' '${outfile.toString()}'"
|
||||
]
|
||||
} else {
|
||||
// no need to copy if infile is the same as outfile
|
||||
[]
|
||||
}
|
||||
}
|
||||
"""
|
||||
echo "Copying output files to destination folder"
|
||||
${copyCommands.join("\n ")}
|
||||
"""
|
||||
}
|
||||
|
||||
|
||||
// this assumes that the state contains no other values other than those specified in the config
|
||||
def publishFilesByConfig(Map args) {
|
||||
def config = args.get("config")
|
||||
assert config != null : "publishFilesByConfig: config must be specified"
|
||||
|
||||
def key_ = args.get("key", config.name)
|
||||
assert key_ != null : "publishFilesByConfig: key must be specified"
|
||||
|
||||
workflow publishFilesSimpleWf {
|
||||
take: input_ch
|
||||
main:
|
||||
input_ch
|
||||
| map { tup ->
|
||||
def id_ = tup[0]
|
||||
def state_ = tup[1] // e.g. [output: new File("myoutput.h5ad"), k: 10]
|
||||
def origState_ = tup[2] // e.g. [output: '$id.$key.foo.h5ad']
|
||||
|
||||
|
||||
// the processed state is a list of [key, value, inputPath, outputFilename] tuples, where
|
||||
// - key is a String
|
||||
// - value is any object that can be serialized to a Yaml (so a String/Integer/Long/Double/Boolean, a List, a Map, or a Path)
|
||||
// - inputPath is a List[Path]
|
||||
// - outputFilename is a List[String]
|
||||
// - (inputPath, outputFilename) are the files that will be copied from src to dest (relative to the state.yaml)
|
||||
def processedState =
|
||||
config.allArguments
|
||||
.findAll { it.direction == "output" }
|
||||
.collectMany { par ->
|
||||
def plainName_ = par.plainName
|
||||
// if the state does not contain the key, it's an
|
||||
// optional argument for which the component did
|
||||
// not generate any output OR multiple channels were emitted
|
||||
// and the output was just not added to using the channel
|
||||
// that is now being parsed
|
||||
if (!state_.containsKey(plainName_)) {
|
||||
return []
|
||||
}
|
||||
def value = state_[plainName_]
|
||||
// if the parameter is not a file, it should be stored
|
||||
// in the state as-is, but is not something that needs
|
||||
// to be copied from the source path to the dest path
|
||||
if (par.type != "file") {
|
||||
return [[inputPath: [], outputFilename: []]]
|
||||
}
|
||||
// if the orig state does not contain this filename,
|
||||
// it's an optional argument for which the user specified
|
||||
// that it should not be returned as a state
|
||||
if (!origState_.containsKey(plainName_)) {
|
||||
return []
|
||||
}
|
||||
def filenameTemplate = origState_[plainName_]
|
||||
// if the pararameter is multiple: true, fetch the template
|
||||
if (par.multiple && filenameTemplate instanceof List) {
|
||||
filenameTemplate = filenameTemplate[0]
|
||||
}
|
||||
// instantiate the template
|
||||
def filename = filenameTemplate
|
||||
.replaceAll('\\$id', id_)
|
||||
.replaceAll('\\$\\{id\\}', id_)
|
||||
.replaceAll('\\$key', key_)
|
||||
.replaceAll('\\$\\{key\\}', key_)
|
||||
if (par.multiple) {
|
||||
// if the parameter is multiple: true, the filename
|
||||
// should contain a wildcard '*' that is replaced with
|
||||
// the index of the file
|
||||
assert filename.contains("*") : "Module '${key_}' id '${id_}': Multiple output files specified, but no wildcard '*' in the filename: ${filename}"
|
||||
def outputPerFile = value.withIndex().collect{ val, ix ->
|
||||
def filename_ix = filename.replace("*", ix.toString())
|
||||
def inputPath = val instanceof File ? val.toPath() : val
|
||||
[inputPath: inputPath, outputFilename: filename_ix]
|
||||
}
|
||||
def transposedOutputs = ["inputPath", "outputFilename"].collectEntries{ key ->
|
||||
[key, outputPerFile.collect{dic -> dic[key]}]
|
||||
}
|
||||
return [[key: plainName_] + transposedOutputs]
|
||||
} else {
|
||||
def value_ = java.nio.file.Paths.get(filename)
|
||||
def inputPath = value instanceof File ? value.toPath() : value
|
||||
return [[inputPath: [inputPath], outputFilename: [filename]]]
|
||||
}
|
||||
}
|
||||
|
||||
def inputPaths = processedState.collectMany{it.inputPath}
|
||||
def outputFilenames = processedState.collectMany{it.outputFilename}
|
||||
|
||||
|
||||
[id_, inputPaths, outputFilenames]
|
||||
}
|
||||
| publishFilesProc
|
||||
emit: input_ch
|
||||
}
|
||||
return publishFilesSimpleWf
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// helper file: 'src/main/resources/io/viash/runners/nextflow/states/publishStates.nf'
|
||||
def collectFiles(obj) {
|
||||
if (obj instanceof java.io.File || obj instanceof Path) {
|
||||
@@ -1723,8 +1877,6 @@ def publishStates(Map args) {
|
||||
|
||||
// the input files and the target output filenames
|
||||
def inputoutputFilenames_ = collectInputOutputPaths(state_, id_ + "." + key_).transpose()
|
||||
def inputFiles_ = inputoutputFilenames_[0]
|
||||
def outputFilenames_ = inputoutputFilenames_[1]
|
||||
|
||||
def yamlFilename = yamlTemplate_
|
||||
.replaceAll('\\$id', id_)
|
||||
@@ -1737,7 +1889,7 @@ def publishStates(Map args) {
|
||||
// convert state to yaml blob
|
||||
def yamlBlob_ = toRelativeTaggedYamlBlob([id: id_] + state_, java.nio.file.Paths.get(yamlFilename))
|
||||
|
||||
[id_, yamlBlob_, yamlFilename, inputFiles_, outputFilenames_]
|
||||
[id_, yamlBlob_, yamlFilename]
|
||||
}
|
||||
| publishStatesProc
|
||||
emit: input_ch
|
||||
@@ -1749,33 +1901,17 @@ process publishStatesProc {
|
||||
publishDir path: "${getPublishDir()}/", mode: "copy"
|
||||
tag "$id"
|
||||
input:
|
||||
tuple val(id), val(yamlBlob), val(yamlFile), path(inputFiles, stageAs: "_inputfile?/*"), val(outputFiles)
|
||||
tuple val(id), val(yamlBlob), val(yamlFile)
|
||||
output:
|
||||
tuple val(id), path{[yamlFile] + outputFiles}
|
||||
tuple val(id), path{[yamlFile]}
|
||||
script:
|
||||
def copyCommands = [
|
||||
inputFiles instanceof List ? inputFiles : [inputFiles],
|
||||
outputFiles instanceof List ? outputFiles : [outputFiles]
|
||||
]
|
||||
.transpose()
|
||||
.collectMany{infile, outfile ->
|
||||
if (infile.toString() != outfile.toString()) {
|
||||
[
|
||||
"[ -d \"\$(dirname '${outfile.toString()}')\" ] || mkdir -p \"\$(dirname '${outfile.toString()}')\"",
|
||||
"cp -r '${infile.toString()}' '${outfile.toString()}'"
|
||||
]
|
||||
} else {
|
||||
// no need to copy if infile is the same as outfile
|
||||
[]
|
||||
}
|
||||
}
|
||||
"""
|
||||
mkdir -p "\$(dirname '${yamlFile}')"
|
||||
echo "Storing state as yaml"
|
||||
echo '${yamlBlob}' > '${yamlFile}'
|
||||
echo "Copying output files to destination folder"
|
||||
${copyCommands.join("\n ")}
|
||||
"""
|
||||
mkdir -p "\$(dirname '${yamlFile}')"
|
||||
echo "Storing state as yaml"
|
||||
cat > '${yamlFile}' << HERE
|
||||
${yamlBlob}
|
||||
HERE
|
||||
"""
|
||||
}
|
||||
|
||||
|
||||
@@ -1806,13 +1942,10 @@ def publishStatesByConfig(Map args) {
|
||||
.replaceAll('\\$\\{key\\}', key_)
|
||||
def yamlDir = java.nio.file.Paths.get(yamlFilename).getParent()
|
||||
|
||||
// the processed state is a list of [key, value, inputPath, outputFilename] tuples, where
|
||||
// the processed state is a list of [key, value] tuples, where
|
||||
// - key is a String
|
||||
// - value is any object that can be serialized to a Yaml (so a String/Integer/Long/Double/Boolean, a List, a Map, or a Path)
|
||||
// - inputPath is a List[Path]
|
||||
// - outputFilename is a List[String]
|
||||
// - (key, value) are the tuples that will be saved to the state.yaml file
|
||||
// - (inputPath, outputFilename) are the files that will be copied from src to dest (relative to the state.yaml)
|
||||
def processedState =
|
||||
config.allArguments
|
||||
.findAll { it.direction == "output" }
|
||||
@@ -1829,7 +1962,7 @@ def publishStatesByConfig(Map args) {
|
||||
// in the state as-is, but is not something that needs
|
||||
// to be copied from the source path to the dest path
|
||||
if (par.type != "file") {
|
||||
return [[key: plainName_, value: value, inputPath: [], outputFilename: []]]
|
||||
return [[key: plainName_, value: value]]
|
||||
}
|
||||
// if the orig state does not contain this filename,
|
||||
// it's an optional argument for which the user specified
|
||||
@@ -1860,13 +1993,9 @@ def publishStatesByConfig(Map args) {
|
||||
if (yamlDir != null) {
|
||||
value_ = yamlDir.relativize(value_)
|
||||
}
|
||||
def inputPath = val instanceof File ? val.toPath() : val
|
||||
[value: value_, inputPath: inputPath, outputFilename: filename_ix]
|
||||
return value_
|
||||
}
|
||||
def transposedOutputs = ["value", "inputPath", "outputFilename"].collectEntries{ key ->
|
||||
[key, outputPerFile.collect{dic -> dic[key]}]
|
||||
}
|
||||
return [[key: plainName_] + transposedOutputs]
|
||||
return [["key": plainName_, "value": outputPerFile]]
|
||||
} else {
|
||||
def value_ = java.nio.file.Paths.get(filename)
|
||||
// if id contains a slash
|
||||
@@ -1874,18 +2003,17 @@ def publishStatesByConfig(Map args) {
|
||||
value_ = yamlDir.relativize(value_)
|
||||
}
|
||||
def inputPath = value instanceof File ? value.toPath() : value
|
||||
return [[key: plainName_, value: value_, inputPath: [inputPath], outputFilename: [filename]]]
|
||||
return [["key": plainName_, value: value_]]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def updatedState_ = processedState.collectEntries{[it.key, it.value]}
|
||||
def inputPaths = processedState.collectMany{it.inputPath}
|
||||
def outputFilenames = processedState.collectMany{it.outputFilename}
|
||||
|
||||
// convert state to yaml blob
|
||||
def yamlBlob_ = toTaggedYamlBlob([id: id_] + updatedState_)
|
||||
|
||||
[id_, yamlBlob_, yamlFilename, inputPaths, outputFilenames]
|
||||
[id_, yamlBlob_, yamlFilename]
|
||||
}
|
||||
| publishStatesProc
|
||||
emit: input_ch
|
||||
@@ -2559,7 +2687,8 @@ def _debug(workflowArgs, debugKey) {
|
||||
def workflowFactory(Map args, Map defaultWfArgs, Map meta) {
|
||||
def workflowArgs = processWorkflowArgs(args, defaultWfArgs, meta)
|
||||
def key_ = workflowArgs["key"]
|
||||
|
||||
def multipleArgs = meta.config.allArguments.findAll{ it.multiple }.collect{it.plainName}
|
||||
|
||||
workflow workflowInstance {
|
||||
take: input_
|
||||
|
||||
@@ -2716,12 +2845,36 @@ def workflowFactory(Map args, Map defaultWfArgs, Map meta) {
|
||||
}
|
||||
|
||||
// TODO: move some of the _meta.join_id wrangling to the safeJoin() function.
|
||||
def chInitialOutput = chArgsWithDefaults
|
||||
def chInitialOutputMulti = chArgsWithDefaults
|
||||
| _debug(workflowArgs, "processed")
|
||||
// run workflow
|
||||
| innerWorkflowFactory(workflowArgs)
|
||||
// check output tuple
|
||||
| map { id_, output_ ->
|
||||
def chInitialOutputList = chInitialOutputMulti instanceof List ? chInitialOutputMulti : [chInitialOutputMulti]
|
||||
assert chInitialOutputList.size() > 0: "should have emitted at least one output channel"
|
||||
// Add a channel ID to the events, which designates the channel the event was emitted from as a running number
|
||||
// This number is used to sort the events later when the events are gathered from across the channels.
|
||||
def chInitialOutputListWithIndexedEvents = chInitialOutputList.withIndex().collect{channel, channelIndex ->
|
||||
def newChannel = channel
|
||||
| map {tuple ->
|
||||
assert tuple instanceof List :
|
||||
"Error in module '${key_}': element in output channel should be a tuple [id, data, ...otherargs...]\n" +
|
||||
" Example: [\"id\", [input: file('foo.txt'), arg: 10]].\n" +
|
||||
" Expected class: List. Found: tuple.getClass() is ${tuple.getClass()}"
|
||||
|
||||
def newEvent = [channelIndex] + tuple
|
||||
return newEvent
|
||||
}
|
||||
return newChannel
|
||||
}
|
||||
// Put the events into 1 channel, cover case where there is only one channel is emitted
|
||||
def chInitialOutput = chInitialOutputList.size() > 1 ? \
|
||||
chInitialOutputListWithIndexedEvents[0].mix(*chInitialOutputListWithIndexedEvents.tail()) : \
|
||||
chInitialOutputListWithIndexedEvents[0]
|
||||
def chInitialOutputProcessed = chInitialOutput
|
||||
| map { tuple ->
|
||||
def channelId = tuple[0]
|
||||
def id_ = tuple[1]
|
||||
def output_ = tuple[2]
|
||||
|
||||
// see if output map contains metadata
|
||||
def meta_ =
|
||||
@@ -2734,19 +2887,94 @@ def workflowFactory(Map args, Map defaultWfArgs, Map meta) {
|
||||
output_ = output_.findAll{k, v -> k != "_meta"}
|
||||
|
||||
// check value types
|
||||
output_ = _processOutputValues(output_, meta.config, id_, key_)
|
||||
output_ = _checkValidOutputArgument(output_, meta.config, id_, key_)
|
||||
|
||||
// simplify output if need be
|
||||
if (workflowArgs.auto.simplifyOutput && output_.size() == 1) {
|
||||
output_ = output_.values()[0]
|
||||
}
|
||||
|
||||
[join_id, id_, output_]
|
||||
[join_id, channelId, id_, output_]
|
||||
}
|
||||
// | view{"chInitialOutput: ${it.take(3)}"}
|
||||
|
||||
// join the output [prev_id, channel_id, new_id, output] with the previous state [prev_id, state, ...]
|
||||
def chPublishWithPreviousState = safeJoin(chInitialOutputProcessed, chRunFiltered, key_)
|
||||
// input tuple format: [join_id, channel_id, id, output, prev_state, ...]
|
||||
// output tuple format: [join_id, channel_id, id, new_state, ...]
|
||||
| map{ tup ->
|
||||
def new_state = workflowArgs.toState(tup.drop(2).take(3))
|
||||
tup.take(3) + [new_state] + tup.drop(5)
|
||||
}
|
||||
if (workflowArgs.auto.publish == "state") {
|
||||
def chPublishFiles = chPublishWithPreviousState
|
||||
// input tuple format: [join_id, channel_id, id, new_state, ...]
|
||||
// output tuple format: [join_id, channel_id, id, new_state]
|
||||
| map{ tup ->
|
||||
tup.take(4)
|
||||
}
|
||||
|
||||
safeJoin(chPublishFiles, chArgsWithDefaults, key_)
|
||||
// input tuple format: [join_id, channel_id, id, new_state, orig_state, ...]
|
||||
// output tuple format: [id, new_state, orig_state]
|
||||
| map { tup ->
|
||||
tup.drop(2).take(3)
|
||||
}
|
||||
| publishFilesByConfig(key: key_, config: meta.config)
|
||||
}
|
||||
// Join the state from the events that were emitted from different channels
|
||||
def chJoined = chInitialOutputProcessed
|
||||
| map {tuple ->
|
||||
def join_id = tuple[0]
|
||||
def channel_id = tuple[1]
|
||||
def id = tuple[2]
|
||||
def other = tuple.drop(3)
|
||||
// Below, groupTuple is used to join the events. To make sure resuming a workflow
|
||||
// keeps working, the output state must be deterministic. This means the state needs to be
|
||||
// sorted with groupTuple's has a 'sort' argument. This argument can be set to 'hash',
|
||||
// but hashing the state when it is large can be problematic in terms of performance.
|
||||
// Therefore, a custom comparator function is provided. We add the channel ID to the
|
||||
// states so that we can use the channel ID to sort the items.
|
||||
def stateWithChannelID = [[channel_id] * other.size(), other].transpose()
|
||||
// A comparator that is provided to groupTuple's 'sort' argument is applied
|
||||
// to all elements of the event tuple (that is not the 'id'). The comparator
|
||||
// closure that is used below expects the input to be List. So the join_id and
|
||||
// channel_id must also be wrapped in a list.
|
||||
[[join_id], [channel_id], id] + stateWithChannelID
|
||||
}
|
||||
| groupTuple(by: 2, sort: {a, b -> a[0] <=> b[0]}, size: chInitialOutputList.size(), remainder: true)
|
||||
| map {join_ids, _, id, statesWithChannelID ->
|
||||
// Remove the channel IDs from the states
|
||||
def states = statesWithChannelID.collect{it[1]}
|
||||
def newJoinId = join_ids.flatten().unique{a, b -> a <=> b}
|
||||
assert newJoinId.size() == 1: "Multiple events were emitted for '$id'."
|
||||
def newJoinIdUnique = newJoinId[0]
|
||||
|
||||
// Merge the states from the different channels
|
||||
def newState = states.inject([:]){ old_state, state_to_add ->
|
||||
return old_state + state_to_add.collectEntries{k, v ->
|
||||
if (!multipleArgs.contains(k)) {
|
||||
// if the key is not a multiple argument, we expect only one value
|
||||
if (old_state.containsKey(k)) {
|
||||
assert old_state[k] == v : "ID $id: multiple entries for argument $k were emitted."
|
||||
}
|
||||
[k, v]
|
||||
} else {
|
||||
// if the key is a multiple argument, append the different values into one list
|
||||
def prevValue = old_state.getOrDefault(k, [])
|
||||
def prevValueAsList = prevValue instanceof List ? prevValue : [prevValue]
|
||||
[k, prevValueAsList + v]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_checkAllRequiredOuputsPresent(newState, meta.config, id, key_)
|
||||
|
||||
// simplify output if need be
|
||||
if (workflowArgs.auto.simplifyOutput && newState.size() == 1) {
|
||||
newState = newState.values()[0]
|
||||
}
|
||||
|
||||
return [newJoinIdUnique, id, newState]
|
||||
}
|
||||
|
||||
// join the output [prev_id, new_id, output] with the previous state [prev_id, state, ...]
|
||||
def chNewState = safeJoin(chInitialOutput, chRunFiltered, key_)
|
||||
def chNewState = safeJoin(chJoined, chRunFiltered, key_)
|
||||
// input tuple format: [join_id, id, output, prev_state, ...]
|
||||
// output tuple format: [join_id, id, new_state, ...]
|
||||
| map{ tup ->
|
||||
@@ -2755,23 +2983,21 @@ def workflowFactory(Map args, Map defaultWfArgs, Map meta) {
|
||||
}
|
||||
|
||||
if (workflowArgs.auto.publish == "state") {
|
||||
def chPublish = chNewState
|
||||
def chPublishStates = chNewState
|
||||
// input tuple format: [join_id, id, new_state, ...]
|
||||
// output tuple format: [join_id, id, new_state]
|
||||
| map{ tup ->
|
||||
tup.take(3)
|
||||
}
|
||||
|
||||
safeJoin(chPublish, chArgsWithDefaults, key_)
|
||||
safeJoin(chPublishStates, chArgsWithDefaults, key_)
|
||||
// input tuple format: [join_id, id, new_state, orig_state, ...]
|
||||
// output tuple format: [id, new_state, orig_state]
|
||||
| map { tup ->
|
||||
tup.drop(1).take(3)
|
||||
}
|
||||
}
|
||||
| publishStatesByConfig(key: key_, config: meta.config)
|
||||
}
|
||||
|
||||
// remove join_id and meta
|
||||
chReturn = chNewState
|
||||
| map { tup ->
|
||||
// input tuple format: [join_id, id, new_state, ...]
|
||||
@@ -2806,7 +3032,7 @@ meta = [
|
||||
"config": processConfig(readJsonBlob('''{
|
||||
"name" : "interop_summary_to_csv",
|
||||
"namespace" : "io",
|
||||
"version" : "v0.3.5",
|
||||
"version" : "v0.3.11",
|
||||
"argument_groups" : [
|
||||
{
|
||||
"name" : "Input arguments",
|
||||
@@ -2862,9 +3088,26 @@ meta = [
|
||||
"dest" : "nextflow_labels.config"
|
||||
}
|
||||
],
|
||||
"test_resources" : [
|
||||
{
|
||||
"type" : "bash_script",
|
||||
"path" : "test.sh",
|
||||
"is_executable" : true
|
||||
},
|
||||
{
|
||||
"type" : "file",
|
||||
"path" : "/testData/iseq-DI"
|
||||
}
|
||||
],
|
||||
"status" : "enabled",
|
||||
"scope" : {
|
||||
"image" : "public",
|
||||
"target" : "public"
|
||||
},
|
||||
"requirements" : {
|
||||
"commands" : [
|
||||
"summary",
|
||||
"index-summary",
|
||||
"ps"
|
||||
]
|
||||
},
|
||||
@@ -2955,7 +3198,7 @@ meta = [
|
||||
"id" : "docker",
|
||||
"image" : "debian:stable-slim",
|
||||
"target_registry" : "images.viash-hub.com",
|
||||
"target_tag" : "v0.3.5",
|
||||
"target_tag" : "v0.3.11",
|
||||
"namespace_separator" : "/",
|
||||
"setup" : [
|
||||
{
|
||||
@@ -2984,31 +3227,31 @@ meta = [
|
||||
"runner" : "nextflow",
|
||||
"engine" : "docker|native",
|
||||
"output" : "target/nextflow/io/interop_summary_to_csv",
|
||||
"viash_version" : "0.9.0",
|
||||
"git_commit" : "18e092d169cc2704a2e4e705485d9231911d0484",
|
||||
"viash_version" : "0.9.4",
|
||||
"git_commit" : "cd2cfac18e14622e1a5d7a0d489f64c30e83dab2",
|
||||
"git_remote" : "https://github.com/viash-hub/demultiplex",
|
||||
"git_tag" : "v0.3.4-6-g18e092d"
|
||||
"git_tag" : "v0.3.10-5-gcd2cfac"
|
||||
},
|
||||
"package_config" : {
|
||||
"name" : "demultiplex",
|
||||
"version" : "v0.3.5",
|
||||
"version" : "v0.3.11",
|
||||
"description" : "Demultiplexing pipeline\n",
|
||||
"info" : {
|
||||
"test_resources" : [
|
||||
{
|
||||
"path" : "gs://viash-hub-test-data/demultiplex/v2/",
|
||||
"path" : "gs://viash-hub-resources/demultiplex/v3",
|
||||
"dest" : "testData"
|
||||
}
|
||||
]
|
||||
},
|
||||
"viash_version" : "0.9.0",
|
||||
"viash_version" : "0.9.4",
|
||||
"source" : "src",
|
||||
"target" : "target",
|
||||
"config_mods" : [
|
||||
".requirements.commands := ['ps']\n.runners[.type == 'nextflow'].directives.tag := '$id'\n.resources += {path: '/src/config/labels.config', dest: 'nextflow_labels.config'}\n.runners[.type == 'nextflow'].config.script := 'includeConfig(\\"nextflow_labels.config\\")'\n",
|
||||
".requirements.commands += ['ps']\n.runners[.type == 'nextflow'].directives.tag := '$id'\n.resources += {path: '/src/config/labels.config', dest: 'nextflow_labels.config'}\n.runners[.type == 'nextflow'].config.script := 'includeConfig(\\"nextflow_labels.config\\")'\n",
|
||||
".engines += { type: \\"native\\" }",
|
||||
".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'",
|
||||
".engines[.type == 'docker'].target_tag := 'v0.3.5'"
|
||||
".engines[.type == 'docker'].target_tag := 'v0.3.11'"
|
||||
],
|
||||
"keywords" : [
|
||||
"bioinformatics",
|
||||
@@ -3404,7 +3647,7 @@ def _vdsl3ProcessFactory(Map workflowArgs, Map meta, String rawScript) {
|
||||
// create process from temp file
|
||||
def binding = new nextflow.script.ScriptBinding([:])
|
||||
def session = nextflow.Nextflow.getSession()
|
||||
def parser = new nextflow.script.ScriptParser(session)
|
||||
def parser = _getScriptLoader(session)
|
||||
.setModule(true)
|
||||
.setBinding(binding)
|
||||
def moduleScript = parser.runScript(tempFile)
|
||||
@@ -3418,6 +3661,27 @@ def _vdsl3ProcessFactory(Map workflowArgs, Map meta, String rawScript) {
|
||||
return scriptMeta.getProcess(procKey)
|
||||
}
|
||||
|
||||
// use Reflection to get a ScriptParser / ScriptLoader
|
||||
// <25.02.0-edge: new nextflow.script.ScriptParser(session)
|
||||
// >=25.02.0-edge: nextflow.script.ScriptLoaderFactory.create(session)
|
||||
def _getScriptLoader(nextflow.Session session) {
|
||||
// try using the old method
|
||||
try {
|
||||
Class<?> scriptParserClass = Class.forName('nextflow.script.ScriptParser')
|
||||
return scriptParserClass.getDeclaredConstructor(nextflow.Session).newInstance(session)
|
||||
} catch (ClassNotFoundException e) {
|
||||
// else try with the new method
|
||||
try {
|
||||
Class<?> scriptLoaderFactoryClass = Class.forName('nextflow.script.ScriptLoaderFactory')
|
||||
def createMethod = scriptLoaderFactoryClass.getDeclaredMethod('create', nextflow.Session)
|
||||
return createMethod.invoke(null, session) // null because create is static
|
||||
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | java.lang.reflect.InvocationTargetException e2) {
|
||||
// Handle the case where neither class is found
|
||||
throw new Exception("Neither nextflow.script.ScriptParser nor nextflow.script.ScriptLoaderFactory could be found. Is this a compatible Nextflow version?", e2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// defaults
|
||||
meta["defaults"] = [
|
||||
// key to be used to trace the process and determine output names
|
||||
@@ -3431,7 +3695,7 @@ meta["defaults"] = [
|
||||
"container" : {
|
||||
"registry" : "images.viash-hub.com",
|
||||
"image" : "vsh/demultiplex/io/interop_summary_to_csv",
|
||||
"tag" : "v0.3.5"
|
||||
"tag" : "v0.3.11"
|
||||
},
|
||||
"tag" : "$id"
|
||||
}'''),
|
||||
|
||||
@@ -2,7 +2,7 @@ manifest {
|
||||
name = 'io/interop_summary_to_csv'
|
||||
mainScript = 'main.nf'
|
||||
nextflowVersion = '!>=20.12.1-edge'
|
||||
version = 'v0.3.5'
|
||||
version = 'v0.3.11'
|
||||
}
|
||||
|
||||
process.container = 'nextflow/bash:latest'
|
||||
|
||||
@@ -37,10 +37,10 @@
|
||||
"output_run_summary": {
|
||||
"type":
|
||||
"string",
|
||||
"description": "Type: `file`, required, default: `$id.$key.output_run_summary.output_run_summary`. ",
|
||||
"help_text": "Type: `file`, required, default: `$id.$key.output_run_summary.output_run_summary`. "
|
||||
"description": "Type: `file`, required, default: `$id.$key.output_run_summary`. ",
|
||||
"help_text": "Type: `file`, required, default: `$id.$key.output_run_summary`. "
|
||||
,
|
||||
"default":"$id.$key.output_run_summary.output_run_summary"
|
||||
"default":"$id.$key.output_run_summary"
|
||||
}
|
||||
|
||||
|
||||
@@ -48,10 +48,10 @@
|
||||
"output_index_summary": {
|
||||
"type":
|
||||
"string",
|
||||
"description": "Type: `file`, required, default: `$id.$key.output_index_summary.output_index_summary`. ",
|
||||
"help_text": "Type: `file`, required, default: `$id.$key.output_index_summary.output_index_summary`. "
|
||||
"description": "Type: `file`, required, default: `$id.$key.output_index_summary`. ",
|
||||
"help_text": "Type: `file`, required, default: `$id.$key.output_index_summary`. "
|
||||
,
|
||||
"default":"$id.$key.output_index_summary.output_index_summary"
|
||||
"default":"$id.$key.output_index_summary"
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: "publish"
|
||||
namespace: "io"
|
||||
version: "v0.3.5"
|
||||
version: "v0.3.11"
|
||||
argument_groups:
|
||||
- name: "Input arguments"
|
||||
arguments:
|
||||
@@ -44,6 +44,15 @@ argument_groups:
|
||||
direction: "input"
|
||||
multiple: false
|
||||
multiple_sep: ";"
|
||||
- type: "file"
|
||||
name: "--input_demultiplexer_logs"
|
||||
info: null
|
||||
must_exist: true
|
||||
create_parent: true
|
||||
required: true
|
||||
direction: "input"
|
||||
multiple: false
|
||||
multiple_sep: ";"
|
||||
- name: "Output arguments"
|
||||
arguments:
|
||||
- type: "file"
|
||||
@@ -90,6 +99,17 @@ argument_groups:
|
||||
direction: "output"
|
||||
multiple: false
|
||||
multiple_sep: ";"
|
||||
- type: "file"
|
||||
name: "--output_demultiplexer_logs"
|
||||
info: null
|
||||
default:
|
||||
- "demultiplexer_logs"
|
||||
must_exist: true
|
||||
create_parent: true
|
||||
required: false
|
||||
direction: "output"
|
||||
multiple: false
|
||||
multiple_sep: ";"
|
||||
resources:
|
||||
- type: "bash_script"
|
||||
path: "code.sh"
|
||||
@@ -100,6 +120,9 @@ resources:
|
||||
description: "Publish the processed results of the run"
|
||||
info: null
|
||||
status: "enabled"
|
||||
scope:
|
||||
image: "public"
|
||||
target: "public"
|
||||
requirements:
|
||||
commands:
|
||||
- "ps"
|
||||
@@ -178,7 +201,7 @@ engines:
|
||||
id: "docker"
|
||||
image: "debian:stable-slim"
|
||||
target_registry: "images.viash-hub.com"
|
||||
target_tag: "v0.3.5"
|
||||
target_tag: "v0.3.11"
|
||||
namespace_separator: "/"
|
||||
setup:
|
||||
- type: "apt"
|
||||
@@ -195,29 +218,29 @@ build_info:
|
||||
engine: "docker|native"
|
||||
output: "target/nextflow/io/publish"
|
||||
executable: "target/nextflow/io/publish/main.nf"
|
||||
viash_version: "0.9.0"
|
||||
git_commit: "18e092d169cc2704a2e4e705485d9231911d0484"
|
||||
viash_version: "0.9.4"
|
||||
git_commit: "cd2cfac18e14622e1a5d7a0d489f64c30e83dab2"
|
||||
git_remote: "https://github.com/viash-hub/demultiplex"
|
||||
git_tag: "v0.3.4-6-g18e092d"
|
||||
git_tag: "v0.3.10-5-gcd2cfac"
|
||||
package_config:
|
||||
name: "demultiplex"
|
||||
version: "v0.3.5"
|
||||
version: "v0.3.11"
|
||||
description: "Demultiplexing pipeline\n"
|
||||
info:
|
||||
test_resources:
|
||||
- path: "gs://viash-hub-test-data/demultiplex/v2/"
|
||||
- path: "gs://viash-hub-resources/demultiplex/v3"
|
||||
dest: "testData"
|
||||
viash_version: "0.9.0"
|
||||
viash_version: "0.9.4"
|
||||
source: "src"
|
||||
target: "target"
|
||||
config_mods:
|
||||
- ".requirements.commands := ['ps']\n.runners[.type == 'nextflow'].directives.tag\
|
||||
- ".requirements.commands += ['ps']\n.runners[.type == 'nextflow'].directives.tag\
|
||||
\ := '$id'\n.resources += {path: '/src/config/labels.config', dest: 'nextflow_labels.config'}\n\
|
||||
.runners[.type == 'nextflow'].config.script := 'includeConfig(\"nextflow_labels.config\"\
|
||||
)'\n"
|
||||
- ".engines += { type: \"native\" }"
|
||||
- ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'"
|
||||
- ".engines[.type == 'docker'].target_tag := 'v0.3.5'"
|
||||
- ".engines[.type == 'docker'].target_tag := 'v0.3.11'"
|
||||
keywords:
|
||||
- "bioinformatics"
|
||||
- "sequence"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// publish v0.3.5
|
||||
// publish v0.3.11
|
||||
//
|
||||
// This wrapper script is auto-generated by viash 0.9.0 and is thus a derivative
|
||||
// This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative
|
||||
// work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data
|
||||
// Intuitive.
|
||||
//
|
||||
@@ -82,64 +82,56 @@ def _checkArgumentType(String stage, Map par, Object value, String errorIdentifi
|
||||
foundClass = "List[${e.foundClass}]"
|
||||
}
|
||||
} else if (par.type == "string") {
|
||||
// cast to string if need be
|
||||
// cast to string if need be. only cast if the value is a GString
|
||||
if (value instanceof GString) {
|
||||
value = value.toString()
|
||||
value = value as String
|
||||
}
|
||||
expectedClass = value instanceof String ? null : "String"
|
||||
} else if (par.type == "integer") {
|
||||
// cast to integer if need be
|
||||
if (value instanceof String) {
|
||||
if (value !instanceof Integer) {
|
||||
try {
|
||||
value = value.toInteger()
|
||||
value = value as Integer
|
||||
} catch (NumberFormatException e) {
|
||||
// do nothing
|
||||
expectedClass = "Integer"
|
||||
}
|
||||
}
|
||||
if (value instanceof java.math.BigInteger) {
|
||||
value = value.intValue()
|
||||
}
|
||||
expectedClass = value instanceof Integer ? null : "Integer"
|
||||
} else if (par.type == "long") {
|
||||
// cast to long if need be
|
||||
if (value instanceof String) {
|
||||
if (value !instanceof Long) {
|
||||
try {
|
||||
value = value.toLong()
|
||||
value = value as Long
|
||||
} catch (NumberFormatException e) {
|
||||
// do nothing
|
||||
expectedClass = "Long"
|
||||
}
|
||||
}
|
||||
if (value instanceof Integer) {
|
||||
value = value.toLong()
|
||||
}
|
||||
expectedClass = value instanceof Long ? null : "Long"
|
||||
} else if (par.type == "double") {
|
||||
// cast to double if need be
|
||||
if (value instanceof String) {
|
||||
if (value !instanceof Double) {
|
||||
try {
|
||||
value = value.toDouble()
|
||||
value = value as Double
|
||||
} catch (NumberFormatException e) {
|
||||
// do nothing
|
||||
expectedClass = "Double"
|
||||
}
|
||||
}
|
||||
if (value instanceof java.math.BigDecimal) {
|
||||
value = value.doubleValue()
|
||||
} else if (par.type == "float") {
|
||||
// cast to float if need be
|
||||
if (value !instanceof Float) {
|
||||
try {
|
||||
value = value as Float
|
||||
} catch (NumberFormatException e) {
|
||||
expectedClass = "Float"
|
||||
}
|
||||
}
|
||||
if (value instanceof Float) {
|
||||
value = value.toDouble()
|
||||
}
|
||||
expectedClass = value instanceof Double ? null : "Double"
|
||||
} else if (par.type == "boolean" | par.type == "boolean_true" | par.type == "boolean_false") {
|
||||
// cast to boolean if need be
|
||||
if (value instanceof String) {
|
||||
def valueLower = value.toLowerCase()
|
||||
if (valueLower == "true") {
|
||||
value = true
|
||||
} else if (valueLower == "false") {
|
||||
value = false
|
||||
if (value !instanceof Boolean) {
|
||||
try {
|
||||
value = value as Boolean
|
||||
} catch (Exception e) {
|
||||
expectedClass = "Boolean"
|
||||
}
|
||||
}
|
||||
expectedClass = value instanceof Boolean ? null : "Boolean"
|
||||
} else if (par.type == "file" && (par.direction == "input" || stage == "output")) {
|
||||
// cast to path if need be
|
||||
if (value instanceof String) {
|
||||
@@ -151,10 +143,13 @@ def _checkArgumentType(String stage, Map par, Object value, String errorIdentifi
|
||||
expectedClass = value instanceof Path ? null : "Path"
|
||||
} else if (par.type == "file" && stage == "input" && par.direction == "output") {
|
||||
// cast to string if need be
|
||||
if (value instanceof GString) {
|
||||
value = value.toString()
|
||||
if (value !instanceof String) {
|
||||
try {
|
||||
value = value as String
|
||||
} catch (Exception e) {
|
||||
expectedClass = "String"
|
||||
}
|
||||
}
|
||||
expectedClass = value instanceof String ? null : "String"
|
||||
} else {
|
||||
// didn't find a match for par.type
|
||||
expectedClass = par.type
|
||||
@@ -173,7 +168,7 @@ def _checkArgumentType(String stage, Map par, Object value, String errorIdentifi
|
||||
Map _processInputValues(Map inputs, Map config, String id, String key) {
|
||||
if (!workflow.stubRun) {
|
||||
config.allArguments.each { arg ->
|
||||
if (arg.required) {
|
||||
if (arg.required && arg.direction == "input") {
|
||||
assert inputs.containsKey(arg.plainName) && inputs.get(arg.plainName) != null :
|
||||
"Error in module '${key}' id '${id}': required input argument '${arg.plainName}' is missing"
|
||||
}
|
||||
@@ -192,15 +187,8 @@ Map _processInputValues(Map inputs, Map config, String id, String key) {
|
||||
}
|
||||
|
||||
// helper file: 'src/main/resources/io/viash/runners/nextflow/arguments/_processOutputValues.nf'
|
||||
Map _processOutputValues(Map outputs, Map config, String id, String key) {
|
||||
Map _checkValidOutputArgument(Map outputs, Map config, String id, String key) {
|
||||
if (!workflow.stubRun) {
|
||||
config.allArguments.each { arg ->
|
||||
if (arg.direction == "output" && arg.required) {
|
||||
assert outputs.containsKey(arg.plainName) && outputs.get(arg.plainName) != null :
|
||||
"Error in module '${key}' id '${id}': required output argument '${arg.plainName}' is missing"
|
||||
}
|
||||
}
|
||||
|
||||
outputs = outputs.collectEntries { name, value ->
|
||||
def par = config.allArguments.find { it.plainName == name && it.direction == "output" }
|
||||
assert par != null : "Error in module '${key}' id '${id}': '${name}' is not a valid output argument"
|
||||
@@ -213,6 +201,16 @@ Map _processOutputValues(Map outputs, Map config, String id, String key) {
|
||||
return outputs
|
||||
}
|
||||
|
||||
void _checkAllRequiredOuputsPresent(Map outputs, Map config, String id, String key) {
|
||||
if (!workflow.stubRun) {
|
||||
config.allArguments.each { arg ->
|
||||
if (arg.direction == "output" && arg.required) {
|
||||
assert outputs.containsKey(arg.plainName) && outputs.get(arg.plainName) != null :
|
||||
"Error in module '${key}' id '${id}': required output argument '${arg.plainName}' is missing"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// helper file: 'src/main/resources/io/viash/runners/nextflow/channel/IDChecker.nf'
|
||||
class IDChecker {
|
||||
final def items = [] as Set
|
||||
@@ -1666,6 +1664,162 @@ def joinStates(Closure apply_) {
|
||||
}
|
||||
return joinStatesWf
|
||||
}
|
||||
// helper file: 'src/main/resources/io/viash/runners/nextflow/states/publishFiles.nf'
|
||||
def publishFiles(Map args) {
|
||||
def key_ = args.get("key")
|
||||
|
||||
assert key_ != null : "publishFiles: key must be specified"
|
||||
|
||||
workflow publishFilesWf {
|
||||
take: input_ch
|
||||
main:
|
||||
input_ch
|
||||
| map { tup ->
|
||||
def id_ = tup[0]
|
||||
def state_ = tup[1]
|
||||
|
||||
// the input files and the target output filenames
|
||||
def inputoutputFilenames_ = collectInputOutputPaths(state_, id_ + "." + key_).transpose()
|
||||
def inputFiles_ = inputoutputFilenames_[0]
|
||||
def outputFilenames_ = inputoutputFilenames_[1]
|
||||
|
||||
[id_, inputFiles_, outputFilenames_]
|
||||
}
|
||||
| publishFilesProc
|
||||
emit: input_ch
|
||||
}
|
||||
return publishFilesWf
|
||||
}
|
||||
|
||||
process publishFilesProc {
|
||||
// todo: check publishpath?
|
||||
publishDir path: "${getPublishDir()}/", mode: "copy"
|
||||
tag "$id"
|
||||
input:
|
||||
tuple val(id), path(inputFiles, stageAs: "_inputfile?/*"), val(outputFiles)
|
||||
output:
|
||||
tuple val(id), path{outputFiles}
|
||||
script:
|
||||
def copyCommands = [
|
||||
inputFiles instanceof List ? inputFiles : [inputFiles],
|
||||
outputFiles instanceof List ? outputFiles : [outputFiles]
|
||||
]
|
||||
.transpose()
|
||||
.collectMany{infile, outfile ->
|
||||
if (infile.toString() != outfile.toString()) {
|
||||
[
|
||||
"[ -d \"\$(dirname '${outfile.toString()}')\" ] || mkdir -p \"\$(dirname '${outfile.toString()}')\"",
|
||||
"cp -r '${infile.toString()}' '${outfile.toString()}'"
|
||||
]
|
||||
} else {
|
||||
// no need to copy if infile is the same as outfile
|
||||
[]
|
||||
}
|
||||
}
|
||||
"""
|
||||
echo "Copying output files to destination folder"
|
||||
${copyCommands.join("\n ")}
|
||||
"""
|
||||
}
|
||||
|
||||
|
||||
// this assumes that the state contains no other values other than those specified in the config
|
||||
def publishFilesByConfig(Map args) {
|
||||
def config = args.get("config")
|
||||
assert config != null : "publishFilesByConfig: config must be specified"
|
||||
|
||||
def key_ = args.get("key", config.name)
|
||||
assert key_ != null : "publishFilesByConfig: key must be specified"
|
||||
|
||||
workflow publishFilesSimpleWf {
|
||||
take: input_ch
|
||||
main:
|
||||
input_ch
|
||||
| map { tup ->
|
||||
def id_ = tup[0]
|
||||
def state_ = tup[1] // e.g. [output: new File("myoutput.h5ad"), k: 10]
|
||||
def origState_ = tup[2] // e.g. [output: '$id.$key.foo.h5ad']
|
||||
|
||||
|
||||
// the processed state is a list of [key, value, inputPath, outputFilename] tuples, where
|
||||
// - key is a String
|
||||
// - value is any object that can be serialized to a Yaml (so a String/Integer/Long/Double/Boolean, a List, a Map, or a Path)
|
||||
// - inputPath is a List[Path]
|
||||
// - outputFilename is a List[String]
|
||||
// - (inputPath, outputFilename) are the files that will be copied from src to dest (relative to the state.yaml)
|
||||
def processedState =
|
||||
config.allArguments
|
||||
.findAll { it.direction == "output" }
|
||||
.collectMany { par ->
|
||||
def plainName_ = par.plainName
|
||||
// if the state does not contain the key, it's an
|
||||
// optional argument for which the component did
|
||||
// not generate any output OR multiple channels were emitted
|
||||
// and the output was just not added to using the channel
|
||||
// that is now being parsed
|
||||
if (!state_.containsKey(plainName_)) {
|
||||
return []
|
||||
}
|
||||
def value = state_[plainName_]
|
||||
// if the parameter is not a file, it should be stored
|
||||
// in the state as-is, but is not something that needs
|
||||
// to be copied from the source path to the dest path
|
||||
if (par.type != "file") {
|
||||
return [[inputPath: [], outputFilename: []]]
|
||||
}
|
||||
// if the orig state does not contain this filename,
|
||||
// it's an optional argument for which the user specified
|
||||
// that it should not be returned as a state
|
||||
if (!origState_.containsKey(plainName_)) {
|
||||
return []
|
||||
}
|
||||
def filenameTemplate = origState_[plainName_]
|
||||
// if the pararameter is multiple: true, fetch the template
|
||||
if (par.multiple && filenameTemplate instanceof List) {
|
||||
filenameTemplate = filenameTemplate[0]
|
||||
}
|
||||
// instantiate the template
|
||||
def filename = filenameTemplate
|
||||
.replaceAll('\\$id', id_)
|
||||
.replaceAll('\\$\\{id\\}', id_)
|
||||
.replaceAll('\\$key', key_)
|
||||
.replaceAll('\\$\\{key\\}', key_)
|
||||
if (par.multiple) {
|
||||
// if the parameter is multiple: true, the filename
|
||||
// should contain a wildcard '*' that is replaced with
|
||||
// the index of the file
|
||||
assert filename.contains("*") : "Module '${key_}' id '${id_}': Multiple output files specified, but no wildcard '*' in the filename: ${filename}"
|
||||
def outputPerFile = value.withIndex().collect{ val, ix ->
|
||||
def filename_ix = filename.replace("*", ix.toString())
|
||||
def inputPath = val instanceof File ? val.toPath() : val
|
||||
[inputPath: inputPath, outputFilename: filename_ix]
|
||||
}
|
||||
def transposedOutputs = ["inputPath", "outputFilename"].collectEntries{ key ->
|
||||
[key, outputPerFile.collect{dic -> dic[key]}]
|
||||
}
|
||||
return [[key: plainName_] + transposedOutputs]
|
||||
} else {
|
||||
def value_ = java.nio.file.Paths.get(filename)
|
||||
def inputPath = value instanceof File ? value.toPath() : value
|
||||
return [[inputPath: [inputPath], outputFilename: [filename]]]
|
||||
}
|
||||
}
|
||||
|
||||
def inputPaths = processedState.collectMany{it.inputPath}
|
||||
def outputFilenames = processedState.collectMany{it.outputFilename}
|
||||
|
||||
|
||||
[id_, inputPaths, outputFilenames]
|
||||
}
|
||||
| publishFilesProc
|
||||
emit: input_ch
|
||||
}
|
||||
return publishFilesSimpleWf
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// helper file: 'src/main/resources/io/viash/runners/nextflow/states/publishStates.nf'
|
||||
def collectFiles(obj) {
|
||||
if (obj instanceof java.io.File || obj instanceof Path) {
|
||||
@@ -1723,8 +1877,6 @@ def publishStates(Map args) {
|
||||
|
||||
// the input files and the target output filenames
|
||||
def inputoutputFilenames_ = collectInputOutputPaths(state_, id_ + "." + key_).transpose()
|
||||
def inputFiles_ = inputoutputFilenames_[0]
|
||||
def outputFilenames_ = inputoutputFilenames_[1]
|
||||
|
||||
def yamlFilename = yamlTemplate_
|
||||
.replaceAll('\\$id', id_)
|
||||
@@ -1737,7 +1889,7 @@ def publishStates(Map args) {
|
||||
// convert state to yaml blob
|
||||
def yamlBlob_ = toRelativeTaggedYamlBlob([id: id_] + state_, java.nio.file.Paths.get(yamlFilename))
|
||||
|
||||
[id_, yamlBlob_, yamlFilename, inputFiles_, outputFilenames_]
|
||||
[id_, yamlBlob_, yamlFilename]
|
||||
}
|
||||
| publishStatesProc
|
||||
emit: input_ch
|
||||
@@ -1749,33 +1901,17 @@ process publishStatesProc {
|
||||
publishDir path: "${getPublishDir()}/", mode: "copy"
|
||||
tag "$id"
|
||||
input:
|
||||
tuple val(id), val(yamlBlob), val(yamlFile), path(inputFiles, stageAs: "_inputfile?/*"), val(outputFiles)
|
||||
tuple val(id), val(yamlBlob), val(yamlFile)
|
||||
output:
|
||||
tuple val(id), path{[yamlFile] + outputFiles}
|
||||
tuple val(id), path{[yamlFile]}
|
||||
script:
|
||||
def copyCommands = [
|
||||
inputFiles instanceof List ? inputFiles : [inputFiles],
|
||||
outputFiles instanceof List ? outputFiles : [outputFiles]
|
||||
]
|
||||
.transpose()
|
||||
.collectMany{infile, outfile ->
|
||||
if (infile.toString() != outfile.toString()) {
|
||||
[
|
||||
"[ -d \"\$(dirname '${outfile.toString()}')\" ] || mkdir -p \"\$(dirname '${outfile.toString()}')\"",
|
||||
"cp -r '${infile.toString()}' '${outfile.toString()}'"
|
||||
]
|
||||
} else {
|
||||
// no need to copy if infile is the same as outfile
|
||||
[]
|
||||
}
|
||||
}
|
||||
"""
|
||||
mkdir -p "\$(dirname '${yamlFile}')"
|
||||
echo "Storing state as yaml"
|
||||
echo '${yamlBlob}' > '${yamlFile}'
|
||||
echo "Copying output files to destination folder"
|
||||
${copyCommands.join("\n ")}
|
||||
"""
|
||||
mkdir -p "\$(dirname '${yamlFile}')"
|
||||
echo "Storing state as yaml"
|
||||
cat > '${yamlFile}' << HERE
|
||||
${yamlBlob}
|
||||
HERE
|
||||
"""
|
||||
}
|
||||
|
||||
|
||||
@@ -1806,13 +1942,10 @@ def publishStatesByConfig(Map args) {
|
||||
.replaceAll('\\$\\{key\\}', key_)
|
||||
def yamlDir = java.nio.file.Paths.get(yamlFilename).getParent()
|
||||
|
||||
// the processed state is a list of [key, value, inputPath, outputFilename] tuples, where
|
||||
// the processed state is a list of [key, value] tuples, where
|
||||
// - key is a String
|
||||
// - value is any object that can be serialized to a Yaml (so a String/Integer/Long/Double/Boolean, a List, a Map, or a Path)
|
||||
// - inputPath is a List[Path]
|
||||
// - outputFilename is a List[String]
|
||||
// - (key, value) are the tuples that will be saved to the state.yaml file
|
||||
// - (inputPath, outputFilename) are the files that will be copied from src to dest (relative to the state.yaml)
|
||||
def processedState =
|
||||
config.allArguments
|
||||
.findAll { it.direction == "output" }
|
||||
@@ -1829,7 +1962,7 @@ def publishStatesByConfig(Map args) {
|
||||
// in the state as-is, but is not something that needs
|
||||
// to be copied from the source path to the dest path
|
||||
if (par.type != "file") {
|
||||
return [[key: plainName_, value: value, inputPath: [], outputFilename: []]]
|
||||
return [[key: plainName_, value: value]]
|
||||
}
|
||||
// if the orig state does not contain this filename,
|
||||
// it's an optional argument for which the user specified
|
||||
@@ -1860,13 +1993,9 @@ def publishStatesByConfig(Map args) {
|
||||
if (yamlDir != null) {
|
||||
value_ = yamlDir.relativize(value_)
|
||||
}
|
||||
def inputPath = val instanceof File ? val.toPath() : val
|
||||
[value: value_, inputPath: inputPath, outputFilename: filename_ix]
|
||||
return value_
|
||||
}
|
||||
def transposedOutputs = ["value", "inputPath", "outputFilename"].collectEntries{ key ->
|
||||
[key, outputPerFile.collect{dic -> dic[key]}]
|
||||
}
|
||||
return [[key: plainName_] + transposedOutputs]
|
||||
return [["key": plainName_, "value": outputPerFile]]
|
||||
} else {
|
||||
def value_ = java.nio.file.Paths.get(filename)
|
||||
// if id contains a slash
|
||||
@@ -1874,18 +2003,17 @@ def publishStatesByConfig(Map args) {
|
||||
value_ = yamlDir.relativize(value_)
|
||||
}
|
||||
def inputPath = value instanceof File ? value.toPath() : value
|
||||
return [[key: plainName_, value: value_, inputPath: [inputPath], outputFilename: [filename]]]
|
||||
return [["key": plainName_, value: value_]]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def updatedState_ = processedState.collectEntries{[it.key, it.value]}
|
||||
def inputPaths = processedState.collectMany{it.inputPath}
|
||||
def outputFilenames = processedState.collectMany{it.outputFilename}
|
||||
|
||||
// convert state to yaml blob
|
||||
def yamlBlob_ = toTaggedYamlBlob([id: id_] + updatedState_)
|
||||
|
||||
[id_, yamlBlob_, yamlFilename, inputPaths, outputFilenames]
|
||||
[id_, yamlBlob_, yamlFilename]
|
||||
}
|
||||
| publishStatesProc
|
||||
emit: input_ch
|
||||
@@ -2559,7 +2687,8 @@ def _debug(workflowArgs, debugKey) {
|
||||
def workflowFactory(Map args, Map defaultWfArgs, Map meta) {
|
||||
def workflowArgs = processWorkflowArgs(args, defaultWfArgs, meta)
|
||||
def key_ = workflowArgs["key"]
|
||||
|
||||
def multipleArgs = meta.config.allArguments.findAll{ it.multiple }.collect{it.plainName}
|
||||
|
||||
workflow workflowInstance {
|
||||
take: input_
|
||||
|
||||
@@ -2716,12 +2845,36 @@ def workflowFactory(Map args, Map defaultWfArgs, Map meta) {
|
||||
}
|
||||
|
||||
// TODO: move some of the _meta.join_id wrangling to the safeJoin() function.
|
||||
def chInitialOutput = chArgsWithDefaults
|
||||
def chInitialOutputMulti = chArgsWithDefaults
|
||||
| _debug(workflowArgs, "processed")
|
||||
// run workflow
|
||||
| innerWorkflowFactory(workflowArgs)
|
||||
// check output tuple
|
||||
| map { id_, output_ ->
|
||||
def chInitialOutputList = chInitialOutputMulti instanceof List ? chInitialOutputMulti : [chInitialOutputMulti]
|
||||
assert chInitialOutputList.size() > 0: "should have emitted at least one output channel"
|
||||
// Add a channel ID to the events, which designates the channel the event was emitted from as a running number
|
||||
// This number is used to sort the events later when the events are gathered from across the channels.
|
||||
def chInitialOutputListWithIndexedEvents = chInitialOutputList.withIndex().collect{channel, channelIndex ->
|
||||
def newChannel = channel
|
||||
| map {tuple ->
|
||||
assert tuple instanceof List :
|
||||
"Error in module '${key_}': element in output channel should be a tuple [id, data, ...otherargs...]\n" +
|
||||
" Example: [\"id\", [input: file('foo.txt'), arg: 10]].\n" +
|
||||
" Expected class: List. Found: tuple.getClass() is ${tuple.getClass()}"
|
||||
|
||||
def newEvent = [channelIndex] + tuple
|
||||
return newEvent
|
||||
}
|
||||
return newChannel
|
||||
}
|
||||
// Put the events into 1 channel, cover case where there is only one channel is emitted
|
||||
def chInitialOutput = chInitialOutputList.size() > 1 ? \
|
||||
chInitialOutputListWithIndexedEvents[0].mix(*chInitialOutputListWithIndexedEvents.tail()) : \
|
||||
chInitialOutputListWithIndexedEvents[0]
|
||||
def chInitialOutputProcessed = chInitialOutput
|
||||
| map { tuple ->
|
||||
def channelId = tuple[0]
|
||||
def id_ = tuple[1]
|
||||
def output_ = tuple[2]
|
||||
|
||||
// see if output map contains metadata
|
||||
def meta_ =
|
||||
@@ -2734,19 +2887,94 @@ def workflowFactory(Map args, Map defaultWfArgs, Map meta) {
|
||||
output_ = output_.findAll{k, v -> k != "_meta"}
|
||||
|
||||
// check value types
|
||||
output_ = _processOutputValues(output_, meta.config, id_, key_)
|
||||
output_ = _checkValidOutputArgument(output_, meta.config, id_, key_)
|
||||
|
||||
// simplify output if need be
|
||||
if (workflowArgs.auto.simplifyOutput && output_.size() == 1) {
|
||||
output_ = output_.values()[0]
|
||||
}
|
||||
|
||||
[join_id, id_, output_]
|
||||
[join_id, channelId, id_, output_]
|
||||
}
|
||||
// | view{"chInitialOutput: ${it.take(3)}"}
|
||||
|
||||
// join the output [prev_id, channel_id, new_id, output] with the previous state [prev_id, state, ...]
|
||||
def chPublishWithPreviousState = safeJoin(chInitialOutputProcessed, chRunFiltered, key_)
|
||||
// input tuple format: [join_id, channel_id, id, output, prev_state, ...]
|
||||
// output tuple format: [join_id, channel_id, id, new_state, ...]
|
||||
| map{ tup ->
|
||||
def new_state = workflowArgs.toState(tup.drop(2).take(3))
|
||||
tup.take(3) + [new_state] + tup.drop(5)
|
||||
}
|
||||
if (workflowArgs.auto.publish == "state") {
|
||||
def chPublishFiles = chPublishWithPreviousState
|
||||
// input tuple format: [join_id, channel_id, id, new_state, ...]
|
||||
// output tuple format: [join_id, channel_id, id, new_state]
|
||||
| map{ tup ->
|
||||
tup.take(4)
|
||||
}
|
||||
|
||||
safeJoin(chPublishFiles, chArgsWithDefaults, key_)
|
||||
// input tuple format: [join_id, channel_id, id, new_state, orig_state, ...]
|
||||
// output tuple format: [id, new_state, orig_state]
|
||||
| map { tup ->
|
||||
tup.drop(2).take(3)
|
||||
}
|
||||
| publishFilesByConfig(key: key_, config: meta.config)
|
||||
}
|
||||
// Join the state from the events that were emitted from different channels
|
||||
def chJoined = chInitialOutputProcessed
|
||||
| map {tuple ->
|
||||
def join_id = tuple[0]
|
||||
def channel_id = tuple[1]
|
||||
def id = tuple[2]
|
||||
def other = tuple.drop(3)
|
||||
// Below, groupTuple is used to join the events. To make sure resuming a workflow
|
||||
// keeps working, the output state must be deterministic. This means the state needs to be
|
||||
// sorted with groupTuple's has a 'sort' argument. This argument can be set to 'hash',
|
||||
// but hashing the state when it is large can be problematic in terms of performance.
|
||||
// Therefore, a custom comparator function is provided. We add the channel ID to the
|
||||
// states so that we can use the channel ID to sort the items.
|
||||
def stateWithChannelID = [[channel_id] * other.size(), other].transpose()
|
||||
// A comparator that is provided to groupTuple's 'sort' argument is applied
|
||||
// to all elements of the event tuple (that is not the 'id'). The comparator
|
||||
// closure that is used below expects the input to be List. So the join_id and
|
||||
// channel_id must also be wrapped in a list.
|
||||
[[join_id], [channel_id], id] + stateWithChannelID
|
||||
}
|
||||
| groupTuple(by: 2, sort: {a, b -> a[0] <=> b[0]}, size: chInitialOutputList.size(), remainder: true)
|
||||
| map {join_ids, _, id, statesWithChannelID ->
|
||||
// Remove the channel IDs from the states
|
||||
def states = statesWithChannelID.collect{it[1]}
|
||||
def newJoinId = join_ids.flatten().unique{a, b -> a <=> b}
|
||||
assert newJoinId.size() == 1: "Multiple events were emitted for '$id'."
|
||||
def newJoinIdUnique = newJoinId[0]
|
||||
|
||||
// Merge the states from the different channels
|
||||
def newState = states.inject([:]){ old_state, state_to_add ->
|
||||
return old_state + state_to_add.collectEntries{k, v ->
|
||||
if (!multipleArgs.contains(k)) {
|
||||
// if the key is not a multiple argument, we expect only one value
|
||||
if (old_state.containsKey(k)) {
|
||||
assert old_state[k] == v : "ID $id: multiple entries for argument $k were emitted."
|
||||
}
|
||||
[k, v]
|
||||
} else {
|
||||
// if the key is a multiple argument, append the different values into one list
|
||||
def prevValue = old_state.getOrDefault(k, [])
|
||||
def prevValueAsList = prevValue instanceof List ? prevValue : [prevValue]
|
||||
[k, prevValueAsList + v]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_checkAllRequiredOuputsPresent(newState, meta.config, id, key_)
|
||||
|
||||
// simplify output if need be
|
||||
if (workflowArgs.auto.simplifyOutput && newState.size() == 1) {
|
||||
newState = newState.values()[0]
|
||||
}
|
||||
|
||||
return [newJoinIdUnique, id, newState]
|
||||
}
|
||||
|
||||
// join the output [prev_id, new_id, output] with the previous state [prev_id, state, ...]
|
||||
def chNewState = safeJoin(chInitialOutput, chRunFiltered, key_)
|
||||
def chNewState = safeJoin(chJoined, chRunFiltered, key_)
|
||||
// input tuple format: [join_id, id, output, prev_state, ...]
|
||||
// output tuple format: [join_id, id, new_state, ...]
|
||||
| map{ tup ->
|
||||
@@ -2755,23 +2983,21 @@ def workflowFactory(Map args, Map defaultWfArgs, Map meta) {
|
||||
}
|
||||
|
||||
if (workflowArgs.auto.publish == "state") {
|
||||
def chPublish = chNewState
|
||||
def chPublishStates = chNewState
|
||||
// input tuple format: [join_id, id, new_state, ...]
|
||||
// output tuple format: [join_id, id, new_state]
|
||||
| map{ tup ->
|
||||
tup.take(3)
|
||||
}
|
||||
|
||||
safeJoin(chPublish, chArgsWithDefaults, key_)
|
||||
safeJoin(chPublishStates, chArgsWithDefaults, key_)
|
||||
// input tuple format: [join_id, id, new_state, orig_state, ...]
|
||||
// output tuple format: [id, new_state, orig_state]
|
||||
| map { tup ->
|
||||
tup.drop(1).take(3)
|
||||
}
|
||||
}
|
||||
| publishStatesByConfig(key: key_, config: meta.config)
|
||||
}
|
||||
|
||||
// remove join_id and meta
|
||||
chReturn = chNewState
|
||||
| map { tup ->
|
||||
// input tuple format: [join_id, id, new_state, ...]
|
||||
@@ -2806,7 +3032,7 @@ meta = [
|
||||
"config": processConfig(readJsonBlob('''{
|
||||
"name" : "publish",
|
||||
"namespace" : "io",
|
||||
"version" : "v0.3.5",
|
||||
"version" : "v0.3.11",
|
||||
"argument_groups" : [
|
||||
{
|
||||
"name" : "Input arguments",
|
||||
@@ -2854,6 +3080,16 @@ meta = [
|
||||
"direction" : "input",
|
||||
"multiple" : false,
|
||||
"multiple_sep" : ";"
|
||||
},
|
||||
{
|
||||
"type" : "file",
|
||||
"name" : "--input_demultiplexer_logs",
|
||||
"must_exist" : true,
|
||||
"create_parent" : true,
|
||||
"required" : true,
|
||||
"direction" : "input",
|
||||
"multiple" : false,
|
||||
"multiple_sep" : ";"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -2911,6 +3147,19 @@ meta = [
|
||||
"direction" : "output",
|
||||
"multiple" : false,
|
||||
"multiple_sep" : ";"
|
||||
},
|
||||
{
|
||||
"type" : "file",
|
||||
"name" : "--output_demultiplexer_logs",
|
||||
"default" : [
|
||||
"demultiplexer_logs"
|
||||
],
|
||||
"must_exist" : true,
|
||||
"create_parent" : true,
|
||||
"required" : false,
|
||||
"direction" : "output",
|
||||
"multiple" : false,
|
||||
"multiple_sep" : ";"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -2929,6 +3178,10 @@ meta = [
|
||||
],
|
||||
"description" : "Publish the processed results of the run",
|
||||
"status" : "enabled",
|
||||
"scope" : {
|
||||
"image" : "public",
|
||||
"target" : "public"
|
||||
},
|
||||
"requirements" : {
|
||||
"commands" : [
|
||||
"ps"
|
||||
@@ -3021,7 +3274,7 @@ meta = [
|
||||
"id" : "docker",
|
||||
"image" : "debian:stable-slim",
|
||||
"target_registry" : "images.viash-hub.com",
|
||||
"target_tag" : "v0.3.5",
|
||||
"target_tag" : "v0.3.11",
|
||||
"namespace_separator" : "/",
|
||||
"setup" : [
|
||||
{
|
||||
@@ -3043,31 +3296,31 @@ meta = [
|
||||
"runner" : "nextflow",
|
||||
"engine" : "docker|native",
|
||||
"output" : "target/nextflow/io/publish",
|
||||
"viash_version" : "0.9.0",
|
||||
"git_commit" : "18e092d169cc2704a2e4e705485d9231911d0484",
|
||||
"viash_version" : "0.9.4",
|
||||
"git_commit" : "cd2cfac18e14622e1a5d7a0d489f64c30e83dab2",
|
||||
"git_remote" : "https://github.com/viash-hub/demultiplex",
|
||||
"git_tag" : "v0.3.4-6-g18e092d"
|
||||
"git_tag" : "v0.3.10-5-gcd2cfac"
|
||||
},
|
||||
"package_config" : {
|
||||
"name" : "demultiplex",
|
||||
"version" : "v0.3.5",
|
||||
"version" : "v0.3.11",
|
||||
"description" : "Demultiplexing pipeline\n",
|
||||
"info" : {
|
||||
"test_resources" : [
|
||||
{
|
||||
"path" : "gs://viash-hub-test-data/demultiplex/v2/",
|
||||
"path" : "gs://viash-hub-resources/demultiplex/v3",
|
||||
"dest" : "testData"
|
||||
}
|
||||
]
|
||||
},
|
||||
"viash_version" : "0.9.0",
|
||||
"viash_version" : "0.9.4",
|
||||
"source" : "src",
|
||||
"target" : "target",
|
||||
"config_mods" : [
|
||||
".requirements.commands := ['ps']\n.runners[.type == 'nextflow'].directives.tag := '$id'\n.resources += {path: '/src/config/labels.config', dest: 'nextflow_labels.config'}\n.runners[.type == 'nextflow'].config.script := 'includeConfig(\\"nextflow_labels.config\\")'\n",
|
||||
".requirements.commands += ['ps']\n.runners[.type == 'nextflow'].directives.tag := '$id'\n.resources += {path: '/src/config/labels.config', dest: 'nextflow_labels.config'}\n.runners[.type == 'nextflow'].config.script := 'includeConfig(\\"nextflow_labels.config\\")'\n",
|
||||
".engines += { type: \\"native\\" }",
|
||||
".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'",
|
||||
".engines[.type == 'docker'].target_tag := 'v0.3.5'"
|
||||
".engines[.type == 'docker'].target_tag := 'v0.3.11'"
|
||||
],
|
||||
"keywords" : [
|
||||
"bioinformatics",
|
||||
@@ -3100,10 +3353,12 @@ $( if [ ! -z ${VIASH_PAR_INPUT+x} ]; then echo "${VIASH_PAR_INPUT}" | sed "s#'#'
|
||||
$( if [ ! -z ${VIASH_PAR_INPUT_FALCO+x} ]; then echo "${VIASH_PAR_INPUT_FALCO}" | sed "s#'#'\\"'\\"'#g;s#.*#par_input_falco='&'#" ; else echo "# par_input_falco="; fi )
|
||||
$( if [ ! -z ${VIASH_PAR_INPUT_MULTIQC+x} ]; then echo "${VIASH_PAR_INPUT_MULTIQC}" | sed "s#'#'\\"'\\"'#g;s#.*#par_input_multiqc='&'#" ; else echo "# par_input_multiqc="; fi )
|
||||
$( if [ ! -z ${VIASH_PAR_INPUT_RUN_INFORMATION+x} ]; then echo "${VIASH_PAR_INPUT_RUN_INFORMATION}" | sed "s#'#'\\"'\\"'#g;s#.*#par_input_run_information='&'#" ; else echo "# par_input_run_information="; fi )
|
||||
$( if [ ! -z ${VIASH_PAR_INPUT_DEMULTIPLEXER_LOGS+x} ]; then echo "${VIASH_PAR_INPUT_DEMULTIPLEXER_LOGS}" | sed "s#'#'\\"'\\"'#g;s#.*#par_input_demultiplexer_logs='&'#" ; else echo "# par_input_demultiplexer_logs="; fi )
|
||||
$( if [ ! -z ${VIASH_PAR_OUTPUT+x} ]; then echo "${VIASH_PAR_OUTPUT}" | sed "s#'#'\\"'\\"'#g;s#.*#par_output='&'#" ; else echo "# par_output="; fi )
|
||||
$( if [ ! -z ${VIASH_PAR_OUTPUT_FALCO+x} ]; then echo "${VIASH_PAR_OUTPUT_FALCO}" | sed "s#'#'\\"'\\"'#g;s#.*#par_output_falco='&'#" ; else echo "# par_output_falco="; fi )
|
||||
$( if [ ! -z ${VIASH_PAR_OUTPUT_MULTIQC+x} ]; then echo "${VIASH_PAR_OUTPUT_MULTIQC}" | sed "s#'#'\\"'\\"'#g;s#.*#par_output_multiqc='&'#" ; else echo "# par_output_multiqc="; fi )
|
||||
$( if [ ! -z ${VIASH_PAR_OUTPUT_RUN_INFORMATION+x} ]; then echo "${VIASH_PAR_OUTPUT_RUN_INFORMATION}" | sed "s#'#'\\"'\\"'#g;s#.*#par_output_run_information='&'#" ; else echo "# par_output_run_information="; fi )
|
||||
$( if [ ! -z ${VIASH_PAR_OUTPUT_DEMULTIPLEXER_LOGS+x} ]; then echo "${VIASH_PAR_OUTPUT_DEMULTIPLEXER_LOGS}" | sed "s#'#'\\"'\\"'#g;s#.*#par_output_demultiplexer_logs='&'#" ; else echo "# par_output_demultiplexer_logs="; fi )
|
||||
$( if [ ! -z ${VIASH_META_NAME+x} ]; then echo "${VIASH_META_NAME}" | sed "s#'#'\\"'\\"'#g;s#.*#meta_name='&'#" ; else echo "# meta_name="; fi )
|
||||
$( if [ ! -z ${VIASH_META_FUNCTIONALITY_NAME+x} ]; then echo "${VIASH_META_FUNCTIONALITY_NAME}" | sed "s#'#'\\"'\\"'#g;s#.*#meta_functionality_name='&'#" ; else echo "# meta_functionality_name="; fi )
|
||||
$( if [ ! -z ${VIASH_META_RESOURCES_DIR+x} ]; then echo "${VIASH_META_RESOURCES_DIR}" | sed "s#'#'\\"'\\"'#g;s#.*#meta_resources_dir='&'#" ; else echo "# meta_resources_dir="; fi )
|
||||
@@ -3131,6 +3386,7 @@ set -eo pipefail
|
||||
declare -A input_output_mapping=(["par_input"]="par_output"
|
||||
["par_input_multiqc"]="par_output_multiqc"
|
||||
["par_input_run_information"]="par_output_run_information"
|
||||
["par_input_demultiplexer_logs"]="par_output_demultiplexer_logs"
|
||||
)
|
||||
|
||||
for input_argument_name in "\\${!input_output_mapping[@]}"
|
||||
@@ -3491,7 +3747,7 @@ def _vdsl3ProcessFactory(Map workflowArgs, Map meta, String rawScript) {
|
||||
// create process from temp file
|
||||
def binding = new nextflow.script.ScriptBinding([:])
|
||||
def session = nextflow.Nextflow.getSession()
|
||||
def parser = new nextflow.script.ScriptParser(session)
|
||||
def parser = _getScriptLoader(session)
|
||||
.setModule(true)
|
||||
.setBinding(binding)
|
||||
def moduleScript = parser.runScript(tempFile)
|
||||
@@ -3505,6 +3761,27 @@ def _vdsl3ProcessFactory(Map workflowArgs, Map meta, String rawScript) {
|
||||
return scriptMeta.getProcess(procKey)
|
||||
}
|
||||
|
||||
// use Reflection to get a ScriptParser / ScriptLoader
|
||||
// <25.02.0-edge: new nextflow.script.ScriptParser(session)
|
||||
// >=25.02.0-edge: nextflow.script.ScriptLoaderFactory.create(session)
|
||||
def _getScriptLoader(nextflow.Session session) {
|
||||
// try using the old method
|
||||
try {
|
||||
Class<?> scriptParserClass = Class.forName('nextflow.script.ScriptParser')
|
||||
return scriptParserClass.getDeclaredConstructor(nextflow.Session).newInstance(session)
|
||||
} catch (ClassNotFoundException e) {
|
||||
// else try with the new method
|
||||
try {
|
||||
Class<?> scriptLoaderFactoryClass = Class.forName('nextflow.script.ScriptLoaderFactory')
|
||||
def createMethod = scriptLoaderFactoryClass.getDeclaredMethod('create', nextflow.Session)
|
||||
return createMethod.invoke(null, session) // null because create is static
|
||||
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | java.lang.reflect.InvocationTargetException e2) {
|
||||
// Handle the case where neither class is found
|
||||
throw new Exception("Neither nextflow.script.ScriptParser nor nextflow.script.ScriptLoaderFactory could be found. Is this a compatible Nextflow version?", e2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// defaults
|
||||
meta["defaults"] = [
|
||||
// key to be used to trace the process and determine output names
|
||||
@@ -3518,7 +3795,7 @@ meta["defaults"] = [
|
||||
"container" : {
|
||||
"registry" : "images.viash-hub.com",
|
||||
"image" : "vsh/demultiplex/io/publish",
|
||||
"tag" : "v0.3.5"
|
||||
"tag" : "v0.3.11"
|
||||
},
|
||||
"tag" : "$id"
|
||||
}'''),
|
||||
|
||||
@@ -2,7 +2,7 @@ manifest {
|
||||
name = 'io/publish'
|
||||
mainScript = 'main.nf'
|
||||
nextflowVersion = '!>=20.12.1-edge'
|
||||
version = 'v0.3.5'
|
||||
version = 'v0.3.11'
|
||||
description = 'Publish the processed results of the run'
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,16 @@
|
||||
}
|
||||
|
||||
|
||||
,
|
||||
"input_demultiplexer_logs": {
|
||||
"type":
|
||||
"string",
|
||||
"description": "Type: `file`, required. ",
|
||||
"help_text": "Type: `file`, required. "
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
@@ -67,10 +77,10 @@
|
||||
"output": {
|
||||
"type":
|
||||
"string",
|
||||
"description": "Type: `file`, default: `$id.$key.output.output`. ",
|
||||
"help_text": "Type: `file`, default: `$id.$key.output.output`. "
|
||||
"description": "Type: `file`, default: `fastq`. ",
|
||||
"help_text": "Type: `file`, default: `fastq`. "
|
||||
,
|
||||
"default":"$id.$key.output.output"
|
||||
"default":"fastq"
|
||||
}
|
||||
|
||||
|
||||
@@ -78,10 +88,10 @@
|
||||
"output_falco": {
|
||||
"type":
|
||||
"string",
|
||||
"description": "Type: `file`, default: `$id.$key.output_falco.output_falco`. ",
|
||||
"help_text": "Type: `file`, default: `$id.$key.output_falco.output_falco`. "
|
||||
"description": "Type: `file`, default: `qc/fastqc`. ",
|
||||
"help_text": "Type: `file`, default: `qc/fastqc`. "
|
||||
,
|
||||
"default":"$id.$key.output_falco.output_falco"
|
||||
"default":"qc/fastqc"
|
||||
}
|
||||
|
||||
|
||||
@@ -89,10 +99,10 @@
|
||||
"output_multiqc": {
|
||||
"type":
|
||||
"string",
|
||||
"description": "Type: `file`, default: `$id.$key.output_multiqc.html`. ",
|
||||
"help_text": "Type: `file`, default: `$id.$key.output_multiqc.html`. "
|
||||
"description": "Type: `file`, default: `qc/multiqc_report.html`. ",
|
||||
"help_text": "Type: `file`, default: `qc/multiqc_report.html`. "
|
||||
,
|
||||
"default":"$id.$key.output_multiqc.html"
|
||||
"default":"qc/multiqc_report.html"
|
||||
}
|
||||
|
||||
|
||||
@@ -100,10 +110,21 @@
|
||||
"output_run_information": {
|
||||
"type":
|
||||
"string",
|
||||
"description": "Type: `file`, default: `$id.$key.output_run_information.csv`. ",
|
||||
"help_text": "Type: `file`, default: `$id.$key.output_run_information.csv`. "
|
||||
"description": "Type: `file`, default: `run_information.csv`. ",
|
||||
"help_text": "Type: `file`, default: `run_information.csv`. "
|
||||
,
|
||||
"default":"$id.$key.output_run_information.csv"
|
||||
"default":"run_information.csv"
|
||||
}
|
||||
|
||||
|
||||
,
|
||||
"output_demultiplexer_logs": {
|
||||
"type":
|
||||
"string",
|
||||
"description": "Type: `file`, default: `demultiplexer_logs`. ",
|
||||
"help_text": "Type: `file`, default: `demultiplexer_logs`. "
|
||||
,
|
||||
"default":"demultiplexer_logs"
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: "untar"
|
||||
namespace: "io"
|
||||
version: "v0.3.5"
|
||||
version: "v0.3.11"
|
||||
argument_groups:
|
||||
- name: "Input arguments"
|
||||
arguments:
|
||||
@@ -57,6 +57,9 @@ test_resources:
|
||||
is_executable: true
|
||||
info: null
|
||||
status: "enabled"
|
||||
scope:
|
||||
image: "public"
|
||||
target: "public"
|
||||
requirements:
|
||||
commands:
|
||||
- "ps"
|
||||
@@ -135,7 +138,7 @@ engines:
|
||||
id: "docker"
|
||||
image: "debian:stable-slim"
|
||||
target_registry: "images.viash-hub.com"
|
||||
target_tag: "v0.3.5"
|
||||
target_tag: "v0.3.11"
|
||||
namespace_separator: "/"
|
||||
setup:
|
||||
- type: "apt"
|
||||
@@ -152,29 +155,29 @@ build_info:
|
||||
engine: "docker|native"
|
||||
output: "target/nextflow/io/untar"
|
||||
executable: "target/nextflow/io/untar/main.nf"
|
||||
viash_version: "0.9.0"
|
||||
git_commit: "18e092d169cc2704a2e4e705485d9231911d0484"
|
||||
viash_version: "0.9.4"
|
||||
git_commit: "cd2cfac18e14622e1a5d7a0d489f64c30e83dab2"
|
||||
git_remote: "https://github.com/viash-hub/demultiplex"
|
||||
git_tag: "v0.3.4-6-g18e092d"
|
||||
git_tag: "v0.3.10-5-gcd2cfac"
|
||||
package_config:
|
||||
name: "demultiplex"
|
||||
version: "v0.3.5"
|
||||
version: "v0.3.11"
|
||||
description: "Demultiplexing pipeline\n"
|
||||
info:
|
||||
test_resources:
|
||||
- path: "gs://viash-hub-test-data/demultiplex/v2/"
|
||||
- path: "gs://viash-hub-resources/demultiplex/v3"
|
||||
dest: "testData"
|
||||
viash_version: "0.9.0"
|
||||
viash_version: "0.9.4"
|
||||
source: "src"
|
||||
target: "target"
|
||||
config_mods:
|
||||
- ".requirements.commands := ['ps']\n.runners[.type == 'nextflow'].directives.tag\
|
||||
- ".requirements.commands += ['ps']\n.runners[.type == 'nextflow'].directives.tag\
|
||||
\ := '$id'\n.resources += {path: '/src/config/labels.config', dest: 'nextflow_labels.config'}\n\
|
||||
.runners[.type == 'nextflow'].config.script := 'includeConfig(\"nextflow_labels.config\"\
|
||||
)'\n"
|
||||
- ".engines += { type: \"native\" }"
|
||||
- ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'"
|
||||
- ".engines[.type == 'docker'].target_tag := 'v0.3.5'"
|
||||
- ".engines[.type == 'docker'].target_tag := 'v0.3.11'"
|
||||
keywords:
|
||||
- "bioinformatics"
|
||||
- "sequence"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// untar v0.3.5
|
||||
// untar v0.3.11
|
||||
//
|
||||
// This wrapper script is auto-generated by viash 0.9.0 and is thus a derivative
|
||||
// This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative
|
||||
// work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data
|
||||
// Intuitive.
|
||||
//
|
||||
@@ -82,64 +82,56 @@ def _checkArgumentType(String stage, Map par, Object value, String errorIdentifi
|
||||
foundClass = "List[${e.foundClass}]"
|
||||
}
|
||||
} else if (par.type == "string") {
|
||||
// cast to string if need be
|
||||
// cast to string if need be. only cast if the value is a GString
|
||||
if (value instanceof GString) {
|
||||
value = value.toString()
|
||||
value = value as String
|
||||
}
|
||||
expectedClass = value instanceof String ? null : "String"
|
||||
} else if (par.type == "integer") {
|
||||
// cast to integer if need be
|
||||
if (value instanceof String) {
|
||||
if (value !instanceof Integer) {
|
||||
try {
|
||||
value = value.toInteger()
|
||||
value = value as Integer
|
||||
} catch (NumberFormatException e) {
|
||||
// do nothing
|
||||
expectedClass = "Integer"
|
||||
}
|
||||
}
|
||||
if (value instanceof java.math.BigInteger) {
|
||||
value = value.intValue()
|
||||
}
|
||||
expectedClass = value instanceof Integer ? null : "Integer"
|
||||
} else if (par.type == "long") {
|
||||
// cast to long if need be
|
||||
if (value instanceof String) {
|
||||
if (value !instanceof Long) {
|
||||
try {
|
||||
value = value.toLong()
|
||||
value = value as Long
|
||||
} catch (NumberFormatException e) {
|
||||
// do nothing
|
||||
expectedClass = "Long"
|
||||
}
|
||||
}
|
||||
if (value instanceof Integer) {
|
||||
value = value.toLong()
|
||||
}
|
||||
expectedClass = value instanceof Long ? null : "Long"
|
||||
} else if (par.type == "double") {
|
||||
// cast to double if need be
|
||||
if (value instanceof String) {
|
||||
if (value !instanceof Double) {
|
||||
try {
|
||||
value = value.toDouble()
|
||||
value = value as Double
|
||||
} catch (NumberFormatException e) {
|
||||
// do nothing
|
||||
expectedClass = "Double"
|
||||
}
|
||||
}
|
||||
if (value instanceof java.math.BigDecimal) {
|
||||
value = value.doubleValue()
|
||||
} else if (par.type == "float") {
|
||||
// cast to float if need be
|
||||
if (value !instanceof Float) {
|
||||
try {
|
||||
value = value as Float
|
||||
} catch (NumberFormatException e) {
|
||||
expectedClass = "Float"
|
||||
}
|
||||
}
|
||||
if (value instanceof Float) {
|
||||
value = value.toDouble()
|
||||
}
|
||||
expectedClass = value instanceof Double ? null : "Double"
|
||||
} else if (par.type == "boolean" | par.type == "boolean_true" | par.type == "boolean_false") {
|
||||
// cast to boolean if need be
|
||||
if (value instanceof String) {
|
||||
def valueLower = value.toLowerCase()
|
||||
if (valueLower == "true") {
|
||||
value = true
|
||||
} else if (valueLower == "false") {
|
||||
value = false
|
||||
if (value !instanceof Boolean) {
|
||||
try {
|
||||
value = value as Boolean
|
||||
} catch (Exception e) {
|
||||
expectedClass = "Boolean"
|
||||
}
|
||||
}
|
||||
expectedClass = value instanceof Boolean ? null : "Boolean"
|
||||
} else if (par.type == "file" && (par.direction == "input" || stage == "output")) {
|
||||
// cast to path if need be
|
||||
if (value instanceof String) {
|
||||
@@ -151,10 +143,13 @@ def _checkArgumentType(String stage, Map par, Object value, String errorIdentifi
|
||||
expectedClass = value instanceof Path ? null : "Path"
|
||||
} else if (par.type == "file" && stage == "input" && par.direction == "output") {
|
||||
// cast to string if need be
|
||||
if (value instanceof GString) {
|
||||
value = value.toString()
|
||||
if (value !instanceof String) {
|
||||
try {
|
||||
value = value as String
|
||||
} catch (Exception e) {
|
||||
expectedClass = "String"
|
||||
}
|
||||
}
|
||||
expectedClass = value instanceof String ? null : "String"
|
||||
} else {
|
||||
// didn't find a match for par.type
|
||||
expectedClass = par.type
|
||||
@@ -173,7 +168,7 @@ def _checkArgumentType(String stage, Map par, Object value, String errorIdentifi
|
||||
Map _processInputValues(Map inputs, Map config, String id, String key) {
|
||||
if (!workflow.stubRun) {
|
||||
config.allArguments.each { arg ->
|
||||
if (arg.required) {
|
||||
if (arg.required && arg.direction == "input") {
|
||||
assert inputs.containsKey(arg.plainName) && inputs.get(arg.plainName) != null :
|
||||
"Error in module '${key}' id '${id}': required input argument '${arg.plainName}' is missing"
|
||||
}
|
||||
@@ -192,15 +187,8 @@ Map _processInputValues(Map inputs, Map config, String id, String key) {
|
||||
}
|
||||
|
||||
// helper file: 'src/main/resources/io/viash/runners/nextflow/arguments/_processOutputValues.nf'
|
||||
Map _processOutputValues(Map outputs, Map config, String id, String key) {
|
||||
Map _checkValidOutputArgument(Map outputs, Map config, String id, String key) {
|
||||
if (!workflow.stubRun) {
|
||||
config.allArguments.each { arg ->
|
||||
if (arg.direction == "output" && arg.required) {
|
||||
assert outputs.containsKey(arg.plainName) && outputs.get(arg.plainName) != null :
|
||||
"Error in module '${key}' id '${id}': required output argument '${arg.plainName}' is missing"
|
||||
}
|
||||
}
|
||||
|
||||
outputs = outputs.collectEntries { name, value ->
|
||||
def par = config.allArguments.find { it.plainName == name && it.direction == "output" }
|
||||
assert par != null : "Error in module '${key}' id '${id}': '${name}' is not a valid output argument"
|
||||
@@ -213,6 +201,16 @@ Map _processOutputValues(Map outputs, Map config, String id, String key) {
|
||||
return outputs
|
||||
}
|
||||
|
||||
void _checkAllRequiredOuputsPresent(Map outputs, Map config, String id, String key) {
|
||||
if (!workflow.stubRun) {
|
||||
config.allArguments.each { arg ->
|
||||
if (arg.direction == "output" && arg.required) {
|
||||
assert outputs.containsKey(arg.plainName) && outputs.get(arg.plainName) != null :
|
||||
"Error in module '${key}' id '${id}': required output argument '${arg.plainName}' is missing"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// helper file: 'src/main/resources/io/viash/runners/nextflow/channel/IDChecker.nf'
|
||||
class IDChecker {
|
||||
final def items = [] as Set
|
||||
@@ -1666,6 +1664,162 @@ def joinStates(Closure apply_) {
|
||||
}
|
||||
return joinStatesWf
|
||||
}
|
||||
// helper file: 'src/main/resources/io/viash/runners/nextflow/states/publishFiles.nf'
|
||||
def publishFiles(Map args) {
|
||||
def key_ = args.get("key")
|
||||
|
||||
assert key_ != null : "publishFiles: key must be specified"
|
||||
|
||||
workflow publishFilesWf {
|
||||
take: input_ch
|
||||
main:
|
||||
input_ch
|
||||
| map { tup ->
|
||||
def id_ = tup[0]
|
||||
def state_ = tup[1]
|
||||
|
||||
// the input files and the target output filenames
|
||||
def inputoutputFilenames_ = collectInputOutputPaths(state_, id_ + "." + key_).transpose()
|
||||
def inputFiles_ = inputoutputFilenames_[0]
|
||||
def outputFilenames_ = inputoutputFilenames_[1]
|
||||
|
||||
[id_, inputFiles_, outputFilenames_]
|
||||
}
|
||||
| publishFilesProc
|
||||
emit: input_ch
|
||||
}
|
||||
return publishFilesWf
|
||||
}
|
||||
|
||||
process publishFilesProc {
|
||||
// todo: check publishpath?
|
||||
publishDir path: "${getPublishDir()}/", mode: "copy"
|
||||
tag "$id"
|
||||
input:
|
||||
tuple val(id), path(inputFiles, stageAs: "_inputfile?/*"), val(outputFiles)
|
||||
output:
|
||||
tuple val(id), path{outputFiles}
|
||||
script:
|
||||
def copyCommands = [
|
||||
inputFiles instanceof List ? inputFiles : [inputFiles],
|
||||
outputFiles instanceof List ? outputFiles : [outputFiles]
|
||||
]
|
||||
.transpose()
|
||||
.collectMany{infile, outfile ->
|
||||
if (infile.toString() != outfile.toString()) {
|
||||
[
|
||||
"[ -d \"\$(dirname '${outfile.toString()}')\" ] || mkdir -p \"\$(dirname '${outfile.toString()}')\"",
|
||||
"cp -r '${infile.toString()}' '${outfile.toString()}'"
|
||||
]
|
||||
} else {
|
||||
// no need to copy if infile is the same as outfile
|
||||
[]
|
||||
}
|
||||
}
|
||||
"""
|
||||
echo "Copying output files to destination folder"
|
||||
${copyCommands.join("\n ")}
|
||||
"""
|
||||
}
|
||||
|
||||
|
||||
// this assumes that the state contains no other values other than those specified in the config
|
||||
def publishFilesByConfig(Map args) {
|
||||
def config = args.get("config")
|
||||
assert config != null : "publishFilesByConfig: config must be specified"
|
||||
|
||||
def key_ = args.get("key", config.name)
|
||||
assert key_ != null : "publishFilesByConfig: key must be specified"
|
||||
|
||||
workflow publishFilesSimpleWf {
|
||||
take: input_ch
|
||||
main:
|
||||
input_ch
|
||||
| map { tup ->
|
||||
def id_ = tup[0]
|
||||
def state_ = tup[1] // e.g. [output: new File("myoutput.h5ad"), k: 10]
|
||||
def origState_ = tup[2] // e.g. [output: '$id.$key.foo.h5ad']
|
||||
|
||||
|
||||
// the processed state is a list of [key, value, inputPath, outputFilename] tuples, where
|
||||
// - key is a String
|
||||
// - value is any object that can be serialized to a Yaml (so a String/Integer/Long/Double/Boolean, a List, a Map, or a Path)
|
||||
// - inputPath is a List[Path]
|
||||
// - outputFilename is a List[String]
|
||||
// - (inputPath, outputFilename) are the files that will be copied from src to dest (relative to the state.yaml)
|
||||
def processedState =
|
||||
config.allArguments
|
||||
.findAll { it.direction == "output" }
|
||||
.collectMany { par ->
|
||||
def plainName_ = par.plainName
|
||||
// if the state does not contain the key, it's an
|
||||
// optional argument for which the component did
|
||||
// not generate any output OR multiple channels were emitted
|
||||
// and the output was just not added to using the channel
|
||||
// that is now being parsed
|
||||
if (!state_.containsKey(plainName_)) {
|
||||
return []
|
||||
}
|
||||
def value = state_[plainName_]
|
||||
// if the parameter is not a file, it should be stored
|
||||
// in the state as-is, but is not something that needs
|
||||
// to be copied from the source path to the dest path
|
||||
if (par.type != "file") {
|
||||
return [[inputPath: [], outputFilename: []]]
|
||||
}
|
||||
// if the orig state does not contain this filename,
|
||||
// it's an optional argument for which the user specified
|
||||
// that it should not be returned as a state
|
||||
if (!origState_.containsKey(plainName_)) {
|
||||
return []
|
||||
}
|
||||
def filenameTemplate = origState_[plainName_]
|
||||
// if the pararameter is multiple: true, fetch the template
|
||||
if (par.multiple && filenameTemplate instanceof List) {
|
||||
filenameTemplate = filenameTemplate[0]
|
||||
}
|
||||
// instantiate the template
|
||||
def filename = filenameTemplate
|
||||
.replaceAll('\\$id', id_)
|
||||
.replaceAll('\\$\\{id\\}', id_)
|
||||
.replaceAll('\\$key', key_)
|
||||
.replaceAll('\\$\\{key\\}', key_)
|
||||
if (par.multiple) {
|
||||
// if the parameter is multiple: true, the filename
|
||||
// should contain a wildcard '*' that is replaced with
|
||||
// the index of the file
|
||||
assert filename.contains("*") : "Module '${key_}' id '${id_}': Multiple output files specified, but no wildcard '*' in the filename: ${filename}"
|
||||
def outputPerFile = value.withIndex().collect{ val, ix ->
|
||||
def filename_ix = filename.replace("*", ix.toString())
|
||||
def inputPath = val instanceof File ? val.toPath() : val
|
||||
[inputPath: inputPath, outputFilename: filename_ix]
|
||||
}
|
||||
def transposedOutputs = ["inputPath", "outputFilename"].collectEntries{ key ->
|
||||
[key, outputPerFile.collect{dic -> dic[key]}]
|
||||
}
|
||||
return [[key: plainName_] + transposedOutputs]
|
||||
} else {
|
||||
def value_ = java.nio.file.Paths.get(filename)
|
||||
def inputPath = value instanceof File ? value.toPath() : value
|
||||
return [[inputPath: [inputPath], outputFilename: [filename]]]
|
||||
}
|
||||
}
|
||||
|
||||
def inputPaths = processedState.collectMany{it.inputPath}
|
||||
def outputFilenames = processedState.collectMany{it.outputFilename}
|
||||
|
||||
|
||||
[id_, inputPaths, outputFilenames]
|
||||
}
|
||||
| publishFilesProc
|
||||
emit: input_ch
|
||||
}
|
||||
return publishFilesSimpleWf
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// helper file: 'src/main/resources/io/viash/runners/nextflow/states/publishStates.nf'
|
||||
def collectFiles(obj) {
|
||||
if (obj instanceof java.io.File || obj instanceof Path) {
|
||||
@@ -1723,8 +1877,6 @@ def publishStates(Map args) {
|
||||
|
||||
// the input files and the target output filenames
|
||||
def inputoutputFilenames_ = collectInputOutputPaths(state_, id_ + "." + key_).transpose()
|
||||
def inputFiles_ = inputoutputFilenames_[0]
|
||||
def outputFilenames_ = inputoutputFilenames_[1]
|
||||
|
||||
def yamlFilename = yamlTemplate_
|
||||
.replaceAll('\\$id', id_)
|
||||
@@ -1737,7 +1889,7 @@ def publishStates(Map args) {
|
||||
// convert state to yaml blob
|
||||
def yamlBlob_ = toRelativeTaggedYamlBlob([id: id_] + state_, java.nio.file.Paths.get(yamlFilename))
|
||||
|
||||
[id_, yamlBlob_, yamlFilename, inputFiles_, outputFilenames_]
|
||||
[id_, yamlBlob_, yamlFilename]
|
||||
}
|
||||
| publishStatesProc
|
||||
emit: input_ch
|
||||
@@ -1749,33 +1901,17 @@ process publishStatesProc {
|
||||
publishDir path: "${getPublishDir()}/", mode: "copy"
|
||||
tag "$id"
|
||||
input:
|
||||
tuple val(id), val(yamlBlob), val(yamlFile), path(inputFiles, stageAs: "_inputfile?/*"), val(outputFiles)
|
||||
tuple val(id), val(yamlBlob), val(yamlFile)
|
||||
output:
|
||||
tuple val(id), path{[yamlFile] + outputFiles}
|
||||
tuple val(id), path{[yamlFile]}
|
||||
script:
|
||||
def copyCommands = [
|
||||
inputFiles instanceof List ? inputFiles : [inputFiles],
|
||||
outputFiles instanceof List ? outputFiles : [outputFiles]
|
||||
]
|
||||
.transpose()
|
||||
.collectMany{infile, outfile ->
|
||||
if (infile.toString() != outfile.toString()) {
|
||||
[
|
||||
"[ -d \"\$(dirname '${outfile.toString()}')\" ] || mkdir -p \"\$(dirname '${outfile.toString()}')\"",
|
||||
"cp -r '${infile.toString()}' '${outfile.toString()}'"
|
||||
]
|
||||
} else {
|
||||
// no need to copy if infile is the same as outfile
|
||||
[]
|
||||
}
|
||||
}
|
||||
"""
|
||||
mkdir -p "\$(dirname '${yamlFile}')"
|
||||
echo "Storing state as yaml"
|
||||
echo '${yamlBlob}' > '${yamlFile}'
|
||||
echo "Copying output files to destination folder"
|
||||
${copyCommands.join("\n ")}
|
||||
"""
|
||||
mkdir -p "\$(dirname '${yamlFile}')"
|
||||
echo "Storing state as yaml"
|
||||
cat > '${yamlFile}' << HERE
|
||||
${yamlBlob}
|
||||
HERE
|
||||
"""
|
||||
}
|
||||
|
||||
|
||||
@@ -1806,13 +1942,10 @@ def publishStatesByConfig(Map args) {
|
||||
.replaceAll('\\$\\{key\\}', key_)
|
||||
def yamlDir = java.nio.file.Paths.get(yamlFilename).getParent()
|
||||
|
||||
// the processed state is a list of [key, value, inputPath, outputFilename] tuples, where
|
||||
// the processed state is a list of [key, value] tuples, where
|
||||
// - key is a String
|
||||
// - value is any object that can be serialized to a Yaml (so a String/Integer/Long/Double/Boolean, a List, a Map, or a Path)
|
||||
// - inputPath is a List[Path]
|
||||
// - outputFilename is a List[String]
|
||||
// - (key, value) are the tuples that will be saved to the state.yaml file
|
||||
// - (inputPath, outputFilename) are the files that will be copied from src to dest (relative to the state.yaml)
|
||||
def processedState =
|
||||
config.allArguments
|
||||
.findAll { it.direction == "output" }
|
||||
@@ -1829,7 +1962,7 @@ def publishStatesByConfig(Map args) {
|
||||
// in the state as-is, but is not something that needs
|
||||
// to be copied from the source path to the dest path
|
||||
if (par.type != "file") {
|
||||
return [[key: plainName_, value: value, inputPath: [], outputFilename: []]]
|
||||
return [[key: plainName_, value: value]]
|
||||
}
|
||||
// if the orig state does not contain this filename,
|
||||
// it's an optional argument for which the user specified
|
||||
@@ -1860,13 +1993,9 @@ def publishStatesByConfig(Map args) {
|
||||
if (yamlDir != null) {
|
||||
value_ = yamlDir.relativize(value_)
|
||||
}
|
||||
def inputPath = val instanceof File ? val.toPath() : val
|
||||
[value: value_, inputPath: inputPath, outputFilename: filename_ix]
|
||||
return value_
|
||||
}
|
||||
def transposedOutputs = ["value", "inputPath", "outputFilename"].collectEntries{ key ->
|
||||
[key, outputPerFile.collect{dic -> dic[key]}]
|
||||
}
|
||||
return [[key: plainName_] + transposedOutputs]
|
||||
return [["key": plainName_, "value": outputPerFile]]
|
||||
} else {
|
||||
def value_ = java.nio.file.Paths.get(filename)
|
||||
// if id contains a slash
|
||||
@@ -1874,18 +2003,17 @@ def publishStatesByConfig(Map args) {
|
||||
value_ = yamlDir.relativize(value_)
|
||||
}
|
||||
def inputPath = value instanceof File ? value.toPath() : value
|
||||
return [[key: plainName_, value: value_, inputPath: [inputPath], outputFilename: [filename]]]
|
||||
return [["key": plainName_, value: value_]]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def updatedState_ = processedState.collectEntries{[it.key, it.value]}
|
||||
def inputPaths = processedState.collectMany{it.inputPath}
|
||||
def outputFilenames = processedState.collectMany{it.outputFilename}
|
||||
|
||||
// convert state to yaml blob
|
||||
def yamlBlob_ = toTaggedYamlBlob([id: id_] + updatedState_)
|
||||
|
||||
[id_, yamlBlob_, yamlFilename, inputPaths, outputFilenames]
|
||||
[id_, yamlBlob_, yamlFilename]
|
||||
}
|
||||
| publishStatesProc
|
||||
emit: input_ch
|
||||
@@ -2559,7 +2687,8 @@ def _debug(workflowArgs, debugKey) {
|
||||
def workflowFactory(Map args, Map defaultWfArgs, Map meta) {
|
||||
def workflowArgs = processWorkflowArgs(args, defaultWfArgs, meta)
|
||||
def key_ = workflowArgs["key"]
|
||||
|
||||
def multipleArgs = meta.config.allArguments.findAll{ it.multiple }.collect{it.plainName}
|
||||
|
||||
workflow workflowInstance {
|
||||
take: input_
|
||||
|
||||
@@ -2716,12 +2845,36 @@ def workflowFactory(Map args, Map defaultWfArgs, Map meta) {
|
||||
}
|
||||
|
||||
// TODO: move some of the _meta.join_id wrangling to the safeJoin() function.
|
||||
def chInitialOutput = chArgsWithDefaults
|
||||
def chInitialOutputMulti = chArgsWithDefaults
|
||||
| _debug(workflowArgs, "processed")
|
||||
// run workflow
|
||||
| innerWorkflowFactory(workflowArgs)
|
||||
// check output tuple
|
||||
| map { id_, output_ ->
|
||||
def chInitialOutputList = chInitialOutputMulti instanceof List ? chInitialOutputMulti : [chInitialOutputMulti]
|
||||
assert chInitialOutputList.size() > 0: "should have emitted at least one output channel"
|
||||
// Add a channel ID to the events, which designates the channel the event was emitted from as a running number
|
||||
// This number is used to sort the events later when the events are gathered from across the channels.
|
||||
def chInitialOutputListWithIndexedEvents = chInitialOutputList.withIndex().collect{channel, channelIndex ->
|
||||
def newChannel = channel
|
||||
| map {tuple ->
|
||||
assert tuple instanceof List :
|
||||
"Error in module '${key_}': element in output channel should be a tuple [id, data, ...otherargs...]\n" +
|
||||
" Example: [\"id\", [input: file('foo.txt'), arg: 10]].\n" +
|
||||
" Expected class: List. Found: tuple.getClass() is ${tuple.getClass()}"
|
||||
|
||||
def newEvent = [channelIndex] + tuple
|
||||
return newEvent
|
||||
}
|
||||
return newChannel
|
||||
}
|
||||
// Put the events into 1 channel, cover case where there is only one channel is emitted
|
||||
def chInitialOutput = chInitialOutputList.size() > 1 ? \
|
||||
chInitialOutputListWithIndexedEvents[0].mix(*chInitialOutputListWithIndexedEvents.tail()) : \
|
||||
chInitialOutputListWithIndexedEvents[0]
|
||||
def chInitialOutputProcessed = chInitialOutput
|
||||
| map { tuple ->
|
||||
def channelId = tuple[0]
|
||||
def id_ = tuple[1]
|
||||
def output_ = tuple[2]
|
||||
|
||||
// see if output map contains metadata
|
||||
def meta_ =
|
||||
@@ -2734,19 +2887,94 @@ def workflowFactory(Map args, Map defaultWfArgs, Map meta) {
|
||||
output_ = output_.findAll{k, v -> k != "_meta"}
|
||||
|
||||
// check value types
|
||||
output_ = _processOutputValues(output_, meta.config, id_, key_)
|
||||
output_ = _checkValidOutputArgument(output_, meta.config, id_, key_)
|
||||
|
||||
// simplify output if need be
|
||||
if (workflowArgs.auto.simplifyOutput && output_.size() == 1) {
|
||||
output_ = output_.values()[0]
|
||||
}
|
||||
|
||||
[join_id, id_, output_]
|
||||
[join_id, channelId, id_, output_]
|
||||
}
|
||||
// | view{"chInitialOutput: ${it.take(3)}"}
|
||||
|
||||
// join the output [prev_id, channel_id, new_id, output] with the previous state [prev_id, state, ...]
|
||||
def chPublishWithPreviousState = safeJoin(chInitialOutputProcessed, chRunFiltered, key_)
|
||||
// input tuple format: [join_id, channel_id, id, output, prev_state, ...]
|
||||
// output tuple format: [join_id, channel_id, id, new_state, ...]
|
||||
| map{ tup ->
|
||||
def new_state = workflowArgs.toState(tup.drop(2).take(3))
|
||||
tup.take(3) + [new_state] + tup.drop(5)
|
||||
}
|
||||
if (workflowArgs.auto.publish == "state") {
|
||||
def chPublishFiles = chPublishWithPreviousState
|
||||
// input tuple format: [join_id, channel_id, id, new_state, ...]
|
||||
// output tuple format: [join_id, channel_id, id, new_state]
|
||||
| map{ tup ->
|
||||
tup.take(4)
|
||||
}
|
||||
|
||||
safeJoin(chPublishFiles, chArgsWithDefaults, key_)
|
||||
// input tuple format: [join_id, channel_id, id, new_state, orig_state, ...]
|
||||
// output tuple format: [id, new_state, orig_state]
|
||||
| map { tup ->
|
||||
tup.drop(2).take(3)
|
||||
}
|
||||
| publishFilesByConfig(key: key_, config: meta.config)
|
||||
}
|
||||
// Join the state from the events that were emitted from different channels
|
||||
def chJoined = chInitialOutputProcessed
|
||||
| map {tuple ->
|
||||
def join_id = tuple[0]
|
||||
def channel_id = tuple[1]
|
||||
def id = tuple[2]
|
||||
def other = tuple.drop(3)
|
||||
// Below, groupTuple is used to join the events. To make sure resuming a workflow
|
||||
// keeps working, the output state must be deterministic. This means the state needs to be
|
||||
// sorted with groupTuple's has a 'sort' argument. This argument can be set to 'hash',
|
||||
// but hashing the state when it is large can be problematic in terms of performance.
|
||||
// Therefore, a custom comparator function is provided. We add the channel ID to the
|
||||
// states so that we can use the channel ID to sort the items.
|
||||
def stateWithChannelID = [[channel_id] * other.size(), other].transpose()
|
||||
// A comparator that is provided to groupTuple's 'sort' argument is applied
|
||||
// to all elements of the event tuple (that is not the 'id'). The comparator
|
||||
// closure that is used below expects the input to be List. So the join_id and
|
||||
// channel_id must also be wrapped in a list.
|
||||
[[join_id], [channel_id], id] + stateWithChannelID
|
||||
}
|
||||
| groupTuple(by: 2, sort: {a, b -> a[0] <=> b[0]}, size: chInitialOutputList.size(), remainder: true)
|
||||
| map {join_ids, _, id, statesWithChannelID ->
|
||||
// Remove the channel IDs from the states
|
||||
def states = statesWithChannelID.collect{it[1]}
|
||||
def newJoinId = join_ids.flatten().unique{a, b -> a <=> b}
|
||||
assert newJoinId.size() == 1: "Multiple events were emitted for '$id'."
|
||||
def newJoinIdUnique = newJoinId[0]
|
||||
|
||||
// Merge the states from the different channels
|
||||
def newState = states.inject([:]){ old_state, state_to_add ->
|
||||
return old_state + state_to_add.collectEntries{k, v ->
|
||||
if (!multipleArgs.contains(k)) {
|
||||
// if the key is not a multiple argument, we expect only one value
|
||||
if (old_state.containsKey(k)) {
|
||||
assert old_state[k] == v : "ID $id: multiple entries for argument $k were emitted."
|
||||
}
|
||||
[k, v]
|
||||
} else {
|
||||
// if the key is a multiple argument, append the different values into one list
|
||||
def prevValue = old_state.getOrDefault(k, [])
|
||||
def prevValueAsList = prevValue instanceof List ? prevValue : [prevValue]
|
||||
[k, prevValueAsList + v]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_checkAllRequiredOuputsPresent(newState, meta.config, id, key_)
|
||||
|
||||
// simplify output if need be
|
||||
if (workflowArgs.auto.simplifyOutput && newState.size() == 1) {
|
||||
newState = newState.values()[0]
|
||||
}
|
||||
|
||||
return [newJoinIdUnique, id, newState]
|
||||
}
|
||||
|
||||
// join the output [prev_id, new_id, output] with the previous state [prev_id, state, ...]
|
||||
def chNewState = safeJoin(chInitialOutput, chRunFiltered, key_)
|
||||
def chNewState = safeJoin(chJoined, chRunFiltered, key_)
|
||||
// input tuple format: [join_id, id, output, prev_state, ...]
|
||||
// output tuple format: [join_id, id, new_state, ...]
|
||||
| map{ tup ->
|
||||
@@ -2755,23 +2983,21 @@ def workflowFactory(Map args, Map defaultWfArgs, Map meta) {
|
||||
}
|
||||
|
||||
if (workflowArgs.auto.publish == "state") {
|
||||
def chPublish = chNewState
|
||||
def chPublishStates = chNewState
|
||||
// input tuple format: [join_id, id, new_state, ...]
|
||||
// output tuple format: [join_id, id, new_state]
|
||||
| map{ tup ->
|
||||
tup.take(3)
|
||||
}
|
||||
|
||||
safeJoin(chPublish, chArgsWithDefaults, key_)
|
||||
safeJoin(chPublishStates, chArgsWithDefaults, key_)
|
||||
// input tuple format: [join_id, id, new_state, orig_state, ...]
|
||||
// output tuple format: [id, new_state, orig_state]
|
||||
| map { tup ->
|
||||
tup.drop(1).take(3)
|
||||
}
|
||||
}
|
||||
| publishStatesByConfig(key: key_, config: meta.config)
|
||||
}
|
||||
|
||||
// remove join_id and meta
|
||||
chReturn = chNewState
|
||||
| map { tup ->
|
||||
// input tuple format: [join_id, id, new_state, ...]
|
||||
@@ -2806,7 +3032,7 @@ meta = [
|
||||
"config": processConfig(readJsonBlob('''{
|
||||
"name" : "untar",
|
||||
"namespace" : "io",
|
||||
"version" : "v0.3.5",
|
||||
"version" : "v0.3.11",
|
||||
"argument_groups" : [
|
||||
{
|
||||
"name" : "Input arguments",
|
||||
@@ -2882,6 +3108,10 @@ meta = [
|
||||
}
|
||||
],
|
||||
"status" : "enabled",
|
||||
"scope" : {
|
||||
"image" : "public",
|
||||
"target" : "public"
|
||||
},
|
||||
"requirements" : {
|
||||
"commands" : [
|
||||
"ps"
|
||||
@@ -2974,7 +3204,7 @@ meta = [
|
||||
"id" : "docker",
|
||||
"image" : "debian:stable-slim",
|
||||
"target_registry" : "images.viash-hub.com",
|
||||
"target_tag" : "v0.3.5",
|
||||
"target_tag" : "v0.3.11",
|
||||
"namespace_separator" : "/",
|
||||
"setup" : [
|
||||
{
|
||||
@@ -2996,31 +3226,31 @@ meta = [
|
||||
"runner" : "nextflow",
|
||||
"engine" : "docker|native",
|
||||
"output" : "target/nextflow/io/untar",
|
||||
"viash_version" : "0.9.0",
|
||||
"git_commit" : "18e092d169cc2704a2e4e705485d9231911d0484",
|
||||
"viash_version" : "0.9.4",
|
||||
"git_commit" : "cd2cfac18e14622e1a5d7a0d489f64c30e83dab2",
|
||||
"git_remote" : "https://github.com/viash-hub/demultiplex",
|
||||
"git_tag" : "v0.3.4-6-g18e092d"
|
||||
"git_tag" : "v0.3.10-5-gcd2cfac"
|
||||
},
|
||||
"package_config" : {
|
||||
"name" : "demultiplex",
|
||||
"version" : "v0.3.5",
|
||||
"version" : "v0.3.11",
|
||||
"description" : "Demultiplexing pipeline\n",
|
||||
"info" : {
|
||||
"test_resources" : [
|
||||
{
|
||||
"path" : "gs://viash-hub-test-data/demultiplex/v2/",
|
||||
"path" : "gs://viash-hub-resources/demultiplex/v3",
|
||||
"dest" : "testData"
|
||||
}
|
||||
]
|
||||
},
|
||||
"viash_version" : "0.9.0",
|
||||
"viash_version" : "0.9.4",
|
||||
"source" : "src",
|
||||
"target" : "target",
|
||||
"config_mods" : [
|
||||
".requirements.commands := ['ps']\n.runners[.type == 'nextflow'].directives.tag := '$id'\n.resources += {path: '/src/config/labels.config', dest: 'nextflow_labels.config'}\n.runners[.type == 'nextflow'].config.script := 'includeConfig(\\"nextflow_labels.config\\")'\n",
|
||||
".requirements.commands += ['ps']\n.runners[.type == 'nextflow'].directives.tag := '$id'\n.resources += {path: '/src/config/labels.config', dest: 'nextflow_labels.config'}\n.runners[.type == 'nextflow'].config.script := 'includeConfig(\\"nextflow_labels.config\\")'\n",
|
||||
".engines += { type: \\"native\\" }",
|
||||
".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'",
|
||||
".engines[.type == 'docker'].target_tag := 'v0.3.5'"
|
||||
".engines[.type == 'docker'].target_tag := 'v0.3.11'"
|
||||
],
|
||||
"keywords" : [
|
||||
"bioinformatics",
|
||||
@@ -3446,7 +3676,7 @@ def _vdsl3ProcessFactory(Map workflowArgs, Map meta, String rawScript) {
|
||||
// create process from temp file
|
||||
def binding = new nextflow.script.ScriptBinding([:])
|
||||
def session = nextflow.Nextflow.getSession()
|
||||
def parser = new nextflow.script.ScriptParser(session)
|
||||
def parser = _getScriptLoader(session)
|
||||
.setModule(true)
|
||||
.setBinding(binding)
|
||||
def moduleScript = parser.runScript(tempFile)
|
||||
@@ -3460,6 +3690,27 @@ def _vdsl3ProcessFactory(Map workflowArgs, Map meta, String rawScript) {
|
||||
return scriptMeta.getProcess(procKey)
|
||||
}
|
||||
|
||||
// use Reflection to get a ScriptParser / ScriptLoader
|
||||
// <25.02.0-edge: new nextflow.script.ScriptParser(session)
|
||||
// >=25.02.0-edge: nextflow.script.ScriptLoaderFactory.create(session)
|
||||
def _getScriptLoader(nextflow.Session session) {
|
||||
// try using the old method
|
||||
try {
|
||||
Class<?> scriptParserClass = Class.forName('nextflow.script.ScriptParser')
|
||||
return scriptParserClass.getDeclaredConstructor(nextflow.Session).newInstance(session)
|
||||
} catch (ClassNotFoundException e) {
|
||||
// else try with the new method
|
||||
try {
|
||||
Class<?> scriptLoaderFactoryClass = Class.forName('nextflow.script.ScriptLoaderFactory')
|
||||
def createMethod = scriptLoaderFactoryClass.getDeclaredMethod('create', nextflow.Session)
|
||||
return createMethod.invoke(null, session) // null because create is static
|
||||
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | java.lang.reflect.InvocationTargetException e2) {
|
||||
// Handle the case where neither class is found
|
||||
throw new Exception("Neither nextflow.script.ScriptParser nor nextflow.script.ScriptLoaderFactory could be found. Is this a compatible Nextflow version?", e2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// defaults
|
||||
meta["defaults"] = [
|
||||
// key to be used to trace the process and determine output names
|
||||
@@ -3473,7 +3724,7 @@ meta["defaults"] = [
|
||||
"container" : {
|
||||
"registry" : "images.viash-hub.com",
|
||||
"image" : "vsh/demultiplex/io/untar",
|
||||
"tag" : "v0.3.5"
|
||||
"tag" : "v0.3.11"
|
||||
},
|
||||
"tag" : "$id"
|
||||
}'''),
|
||||
|
||||
@@ -2,7 +2,7 @@ manifest {
|
||||
name = 'io/untar'
|
||||
mainScript = 'main.nf'
|
||||
nextflowVersion = '!>=20.12.1-edge'
|
||||
version = 'v0.3.5'
|
||||
version = 'v0.3.11'
|
||||
description = 'Unpack a .tar file. When the contents of the .tar file is just a single directory,\nput the contents of the directory into the output folder instead of that directory.\n'
|
||||
}
|
||||
|
||||
|
||||
@@ -37,10 +37,10 @@
|
||||
"output": {
|
||||
"type":
|
||||
"string",
|
||||
"description": "Type: `file`, required, default: `$id.$key.output.output`. Directory to write the contents of the ",
|
||||
"help_text": "Type: `file`, required, default: `$id.$key.output.output`. Directory to write the contents of the .tar file to."
|
||||
"description": "Type: `file`, required, default: `$id.$key.output`. Directory to write the contents of the ",
|
||||
"help_text": "Type: `file`, required, default: `$id.$key.output`. Directory to write the contents of the .tar file to."
|
||||
,
|
||||
"default":"$id.$key.output.output"
|
||||
"default":"$id.$key.output"
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: "runner"
|
||||
version: "v0.3.5"
|
||||
version: "v0.3.11"
|
||||
argument_groups:
|
||||
- name: "Input arguments"
|
||||
arguments:
|
||||
@@ -84,6 +84,17 @@ argument_groups:
|
||||
direction: "output"
|
||||
multiple: false
|
||||
multiple_sep: ";"
|
||||
- type: "file"
|
||||
name: "--demultiplexer_logs"
|
||||
info: null
|
||||
default:
|
||||
- "demultiplexer_logs"
|
||||
must_exist: true
|
||||
create_parent: true
|
||||
required: false
|
||||
direction: "output"
|
||||
multiple: false
|
||||
multiple_sep: ";"
|
||||
- name: "Other arguments"
|
||||
arguments:
|
||||
- type: "boolean_true"
|
||||
@@ -103,6 +114,9 @@ resources:
|
||||
description: "Runner for demultiplexing of raw sequencing data"
|
||||
info: null
|
||||
status: "enabled"
|
||||
scope:
|
||||
image: "public"
|
||||
target: "public"
|
||||
requirements:
|
||||
commands:
|
||||
- "ps"
|
||||
@@ -191,32 +205,32 @@ build_info:
|
||||
engine: "native|native"
|
||||
output: "target/nextflow/runner"
|
||||
executable: "target/nextflow/runner/main.nf"
|
||||
viash_version: "0.9.0"
|
||||
git_commit: "18e092d169cc2704a2e4e705485d9231911d0484"
|
||||
viash_version: "0.9.4"
|
||||
git_commit: "cd2cfac18e14622e1a5d7a0d489f64c30e83dab2"
|
||||
git_remote: "https://github.com/viash-hub/demultiplex"
|
||||
git_tag: "v0.3.4-6-g18e092d"
|
||||
git_tag: "v0.3.10-5-gcd2cfac"
|
||||
dependencies:
|
||||
- "target/nextflow/demultiplex"
|
||||
- "target/nextflow/io/publish"
|
||||
package_config:
|
||||
name: "demultiplex"
|
||||
version: "v0.3.5"
|
||||
version: "v0.3.11"
|
||||
description: "Demultiplexing pipeline\n"
|
||||
info:
|
||||
test_resources:
|
||||
- path: "gs://viash-hub-test-data/demultiplex/v2/"
|
||||
- path: "gs://viash-hub-resources/demultiplex/v3"
|
||||
dest: "testData"
|
||||
viash_version: "0.9.0"
|
||||
viash_version: "0.9.4"
|
||||
source: "src"
|
||||
target: "target"
|
||||
config_mods:
|
||||
- ".requirements.commands := ['ps']\n.runners[.type == 'nextflow'].directives.tag\
|
||||
- ".requirements.commands += ['ps']\n.runners[.type == 'nextflow'].directives.tag\
|
||||
\ := '$id'\n.resources += {path: '/src/config/labels.config', dest: 'nextflow_labels.config'}\n\
|
||||
.runners[.type == 'nextflow'].config.script := 'includeConfig(\"nextflow_labels.config\"\
|
||||
)'\n"
|
||||
- ".engines += { type: \"native\" }"
|
||||
- ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'"
|
||||
- ".engines[.type == 'docker'].target_tag := 'v0.3.5'"
|
||||
- ".engines[.type == 'docker'].target_tag := 'v0.3.11'"
|
||||
keywords:
|
||||
- "bioinformatics"
|
||||
- "sequence"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// runner v0.3.5
|
||||
// runner v0.3.11
|
||||
//
|
||||
// This wrapper script is auto-generated by viash 0.9.0 and is thus a derivative
|
||||
// This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative
|
||||
// work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data
|
||||
// Intuitive.
|
||||
//
|
||||
@@ -82,64 +82,56 @@ def _checkArgumentType(String stage, Map par, Object value, String errorIdentifi
|
||||
foundClass = "List[${e.foundClass}]"
|
||||
}
|
||||
} else if (par.type == "string") {
|
||||
// cast to string if need be
|
||||
// cast to string if need be. only cast if the value is a GString
|
||||
if (value instanceof GString) {
|
||||
value = value.toString()
|
||||
value = value as String
|
||||
}
|
||||
expectedClass = value instanceof String ? null : "String"
|
||||
} else if (par.type == "integer") {
|
||||
// cast to integer if need be
|
||||
if (value instanceof String) {
|
||||
if (value !instanceof Integer) {
|
||||
try {
|
||||
value = value.toInteger()
|
||||
value = value as Integer
|
||||
} catch (NumberFormatException e) {
|
||||
// do nothing
|
||||
expectedClass = "Integer"
|
||||
}
|
||||
}
|
||||
if (value instanceof java.math.BigInteger) {
|
||||
value = value.intValue()
|
||||
}
|
||||
expectedClass = value instanceof Integer ? null : "Integer"
|
||||
} else if (par.type == "long") {
|
||||
// cast to long if need be
|
||||
if (value instanceof String) {
|
||||
if (value !instanceof Long) {
|
||||
try {
|
||||
value = value.toLong()
|
||||
value = value as Long
|
||||
} catch (NumberFormatException e) {
|
||||
// do nothing
|
||||
expectedClass = "Long"
|
||||
}
|
||||
}
|
||||
if (value instanceof Integer) {
|
||||
value = value.toLong()
|
||||
}
|
||||
expectedClass = value instanceof Long ? null : "Long"
|
||||
} else if (par.type == "double") {
|
||||
// cast to double if need be
|
||||
if (value instanceof String) {
|
||||
if (value !instanceof Double) {
|
||||
try {
|
||||
value = value.toDouble()
|
||||
value = value as Double
|
||||
} catch (NumberFormatException e) {
|
||||
// do nothing
|
||||
expectedClass = "Double"
|
||||
}
|
||||
}
|
||||
if (value instanceof java.math.BigDecimal) {
|
||||
value = value.doubleValue()
|
||||
} else if (par.type == "float") {
|
||||
// cast to float if need be
|
||||
if (value !instanceof Float) {
|
||||
try {
|
||||
value = value as Float
|
||||
} catch (NumberFormatException e) {
|
||||
expectedClass = "Float"
|
||||
}
|
||||
}
|
||||
if (value instanceof Float) {
|
||||
value = value.toDouble()
|
||||
}
|
||||
expectedClass = value instanceof Double ? null : "Double"
|
||||
} else if (par.type == "boolean" | par.type == "boolean_true" | par.type == "boolean_false") {
|
||||
// cast to boolean if need be
|
||||
if (value instanceof String) {
|
||||
def valueLower = value.toLowerCase()
|
||||
if (valueLower == "true") {
|
||||
value = true
|
||||
} else if (valueLower == "false") {
|
||||
value = false
|
||||
if (value !instanceof Boolean) {
|
||||
try {
|
||||
value = value as Boolean
|
||||
} catch (Exception e) {
|
||||
expectedClass = "Boolean"
|
||||
}
|
||||
}
|
||||
expectedClass = value instanceof Boolean ? null : "Boolean"
|
||||
} else if (par.type == "file" && (par.direction == "input" || stage == "output")) {
|
||||
// cast to path if need be
|
||||
if (value instanceof String) {
|
||||
@@ -151,10 +143,13 @@ def _checkArgumentType(String stage, Map par, Object value, String errorIdentifi
|
||||
expectedClass = value instanceof Path ? null : "Path"
|
||||
} else if (par.type == "file" && stage == "input" && par.direction == "output") {
|
||||
// cast to string if need be
|
||||
if (value instanceof GString) {
|
||||
value = value.toString()
|
||||
if (value !instanceof String) {
|
||||
try {
|
||||
value = value as String
|
||||
} catch (Exception e) {
|
||||
expectedClass = "String"
|
||||
}
|
||||
}
|
||||
expectedClass = value instanceof String ? null : "String"
|
||||
} else {
|
||||
// didn't find a match for par.type
|
||||
expectedClass = par.type
|
||||
@@ -173,7 +168,7 @@ def _checkArgumentType(String stage, Map par, Object value, String errorIdentifi
|
||||
Map _processInputValues(Map inputs, Map config, String id, String key) {
|
||||
if (!workflow.stubRun) {
|
||||
config.allArguments.each { arg ->
|
||||
if (arg.required) {
|
||||
if (arg.required && arg.direction == "input") {
|
||||
assert inputs.containsKey(arg.plainName) && inputs.get(arg.plainName) != null :
|
||||
"Error in module '${key}' id '${id}': required input argument '${arg.plainName}' is missing"
|
||||
}
|
||||
@@ -192,15 +187,8 @@ Map _processInputValues(Map inputs, Map config, String id, String key) {
|
||||
}
|
||||
|
||||
// helper file: 'src/main/resources/io/viash/runners/nextflow/arguments/_processOutputValues.nf'
|
||||
Map _processOutputValues(Map outputs, Map config, String id, String key) {
|
||||
Map _checkValidOutputArgument(Map outputs, Map config, String id, String key) {
|
||||
if (!workflow.stubRun) {
|
||||
config.allArguments.each { arg ->
|
||||
if (arg.direction == "output" && arg.required) {
|
||||
assert outputs.containsKey(arg.plainName) && outputs.get(arg.plainName) != null :
|
||||
"Error in module '${key}' id '${id}': required output argument '${arg.plainName}' is missing"
|
||||
}
|
||||
}
|
||||
|
||||
outputs = outputs.collectEntries { name, value ->
|
||||
def par = config.allArguments.find { it.plainName == name && it.direction == "output" }
|
||||
assert par != null : "Error in module '${key}' id '${id}': '${name}' is not a valid output argument"
|
||||
@@ -213,6 +201,16 @@ Map _processOutputValues(Map outputs, Map config, String id, String key) {
|
||||
return outputs
|
||||
}
|
||||
|
||||
void _checkAllRequiredOuputsPresent(Map outputs, Map config, String id, String key) {
|
||||
if (!workflow.stubRun) {
|
||||
config.allArguments.each { arg ->
|
||||
if (arg.direction == "output" && arg.required) {
|
||||
assert outputs.containsKey(arg.plainName) && outputs.get(arg.plainName) != null :
|
||||
"Error in module '${key}' id '${id}': required output argument '${arg.plainName}' is missing"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// helper file: 'src/main/resources/io/viash/runners/nextflow/channel/IDChecker.nf'
|
||||
class IDChecker {
|
||||
final def items = [] as Set
|
||||
@@ -1666,6 +1664,162 @@ def joinStates(Closure apply_) {
|
||||
}
|
||||
return joinStatesWf
|
||||
}
|
||||
// helper file: 'src/main/resources/io/viash/runners/nextflow/states/publishFiles.nf'
|
||||
def publishFiles(Map args) {
|
||||
def key_ = args.get("key")
|
||||
|
||||
assert key_ != null : "publishFiles: key must be specified"
|
||||
|
||||
workflow publishFilesWf {
|
||||
take: input_ch
|
||||
main:
|
||||
input_ch
|
||||
| map { tup ->
|
||||
def id_ = tup[0]
|
||||
def state_ = tup[1]
|
||||
|
||||
// the input files and the target output filenames
|
||||
def inputoutputFilenames_ = collectInputOutputPaths(state_, id_ + "." + key_).transpose()
|
||||
def inputFiles_ = inputoutputFilenames_[0]
|
||||
def outputFilenames_ = inputoutputFilenames_[1]
|
||||
|
||||
[id_, inputFiles_, outputFilenames_]
|
||||
}
|
||||
| publishFilesProc
|
||||
emit: input_ch
|
||||
}
|
||||
return publishFilesWf
|
||||
}
|
||||
|
||||
process publishFilesProc {
|
||||
// todo: check publishpath?
|
||||
publishDir path: "${getPublishDir()}/", mode: "copy"
|
||||
tag "$id"
|
||||
input:
|
||||
tuple val(id), path(inputFiles, stageAs: "_inputfile?/*"), val(outputFiles)
|
||||
output:
|
||||
tuple val(id), path{outputFiles}
|
||||
script:
|
||||
def copyCommands = [
|
||||
inputFiles instanceof List ? inputFiles : [inputFiles],
|
||||
outputFiles instanceof List ? outputFiles : [outputFiles]
|
||||
]
|
||||
.transpose()
|
||||
.collectMany{infile, outfile ->
|
||||
if (infile.toString() != outfile.toString()) {
|
||||
[
|
||||
"[ -d \"\$(dirname '${outfile.toString()}')\" ] || mkdir -p \"\$(dirname '${outfile.toString()}')\"",
|
||||
"cp -r '${infile.toString()}' '${outfile.toString()}'"
|
||||
]
|
||||
} else {
|
||||
// no need to copy if infile is the same as outfile
|
||||
[]
|
||||
}
|
||||
}
|
||||
"""
|
||||
echo "Copying output files to destination folder"
|
||||
${copyCommands.join("\n ")}
|
||||
"""
|
||||
}
|
||||
|
||||
|
||||
// this assumes that the state contains no other values other than those specified in the config
|
||||
def publishFilesByConfig(Map args) {
|
||||
def config = args.get("config")
|
||||
assert config != null : "publishFilesByConfig: config must be specified"
|
||||
|
||||
def key_ = args.get("key", config.name)
|
||||
assert key_ != null : "publishFilesByConfig: key must be specified"
|
||||
|
||||
workflow publishFilesSimpleWf {
|
||||
take: input_ch
|
||||
main:
|
||||
input_ch
|
||||
| map { tup ->
|
||||
def id_ = tup[0]
|
||||
def state_ = tup[1] // e.g. [output: new File("myoutput.h5ad"), k: 10]
|
||||
def origState_ = tup[2] // e.g. [output: '$id.$key.foo.h5ad']
|
||||
|
||||
|
||||
// the processed state is a list of [key, value, inputPath, outputFilename] tuples, where
|
||||
// - key is a String
|
||||
// - value is any object that can be serialized to a Yaml (so a String/Integer/Long/Double/Boolean, a List, a Map, or a Path)
|
||||
// - inputPath is a List[Path]
|
||||
// - outputFilename is a List[String]
|
||||
// - (inputPath, outputFilename) are the files that will be copied from src to dest (relative to the state.yaml)
|
||||
def processedState =
|
||||
config.allArguments
|
||||
.findAll { it.direction == "output" }
|
||||
.collectMany { par ->
|
||||
def plainName_ = par.plainName
|
||||
// if the state does not contain the key, it's an
|
||||
// optional argument for which the component did
|
||||
// not generate any output OR multiple channels were emitted
|
||||
// and the output was just not added to using the channel
|
||||
// that is now being parsed
|
||||
if (!state_.containsKey(plainName_)) {
|
||||
return []
|
||||
}
|
||||
def value = state_[plainName_]
|
||||
// if the parameter is not a file, it should be stored
|
||||
// in the state as-is, but is not something that needs
|
||||
// to be copied from the source path to the dest path
|
||||
if (par.type != "file") {
|
||||
return [[inputPath: [], outputFilename: []]]
|
||||
}
|
||||
// if the orig state does not contain this filename,
|
||||
// it's an optional argument for which the user specified
|
||||
// that it should not be returned as a state
|
||||
if (!origState_.containsKey(plainName_)) {
|
||||
return []
|
||||
}
|
||||
def filenameTemplate = origState_[plainName_]
|
||||
// if the pararameter is multiple: true, fetch the template
|
||||
if (par.multiple && filenameTemplate instanceof List) {
|
||||
filenameTemplate = filenameTemplate[0]
|
||||
}
|
||||
// instantiate the template
|
||||
def filename = filenameTemplate
|
||||
.replaceAll('\\$id', id_)
|
||||
.replaceAll('\\$\\{id\\}', id_)
|
||||
.replaceAll('\\$key', key_)
|
||||
.replaceAll('\\$\\{key\\}', key_)
|
||||
if (par.multiple) {
|
||||
// if the parameter is multiple: true, the filename
|
||||
// should contain a wildcard '*' that is replaced with
|
||||
// the index of the file
|
||||
assert filename.contains("*") : "Module '${key_}' id '${id_}': Multiple output files specified, but no wildcard '*' in the filename: ${filename}"
|
||||
def outputPerFile = value.withIndex().collect{ val, ix ->
|
||||
def filename_ix = filename.replace("*", ix.toString())
|
||||
def inputPath = val instanceof File ? val.toPath() : val
|
||||
[inputPath: inputPath, outputFilename: filename_ix]
|
||||
}
|
||||
def transposedOutputs = ["inputPath", "outputFilename"].collectEntries{ key ->
|
||||
[key, outputPerFile.collect{dic -> dic[key]}]
|
||||
}
|
||||
return [[key: plainName_] + transposedOutputs]
|
||||
} else {
|
||||
def value_ = java.nio.file.Paths.get(filename)
|
||||
def inputPath = value instanceof File ? value.toPath() : value
|
||||
return [[inputPath: [inputPath], outputFilename: [filename]]]
|
||||
}
|
||||
}
|
||||
|
||||
def inputPaths = processedState.collectMany{it.inputPath}
|
||||
def outputFilenames = processedState.collectMany{it.outputFilename}
|
||||
|
||||
|
||||
[id_, inputPaths, outputFilenames]
|
||||
}
|
||||
| publishFilesProc
|
||||
emit: input_ch
|
||||
}
|
||||
return publishFilesSimpleWf
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// helper file: 'src/main/resources/io/viash/runners/nextflow/states/publishStates.nf'
|
||||
def collectFiles(obj) {
|
||||
if (obj instanceof java.io.File || obj instanceof Path) {
|
||||
@@ -1723,8 +1877,6 @@ def publishStates(Map args) {
|
||||
|
||||
// the input files and the target output filenames
|
||||
def inputoutputFilenames_ = collectInputOutputPaths(state_, id_ + "." + key_).transpose()
|
||||
def inputFiles_ = inputoutputFilenames_[0]
|
||||
def outputFilenames_ = inputoutputFilenames_[1]
|
||||
|
||||
def yamlFilename = yamlTemplate_
|
||||
.replaceAll('\\$id', id_)
|
||||
@@ -1737,7 +1889,7 @@ def publishStates(Map args) {
|
||||
// convert state to yaml blob
|
||||
def yamlBlob_ = toRelativeTaggedYamlBlob([id: id_] + state_, java.nio.file.Paths.get(yamlFilename))
|
||||
|
||||
[id_, yamlBlob_, yamlFilename, inputFiles_, outputFilenames_]
|
||||
[id_, yamlBlob_, yamlFilename]
|
||||
}
|
||||
| publishStatesProc
|
||||
emit: input_ch
|
||||
@@ -1749,33 +1901,17 @@ process publishStatesProc {
|
||||
publishDir path: "${getPublishDir()}/", mode: "copy"
|
||||
tag "$id"
|
||||
input:
|
||||
tuple val(id), val(yamlBlob), val(yamlFile), path(inputFiles, stageAs: "_inputfile?/*"), val(outputFiles)
|
||||
tuple val(id), val(yamlBlob), val(yamlFile)
|
||||
output:
|
||||
tuple val(id), path{[yamlFile] + outputFiles}
|
||||
tuple val(id), path{[yamlFile]}
|
||||
script:
|
||||
def copyCommands = [
|
||||
inputFiles instanceof List ? inputFiles : [inputFiles],
|
||||
outputFiles instanceof List ? outputFiles : [outputFiles]
|
||||
]
|
||||
.transpose()
|
||||
.collectMany{infile, outfile ->
|
||||
if (infile.toString() != outfile.toString()) {
|
||||
[
|
||||
"[ -d \"\$(dirname '${outfile.toString()}')\" ] || mkdir -p \"\$(dirname '${outfile.toString()}')\"",
|
||||
"cp -r '${infile.toString()}' '${outfile.toString()}'"
|
||||
]
|
||||
} else {
|
||||
// no need to copy if infile is the same as outfile
|
||||
[]
|
||||
}
|
||||
}
|
||||
"""
|
||||
mkdir -p "\$(dirname '${yamlFile}')"
|
||||
echo "Storing state as yaml"
|
||||
echo '${yamlBlob}' > '${yamlFile}'
|
||||
echo "Copying output files to destination folder"
|
||||
${copyCommands.join("\n ")}
|
||||
"""
|
||||
mkdir -p "\$(dirname '${yamlFile}')"
|
||||
echo "Storing state as yaml"
|
||||
cat > '${yamlFile}' << HERE
|
||||
${yamlBlob}
|
||||
HERE
|
||||
"""
|
||||
}
|
||||
|
||||
|
||||
@@ -1806,13 +1942,10 @@ def publishStatesByConfig(Map args) {
|
||||
.replaceAll('\\$\\{key\\}', key_)
|
||||
def yamlDir = java.nio.file.Paths.get(yamlFilename).getParent()
|
||||
|
||||
// the processed state is a list of [key, value, inputPath, outputFilename] tuples, where
|
||||
// the processed state is a list of [key, value] tuples, where
|
||||
// - key is a String
|
||||
// - value is any object that can be serialized to a Yaml (so a String/Integer/Long/Double/Boolean, a List, a Map, or a Path)
|
||||
// - inputPath is a List[Path]
|
||||
// - outputFilename is a List[String]
|
||||
// - (key, value) are the tuples that will be saved to the state.yaml file
|
||||
// - (inputPath, outputFilename) are the files that will be copied from src to dest (relative to the state.yaml)
|
||||
def processedState =
|
||||
config.allArguments
|
||||
.findAll { it.direction == "output" }
|
||||
@@ -1829,7 +1962,7 @@ def publishStatesByConfig(Map args) {
|
||||
// in the state as-is, but is not something that needs
|
||||
// to be copied from the source path to the dest path
|
||||
if (par.type != "file") {
|
||||
return [[key: plainName_, value: value, inputPath: [], outputFilename: []]]
|
||||
return [[key: plainName_, value: value]]
|
||||
}
|
||||
// if the orig state does not contain this filename,
|
||||
// it's an optional argument for which the user specified
|
||||
@@ -1860,13 +1993,9 @@ def publishStatesByConfig(Map args) {
|
||||
if (yamlDir != null) {
|
||||
value_ = yamlDir.relativize(value_)
|
||||
}
|
||||
def inputPath = val instanceof File ? val.toPath() : val
|
||||
[value: value_, inputPath: inputPath, outputFilename: filename_ix]
|
||||
return value_
|
||||
}
|
||||
def transposedOutputs = ["value", "inputPath", "outputFilename"].collectEntries{ key ->
|
||||
[key, outputPerFile.collect{dic -> dic[key]}]
|
||||
}
|
||||
return [[key: plainName_] + transposedOutputs]
|
||||
return [["key": plainName_, "value": outputPerFile]]
|
||||
} else {
|
||||
def value_ = java.nio.file.Paths.get(filename)
|
||||
// if id contains a slash
|
||||
@@ -1874,18 +2003,17 @@ def publishStatesByConfig(Map args) {
|
||||
value_ = yamlDir.relativize(value_)
|
||||
}
|
||||
def inputPath = value instanceof File ? value.toPath() : value
|
||||
return [[key: plainName_, value: value_, inputPath: [inputPath], outputFilename: [filename]]]
|
||||
return [["key": plainName_, value: value_]]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def updatedState_ = processedState.collectEntries{[it.key, it.value]}
|
||||
def inputPaths = processedState.collectMany{it.inputPath}
|
||||
def outputFilenames = processedState.collectMany{it.outputFilename}
|
||||
|
||||
// convert state to yaml blob
|
||||
def yamlBlob_ = toTaggedYamlBlob([id: id_] + updatedState_)
|
||||
|
||||
[id_, yamlBlob_, yamlFilename, inputPaths, outputFilenames]
|
||||
[id_, yamlBlob_, yamlFilename]
|
||||
}
|
||||
| publishStatesProc
|
||||
emit: input_ch
|
||||
@@ -2559,7 +2687,8 @@ def _debug(workflowArgs, debugKey) {
|
||||
def workflowFactory(Map args, Map defaultWfArgs, Map meta) {
|
||||
def workflowArgs = processWorkflowArgs(args, defaultWfArgs, meta)
|
||||
def key_ = workflowArgs["key"]
|
||||
|
||||
def multipleArgs = meta.config.allArguments.findAll{ it.multiple }.collect{it.plainName}
|
||||
|
||||
workflow workflowInstance {
|
||||
take: input_
|
||||
|
||||
@@ -2716,12 +2845,36 @@ def workflowFactory(Map args, Map defaultWfArgs, Map meta) {
|
||||
}
|
||||
|
||||
// TODO: move some of the _meta.join_id wrangling to the safeJoin() function.
|
||||
def chInitialOutput = chArgsWithDefaults
|
||||
def chInitialOutputMulti = chArgsWithDefaults
|
||||
| _debug(workflowArgs, "processed")
|
||||
// run workflow
|
||||
| innerWorkflowFactory(workflowArgs)
|
||||
// check output tuple
|
||||
| map { id_, output_ ->
|
||||
def chInitialOutputList = chInitialOutputMulti instanceof List ? chInitialOutputMulti : [chInitialOutputMulti]
|
||||
assert chInitialOutputList.size() > 0: "should have emitted at least one output channel"
|
||||
// Add a channel ID to the events, which designates the channel the event was emitted from as a running number
|
||||
// This number is used to sort the events later when the events are gathered from across the channels.
|
||||
def chInitialOutputListWithIndexedEvents = chInitialOutputList.withIndex().collect{channel, channelIndex ->
|
||||
def newChannel = channel
|
||||
| map {tuple ->
|
||||
assert tuple instanceof List :
|
||||
"Error in module '${key_}': element in output channel should be a tuple [id, data, ...otherargs...]\n" +
|
||||
" Example: [\"id\", [input: file('foo.txt'), arg: 10]].\n" +
|
||||
" Expected class: List. Found: tuple.getClass() is ${tuple.getClass()}"
|
||||
|
||||
def newEvent = [channelIndex] + tuple
|
||||
return newEvent
|
||||
}
|
||||
return newChannel
|
||||
}
|
||||
// Put the events into 1 channel, cover case where there is only one channel is emitted
|
||||
def chInitialOutput = chInitialOutputList.size() > 1 ? \
|
||||
chInitialOutputListWithIndexedEvents[0].mix(*chInitialOutputListWithIndexedEvents.tail()) : \
|
||||
chInitialOutputListWithIndexedEvents[0]
|
||||
def chInitialOutputProcessed = chInitialOutput
|
||||
| map { tuple ->
|
||||
def channelId = tuple[0]
|
||||
def id_ = tuple[1]
|
||||
def output_ = tuple[2]
|
||||
|
||||
// see if output map contains metadata
|
||||
def meta_ =
|
||||
@@ -2734,19 +2887,94 @@ def workflowFactory(Map args, Map defaultWfArgs, Map meta) {
|
||||
output_ = output_.findAll{k, v -> k != "_meta"}
|
||||
|
||||
// check value types
|
||||
output_ = _processOutputValues(output_, meta.config, id_, key_)
|
||||
output_ = _checkValidOutputArgument(output_, meta.config, id_, key_)
|
||||
|
||||
// simplify output if need be
|
||||
if (workflowArgs.auto.simplifyOutput && output_.size() == 1) {
|
||||
output_ = output_.values()[0]
|
||||
}
|
||||
|
||||
[join_id, id_, output_]
|
||||
[join_id, channelId, id_, output_]
|
||||
}
|
||||
// | view{"chInitialOutput: ${it.take(3)}"}
|
||||
|
||||
// join the output [prev_id, channel_id, new_id, output] with the previous state [prev_id, state, ...]
|
||||
def chPublishWithPreviousState = safeJoin(chInitialOutputProcessed, chRunFiltered, key_)
|
||||
// input tuple format: [join_id, channel_id, id, output, prev_state, ...]
|
||||
// output tuple format: [join_id, channel_id, id, new_state, ...]
|
||||
| map{ tup ->
|
||||
def new_state = workflowArgs.toState(tup.drop(2).take(3))
|
||||
tup.take(3) + [new_state] + tup.drop(5)
|
||||
}
|
||||
if (workflowArgs.auto.publish == "state") {
|
||||
def chPublishFiles = chPublishWithPreviousState
|
||||
// input tuple format: [join_id, channel_id, id, new_state, ...]
|
||||
// output tuple format: [join_id, channel_id, id, new_state]
|
||||
| map{ tup ->
|
||||
tup.take(4)
|
||||
}
|
||||
|
||||
safeJoin(chPublishFiles, chArgsWithDefaults, key_)
|
||||
// input tuple format: [join_id, channel_id, id, new_state, orig_state, ...]
|
||||
// output tuple format: [id, new_state, orig_state]
|
||||
| map { tup ->
|
||||
tup.drop(2).take(3)
|
||||
}
|
||||
| publishFilesByConfig(key: key_, config: meta.config)
|
||||
}
|
||||
// Join the state from the events that were emitted from different channels
|
||||
def chJoined = chInitialOutputProcessed
|
||||
| map {tuple ->
|
||||
def join_id = tuple[0]
|
||||
def channel_id = tuple[1]
|
||||
def id = tuple[2]
|
||||
def other = tuple.drop(3)
|
||||
// Below, groupTuple is used to join the events. To make sure resuming a workflow
|
||||
// keeps working, the output state must be deterministic. This means the state needs to be
|
||||
// sorted with groupTuple's has a 'sort' argument. This argument can be set to 'hash',
|
||||
// but hashing the state when it is large can be problematic in terms of performance.
|
||||
// Therefore, a custom comparator function is provided. We add the channel ID to the
|
||||
// states so that we can use the channel ID to sort the items.
|
||||
def stateWithChannelID = [[channel_id] * other.size(), other].transpose()
|
||||
// A comparator that is provided to groupTuple's 'sort' argument is applied
|
||||
// to all elements of the event tuple (that is not the 'id'). The comparator
|
||||
// closure that is used below expects the input to be List. So the join_id and
|
||||
// channel_id must also be wrapped in a list.
|
||||
[[join_id], [channel_id], id] + stateWithChannelID
|
||||
}
|
||||
| groupTuple(by: 2, sort: {a, b -> a[0] <=> b[0]}, size: chInitialOutputList.size(), remainder: true)
|
||||
| map {join_ids, _, id, statesWithChannelID ->
|
||||
// Remove the channel IDs from the states
|
||||
def states = statesWithChannelID.collect{it[1]}
|
||||
def newJoinId = join_ids.flatten().unique{a, b -> a <=> b}
|
||||
assert newJoinId.size() == 1: "Multiple events were emitted for '$id'."
|
||||
def newJoinIdUnique = newJoinId[0]
|
||||
|
||||
// Merge the states from the different channels
|
||||
def newState = states.inject([:]){ old_state, state_to_add ->
|
||||
return old_state + state_to_add.collectEntries{k, v ->
|
||||
if (!multipleArgs.contains(k)) {
|
||||
// if the key is not a multiple argument, we expect only one value
|
||||
if (old_state.containsKey(k)) {
|
||||
assert old_state[k] == v : "ID $id: multiple entries for argument $k were emitted."
|
||||
}
|
||||
[k, v]
|
||||
} else {
|
||||
// if the key is a multiple argument, append the different values into one list
|
||||
def prevValue = old_state.getOrDefault(k, [])
|
||||
def prevValueAsList = prevValue instanceof List ? prevValue : [prevValue]
|
||||
[k, prevValueAsList + v]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_checkAllRequiredOuputsPresent(newState, meta.config, id, key_)
|
||||
|
||||
// simplify output if need be
|
||||
if (workflowArgs.auto.simplifyOutput && newState.size() == 1) {
|
||||
newState = newState.values()[0]
|
||||
}
|
||||
|
||||
return [newJoinIdUnique, id, newState]
|
||||
}
|
||||
|
||||
// join the output [prev_id, new_id, output] with the previous state [prev_id, state, ...]
|
||||
def chNewState = safeJoin(chInitialOutput, chRunFiltered, key_)
|
||||
def chNewState = safeJoin(chJoined, chRunFiltered, key_)
|
||||
// input tuple format: [join_id, id, output, prev_state, ...]
|
||||
// output tuple format: [join_id, id, new_state, ...]
|
||||
| map{ tup ->
|
||||
@@ -2755,23 +2983,21 @@ def workflowFactory(Map args, Map defaultWfArgs, Map meta) {
|
||||
}
|
||||
|
||||
if (workflowArgs.auto.publish == "state") {
|
||||
def chPublish = chNewState
|
||||
def chPublishStates = chNewState
|
||||
// input tuple format: [join_id, id, new_state, ...]
|
||||
// output tuple format: [join_id, id, new_state]
|
||||
| map{ tup ->
|
||||
tup.take(3)
|
||||
}
|
||||
|
||||
safeJoin(chPublish, chArgsWithDefaults, key_)
|
||||
safeJoin(chPublishStates, chArgsWithDefaults, key_)
|
||||
// input tuple format: [join_id, id, new_state, orig_state, ...]
|
||||
// output tuple format: [id, new_state, orig_state]
|
||||
| map { tup ->
|
||||
tup.drop(1).take(3)
|
||||
}
|
||||
}
|
||||
| publishStatesByConfig(key: key_, config: meta.config)
|
||||
}
|
||||
|
||||
// remove join_id and meta
|
||||
chReturn = chNewState
|
||||
| map { tup ->
|
||||
// input tuple format: [join_id, id, new_state, ...]
|
||||
@@ -2805,7 +3031,7 @@ meta = [
|
||||
"resources_dir": moduleDir.toRealPath().normalize(),
|
||||
"config": processConfig(readJsonBlob('''{
|
||||
"name" : "runner",
|
||||
"version" : "v0.3.5",
|
||||
"version" : "v0.3.11",
|
||||
"argument_groups" : [
|
||||
{
|
||||
"name" : "Input arguments",
|
||||
@@ -2899,6 +3125,19 @@ meta = [
|
||||
"direction" : "output",
|
||||
"multiple" : false,
|
||||
"multiple_sep" : ";"
|
||||
},
|
||||
{
|
||||
"type" : "file",
|
||||
"name" : "--demultiplexer_logs",
|
||||
"default" : [
|
||||
"demultiplexer_logs"
|
||||
],
|
||||
"must_exist" : true,
|
||||
"create_parent" : true,
|
||||
"required" : false,
|
||||
"direction" : "output",
|
||||
"multiple" : false,
|
||||
"multiple_sep" : ";"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -2929,6 +3168,10 @@ meta = [
|
||||
],
|
||||
"description" : "Runner for demultiplexing of raw sequencing data",
|
||||
"status" : "enabled",
|
||||
"scope" : {
|
||||
"image" : "public",
|
||||
"target" : "public"
|
||||
},
|
||||
"requirements" : {
|
||||
"commands" : [
|
||||
"ps"
|
||||
@@ -3039,31 +3282,31 @@ meta = [
|
||||
"runner" : "nextflow",
|
||||
"engine" : "native|native",
|
||||
"output" : "target/nextflow/runner",
|
||||
"viash_version" : "0.9.0",
|
||||
"git_commit" : "18e092d169cc2704a2e4e705485d9231911d0484",
|
||||
"viash_version" : "0.9.4",
|
||||
"git_commit" : "cd2cfac18e14622e1a5d7a0d489f64c30e83dab2",
|
||||
"git_remote" : "https://github.com/viash-hub/demultiplex",
|
||||
"git_tag" : "v0.3.4-6-g18e092d"
|
||||
"git_tag" : "v0.3.10-5-gcd2cfac"
|
||||
},
|
||||
"package_config" : {
|
||||
"name" : "demultiplex",
|
||||
"version" : "v0.3.5",
|
||||
"version" : "v0.3.11",
|
||||
"description" : "Demultiplexing pipeline\n",
|
||||
"info" : {
|
||||
"test_resources" : [
|
||||
{
|
||||
"path" : "gs://viash-hub-test-data/demultiplex/v2/",
|
||||
"path" : "gs://viash-hub-resources/demultiplex/v3",
|
||||
"dest" : "testData"
|
||||
}
|
||||
]
|
||||
},
|
||||
"viash_version" : "0.9.0",
|
||||
"viash_version" : "0.9.4",
|
||||
"source" : "src",
|
||||
"target" : "target",
|
||||
"config_mods" : [
|
||||
".requirements.commands := ['ps']\n.runners[.type == 'nextflow'].directives.tag := '$id'\n.resources += {path: '/src/config/labels.config', dest: 'nextflow_labels.config'}\n.runners[.type == 'nextflow'].config.script := 'includeConfig(\\"nextflow_labels.config\\")'\n",
|
||||
".requirements.commands += ['ps']\n.runners[.type == 'nextflow'].directives.tag := '$id'\n.resources += {path: '/src/config/labels.config', dest: 'nextflow_labels.config'}\n.runners[.type == 'nextflow'].config.script := 'includeConfig(\\"nextflow_labels.config\\")'\n",
|
||||
".engines += { type: \\"native\\" }",
|
||||
".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'",
|
||||
".engines[.type == 'docker'].target_tag := 'v0.3.5'"
|
||||
".engines[.type == 'docker'].target_tag := 'v0.3.11'"
|
||||
],
|
||||
"keywords" : [
|
||||
"bioinformatics",
|
||||
@@ -3118,6 +3361,7 @@ workflow run_wf {
|
||||
"output": "$id/fastq",
|
||||
"output_falco": "$id/qc/fastqc",
|
||||
"output_multiqc": "$id/qc/multiqc_report.html",
|
||||
"demultiplexer_logs": "$id/demultiplexer_logs",
|
||||
]
|
||||
if (state.run_information) {
|
||||
state_to_pass += ["output_run_information": state.run_information.getName()]
|
||||
@@ -3138,6 +3382,7 @@ workflow run_wf {
|
||||
def falco_output_1 = (id2 == "run") ? state.falco_output : "${id2}/" + state.falco_output
|
||||
def multiqc_output_1 = (id2 == "run") ? state.multiqc_output : "${id2}/" + state.multiqc_output
|
||||
def run_information_output_1 = (id2 == "run") ? "${state.output_run_information.getName()}" : "${id2}/${state.output_run_information.getName()}"
|
||||
def demultiplexer_logs_output = (id2 == "run") ? state.demultiplexer_logs : "${id2}/${state.demultiplexer_logs.getName()}"
|
||||
|
||||
if (id2 == "run") {
|
||||
println("Publising to ${params.publish_dir}")
|
||||
@@ -3150,10 +3395,12 @@ workflow run_wf {
|
||||
input_falco: state.output_falco,
|
||||
input_multiqc: state.output_multiqc,
|
||||
input_run_information: state.output_run_information,
|
||||
input_demultiplexer_logs: state.demultiplexer_logs,
|
||||
output: fastq_output_1,
|
||||
output_falco: falco_output_1,
|
||||
output_multiqc: multiqc_output_1,
|
||||
output_run_information: run_information_output_1,
|
||||
output_demultiplexer_logs: demultiplexer_logs_output,
|
||||
]
|
||||
},
|
||||
toState: { id, result, state -> [:] },
|
||||
|
||||
@@ -2,7 +2,7 @@ manifest {
|
||||
name = 'runner'
|
||||
mainScript = 'main.nf'
|
||||
nextflowVersion = '!>=20.12.1-edge'
|
||||
version = 'v0.3.5'
|
||||
version = 'v0.3.11'
|
||||
description = 'Runner for demultiplexing of raw sequencing data'
|
||||
}
|
||||
|
||||
|
||||
@@ -80,10 +80,10 @@
|
||||
"fastq_output": {
|
||||
"type":
|
||||
"string",
|
||||
"description": "Type: `file`, default: `$id.$key.fastq_output.fastq_output`. ",
|
||||
"help_text": "Type: `file`, default: `$id.$key.fastq_output.fastq_output`. "
|
||||
"description": "Type: `file`, default: `fastq`. ",
|
||||
"help_text": "Type: `file`, default: `fastq`. "
|
||||
,
|
||||
"default":"$id.$key.fastq_output.fastq_output"
|
||||
"default":"fastq"
|
||||
}
|
||||
|
||||
|
||||
@@ -91,10 +91,10 @@
|
||||
"falco_output": {
|
||||
"type":
|
||||
"string",
|
||||
"description": "Type: `file`, default: `$id.$key.falco_output.falco_output`. ",
|
||||
"help_text": "Type: `file`, default: `$id.$key.falco_output.falco_output`. "
|
||||
"description": "Type: `file`, default: `qc/fastqc`. ",
|
||||
"help_text": "Type: `file`, default: `qc/fastqc`. "
|
||||
,
|
||||
"default":"$id.$key.falco_output.falco_output"
|
||||
"default":"qc/fastqc"
|
||||
}
|
||||
|
||||
|
||||
@@ -102,10 +102,21 @@
|
||||
"multiqc_output": {
|
||||
"type":
|
||||
"string",
|
||||
"description": "Type: `file`, default: `$id.$key.multiqc_output.html`. ",
|
||||
"help_text": "Type: `file`, default: `$id.$key.multiqc_output.html`. "
|
||||
"description": "Type: `file`, default: `qc/multiqc_report.html`. ",
|
||||
"help_text": "Type: `file`, default: `qc/multiqc_report.html`. "
|
||||
,
|
||||
"default":"$id.$key.multiqc_output.html"
|
||||
"default":"qc/multiqc_report.html"
|
||||
}
|
||||
|
||||
|
||||
,
|
||||
"demultiplexer_logs": {
|
||||
"type":
|
||||
"string",
|
||||
"description": "Type: `file`, default: `demultiplexer_logs`. ",
|
||||
"help_text": "Type: `file`, default: `demultiplexer_logs`. "
|
||||
,
|
||||
"default":"demultiplexer_logs"
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user