From 3398a4fbabb9e3e7cd89009f85e773c1463c94e5 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 13:09:36 +0000 Subject: [PATCH 001/113] Correct memory in test profile --- docs/hello_nf-core/01_run_demo.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index e6e15048de..7494a8a13b 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -168,7 +168,7 @@ The `test` profile for `nf-core/demo` is shown below: process { resourceLimits = [ cpus: 4, - memory: '15.GB', + memory: '4.GB', time: '1.h' ] } From ef869bbd834b3c9dee4ef51df7b7d6508a84ae7e Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 13:14:51 +0000 Subject: [PATCH 002/113] Reframe test profile section to avoid implying param inference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous text suggested learners could infer required parameters by examining what was present/absent in the test profile config. This is misleading - the test profile only shows pre-configured values, not requirements. Changed section 2.1 to: - Emphasize what the test profile *provides* (pre-configured input) - Point to the usage example comment as the source for --outdir info - Avoid suggesting we can infer requirements from config structure This keeps the pedagogical value while being accurate about where information comes from, deferring proper param discovery (via schema) to later sections. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/01_run_demo.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index 7494a8a13b..13e9e22dac 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -152,7 +152,7 @@ This is a minimal set of configuration settings for the pipeline to run using a It's good practice to check what a pipeline's test profile specifies before running it. The `test` profile for `nf-core/demo` is shown below: -```groovy title="conf/test.config" linenums="1" hl_lines="26" +```groovy title="conf/test.config" linenums="1" hl_lines="8 26" /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Nextflow config file for running minimal tests @@ -183,12 +183,14 @@ params { } ``` -This tells us that the `nf-core/demo` test profile already specifies the input parameter, so you don't have to provide any input yourself. -However, the `outdir` parameter is not included in the test profile, so we will have to add it to the execution command using the `--outdir` flag. +The test profile shows us what has been pre-configured for testing: most notably, the `input` parameter is already set to point to a test dataset, so we don't need to provide our own data. + +The comment block at the top also includes a usage example showing how to run with this test profile. +Notice that it includes `--outdir ` - this tells us we'll need to specify an output directory when we run the pipeline. ### 2.2. Run the pipeline -Our examination of the test profile above told us what pipeline argument(s) we need to specify: just `--outdir`. +Based on the usage example in the test profile, we know we need to specify `--outdir` to tell the pipeline where to save results. We're also going to specify `-profile docker,test`, which by nf-core convention enables the use of Docker containers, and of course, invokes the test profile. From 73663f537b6b5d235bdea9f85137488218fb4cee Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 13:17:43 +0000 Subject: [PATCH 003/113] Update Nextflow version to 25.04.3 in nf-core materials MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Standardized all Nextflow version references in the Hello nf-core training materials to version 25.04.3. Updated files: - 01_run_demo.md: 24.10.0 → 25.04.3 - 02_rewrite_hello.md: 24.10.4 → 25.04.3 (4 occurrences) Files 03_use_module.md and 05_input_validation.md already had the correct version. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/01_run_demo.md | 2 +- docs/hello_nf-core/02_rewrite_hello.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index 13e9e22dac..4b359a526a 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -203,7 +203,7 @@ nextflow run nf-core/demo -profile docker,test --outdir demo-results Here's the console output from the pipeline: ```console title="Output" - N E X T F L O W ~ version 24.10.0 + N E X T F L O W ~ version 25.04.3 Launching `https://github.com/nf-core/demo` [maniac_jones] DSL2 - revision: 04060b4644 [master] diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index 3971494397..3061809728 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -160,7 +160,7 @@ nextflow run ./core-hello -profile docker,test --outdir core-hello-results ``` ```console title="Output" - N E X T F L O W ~ version 24.10.4 + N E X T F L O W ~ version 25.04.3 Launching `core-hello/main.nf` [special_ride] DSL2 - revision: c31b966b36 @@ -303,7 +303,7 @@ nextflow run original-hello/hello.nf ``` ```console title="Output" - N E X T F L O W ~ version 24.10.4 + N E X T F L O W ~ version 25.04.3 Launching `original-hello/hello.nf` [goofy_babbage] DSL2 - revision: e9e72441e9 @@ -576,7 +576,7 @@ nextflow run ./original-hello If you made all the changes correctly, this should run to completion. ```console title="Output" - N E X T F L O W ~ version 24.10.4 + N E X T F L O W ~ version 25.04.3 Launching `original-hello/main.nf` [friendly_wright] DSL2 - revision: 1ecd2d9c0a @@ -1205,7 +1205,7 @@ nextflow run core-hello --outdir core-hello-results -profile test,docker --valid If you've done all of the modifications correctly, it should run to completion. ```console title="Output" - N E X T F L O W ~ version 24.10.4 + N E X T F L O W ~ version 25.04.3 Launching `core-hello/main.nf` [agitated_noyce] DSL2 - revision: c31b966b36 From 2046e07ed9939d0c9ec965628adcd4ae9d8d214b Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 13:19:49 +0000 Subject: [PATCH 004/113] Update nf-core/demo version to 1.0.2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated all references to nf-core/demo from version 1.0.1 to 1.0.2 to reflect the current release. Changes: - Console output example showing pipeline version - Documentation link to nf-co.re/demo 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/01_run_demo.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index 4b359a526a..be8d1ef3ea 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -214,7 +214,7 @@ Launching `https://github.com/nf-core/demo` [maniac_jones] DSL2 - revision: 0406 |\ | |__ __ / ` / \ |__) |__ } { | \| | \__, \__/ | \ |___ \`-._,-`-, `._,._,' - nf-core/demo 1.0.1 + nf-core/demo 1.0.2 ------------------------------------------------------ Input/output options input : https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/samplesheet/samplesheet_test_illumina_amplicon.csv @@ -306,7 +306,7 @@ demo-results/ └── pipeline_dag_2025-03-05_09-44-26.html ``` -If you're curious about the specifics what that all means, check out [the nf-core/demo pipeline documentation page](https://nf-co.re/demo/1.0.1/). +If you're curious about the specifics what that all means, check out [the nf-core/demo pipeline documentation page](https://nf-co.re/demo/1.0.2/). At this stage, what's important to observe is that the results are organized by module, and there is additionally a directory called `pipeline_info` containing various timestamped reports about the pipeline execution. This is standard for nf-core pipelines. From a140ea4ae348a3ef5f65feec76dd7e29fe2ddff0 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 13:24:38 +0000 Subject: [PATCH 005/113] Updated nf-core/demo output --- docs/hello_nf-core/01_run_demo.md | 33 ++++++++++++++++--------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index be8d1ef3ea..43da77ad3c 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -205,7 +205,7 @@ Here's the console output from the pipeline: ```console title="Output" N E X T F L O W ~ version 25.04.3 -Launching `https://github.com/nf-core/demo` [maniac_jones] DSL2 - revision: 04060b4644 [master] +Launching `https://github.com/nf-core/demo` [happy_varahamihira] DSL2 - revision: db7f526ce1 [master] ------------------------------------------------------ @@ -218,26 +218,30 @@ Launching `https://github.com/nf-core/demo` [maniac_jones] DSL2 - revision: 0406 ------------------------------------------------------ Input/output options input : https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/samplesheet/samplesheet_test_illumina_amplicon.csv - outdir : results + outdir : demo-results Institutional config options config_profile_name : Test profile config_profile_description: Minimal test dataset to check pipeline function +Generic options + trace_report_suffix : 2025-10-30_13-22-01 + Core Nextflow options revision : master - runName : maniac_jones + runName : happy_varahamihira containerEngine : docker - launchDir : /workspaces/training/side-quests/nf-core/nf-core-demo - workDir : /workspaces/training/side-quests/nf-core/nf-core-demo/work + launchDir : /workspaces/training/hello-nf-core + workDir : /workspaces/training/hello-nf-core/work projectDir : /workspaces/.nextflow/assets/nf-core/demo - userName : gitpod + userName : root profile : docker,test - configFiles : + configFiles : /workspaces/.nextflow/assets/nf-core/demo/nextflow.config !! Only displaying parameters that differ from the pipeline defaults !! -------------------------------------------------------* The pipeline - https://doi.org/10.5281/zenodo.12192442 +------------------------------------------------------ +* The pipeline + https://doi.org/10.5281/zenodo.12192442 * The nf-core framework https://doi.org/10.1038/s41587-020-0439-x @@ -245,15 +249,12 @@ Core Nextflow options * Software dependencies https://github.com/nf-core/demo/blob/master/CITATIONS.md + executor > local (7) -[3c/a00024] NFC…_DEMO:DEMO:FASTQC (SAMPLE2_PE) | 3 of 3 ✔ -[94/d1d602] NFC…O:DEMO:SEQTK_TRIM (SAMPLE2_PE) | 3 of 3 ✔ -[ab/460670] NFCORE_DEMO:DEMO:MULTIQC | 1 of 1 ✔ +[db/fae3ff] NFCORE_DEMO:DEMO:FASTQC (SAMPLE3_SE) [100%] 3 of 3 ✔ +[d0/f6ea55] NFCORE_DEMO:DEMO:SEQTK_TRIM (SAMPLE1_PE) [100%] 3 of 3 ✔ +[af/e6da56] NFCORE_DEMO:DEMO:MULTIQC [100%] 1 of 1 ✔ -[nf-core/demo] Pipeline completed successfully- -Completed at: 05-Mar-2025 09:46:21 -Duration : 1m 54s -CPU hours : (a few seconds) -Succeeded : 7 ``` You see that there is more console output than when you run a basic Netxflow pipeline. From 7ea59bd30752fe18869afd939cc3f61db5516b36 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 13:25:37 +0000 Subject: [PATCH 006/113] mend --- docs/hello_nf-core/01_run_demo.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index 43da77ad3c..c69b2a8993 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -263,9 +263,9 @@ There's a header that includes a summary of the pipeline's version, inputs and o Moving on to the execution output, let's have a look at the lines that tell us what processes were run: ```console title="Output (subset)" -[3c/a00024] NFC…_DEMO:DEMO:FASTQC (SAMPLE2_PE) | 3 of 3 ✔ -[94/d1d602] NFC…O:DEMO:SEQTK_TRIM (SAMPLE2_PE) | 3 of 3 ✔ -[ab/460670] NFCORE_DEMO:DEMO:MULTIQC | 1 of 1 ✔ +[db/fae3ff] NFCORE_DEMO:DEMO:FASTQC (SAMPLE3_SE) [100%] 3 of 3 ✔ +[d0/f6ea55] NFCORE_DEMO:DEMO:SEQTK_TRIM (SAMPLE1_PE) [100%] 3 of 3 ✔ +[af/e6da56] NFCORE_DEMO:DEMO:MULTIQC [100%] 1 of 1 ✔ ``` This tells us that three processes were run, corresponding to the three tools shown in the pipeline documentation page on the nf-core website: FASTQC, SEQTK_TRIM and MULTIQC. From f9cb647bddf934dfdf96bd894f88e584f5a87ca5 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 13:28:27 +0000 Subject: [PATCH 007/113] update demo pipeline contents --- docs/hello_nf-core/01_run_demo.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index c69b2a8993..786c8a333a 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -337,6 +337,7 @@ tree -L 1 pipelines/nf-core/demo ```console title="Output (top-level only)" pipelines/nf-core/demo +pipelines/nf-core/demo ├── assets ├── CHANGELOG.md ├── CITATIONS.md @@ -347,10 +348,13 @@ pipelines/nf-core/demo ├── main.nf ├── modules ├── modules.json -├── nextflow_schema.json ├── nextflow.config +├── nextflow_schema.json +├── nf-test.config ├── README.md +├── ro-crate-metadata.json ├── subworkflows +├── tests ├── tower.yml └── workflows ``` From 7519429ddbf63fa600cd2b50ad7b8e76e8d41e3a Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 13:37:59 +0000 Subject: [PATCH 008/113] Refine subworkflow coverage --- docs/hello_nf-core/01_run_demo.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index 786c8a333a..61943586e3 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -375,13 +375,13 @@ The pipeline code organization follows a modular structure that is designed to m !!! note We won't go over the actual code for how these modular components are connected, because there is some additional complexity associated with the use of subworkflows that can be confusing, and understanding that is not necessary at this stage of the training. - For now, we're going to focus on the logic of this modular organization. + For now, we're going to focus on the logic of this modular organization. If you'd like to learn more about composing workflows with subworkflows, check out the [Workflows of Workflows](../side_quests/workflows_of_workflows/) Side Quest. #### 3.1.1. Overall organization and `main.nf` script At the top level, there is the `main.nf` script, which is the entrypoint Nextflow starts from when we execute `nextflow run nf-core/demo`. That means when you run `nextflow run nf-core/demo` to run the pipeline, Nextflow automatically finds and executes the `main.nf` script, and everything else will flow from there. -In practice, the `main.nf` script calls the actual workflow of interest, stored inside the `workflows` folder, called `demo.nf`. It also calls a few 'housekeeping' subworkflows that we're going to ignore for now. +The central logic of the pipeline is stored inside the `workflows` folder, in a file called `demo.nf`, which is called from `main.nf`. ```bash tree pipelines/nf-core/demo/workflows @@ -392,6 +392,8 @@ pipelines/nf-core/demo/workflows └── demo.nf ``` +`main.nf` also calls a few 'housekeeping' subworkflows that we're going to ignore for now. + The `demo.nf` workflow itself calls out to various script components, namely, modules and subworkflows, stored in the corresponding `modules` and `subworkflows` folders. - **Module:** A wrapper around a single process. From 879a0f2cac87680c638226d942a1c7da8040b60e Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 13:39:41 +0000 Subject: [PATCH 009/113] correct tree level --- docs/hello_nf-core/01_run_demo.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index 61943586e3..83ab67d6a7 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -421,7 +421,7 @@ In the nf-core project, modules are organized using a nested structure that refe The module code file describing the process is always called `main.nf`, and is accompanied by tests and `.yml` files. ```bash -tree -L 4 pipelines/nf-core/demo/modules +tree -L 3 pipelines/nf-core/demo/modules ``` ```console title="Output" From 848ada18bd4fa73b55095eddb2c37b99c1235872 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 13:43:45 +0000 Subject: [PATCH 010/113] Correct another tree level, params schema coverage --- docs/hello_nf-core/01_run_demo.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index 83ab67d6a7..efe5279219 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -455,7 +455,7 @@ As noted above, subworkflows function as wrappers that call two or more modules. In an nf-core pipeline, the subworkflows are divided into `local` and `nf-core` directories, and each subworkflow has its own nested directory structure with its own `main.nf` script. ```bash -tree -L 4 pipelines/nf-core/demo/subworkflows +tree -L 3 pipelines/nf-core/demo/subworkflows ``` ```console title="Output" @@ -512,7 +512,7 @@ In addition to these human-readable documents, there are two JSON files that pro The `nextflow_schema.json` is a file used to store information about the pipeline parameters including type, description and help text in a machine readable format. The schema is used for various purposes, including automated parameter validation, help text generation, and interactive parameter form rendering in UI interfaces. -```json title="assets/nextflow_schema.json (not showing full file)" linenums="1" +```json title="nextflow_schema.json (not showing full file)" linenums="1" { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://raw.githubusercontent.com/nf-core/demo/master/nextflow_schema.json", @@ -532,8 +532,7 @@ The schema is used for various purposes, including automated parameter validatio "format": "file-path", "exists": true, "schema": "assets/schema_input.json", - "mimetype": "text/csv", - "pattern": "^\\S+\\.csv$", + "pattern": "^\\S+\\.(csv|tsv|json|yaml|yml)$", "description": "Path to comma-separated file containing information about the samples in the experiment.", "help_text": "You will need to create a design file with information about the samples in your experiment before running the pipeline. Use this parameter to specify its location. It has to be a comma-separated file with 3 columns, and a header row. See [usage docs](https://nf-co.re/demo/usage#samplesheet-input).", "fa_icon": "fas fa-file-csv" From 4a4338227165c2fa10a662aa1336d2ef6b8d8ed4 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 13:45:32 +0000 Subject: [PATCH 011/113] Update input schema content --- docs/hello_nf-core/01_run_demo.md | 62 +++++++++++++++---------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index efe5279219..a579d2a6c9 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -565,37 +565,37 @@ Each column can have a type, pattern, description and help text in a machine rea ```json title="assets/schema_input.json" linenums="1" { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://raw.githubusercontent.com/nf-core/demo/master/assets/schema_input.json", - "title": "nf-core/demo pipeline - params.input schema", - "description": "Schema for the file provided with params.input", - "type": "array", - "items": { - "type": "object", - "properties": { - "sample": { - "type": "string", - "pattern": "^\\S+$", - "errorMessage": "Sample name must be provided and cannot contain spaces", - "meta": ["id"] - }, - "fastq_1": { - "type": "string", - "format": "file-path", - "exists": true, - "pattern": "^\\S+\\.f(ast)?q\\.gz$", - "errorMessage": "FastQ file for reads 1 must be provided, cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" - }, - "fastq_2": { - "type": "string", - "format": "file-path", - "exists": true, - "pattern": "^\\S+\\.f(ast)?q\\.gz$", - "errorMessage": "FastQ file for reads 2 cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" - } - }, - "required": ["sample", "fastq_1"] - } + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/nf-core/demo/master/assets/schema_input.json", + "title": "nf-core/demo pipeline - params.input schema", + "description": "Schema for the file provided with params.input", + "type": "array", + "items": { + "type": "object", + "properties": { + "sample": { + "type": "string", + "pattern": "^\\S+$", + "errorMessage": "Sample name must be provided and cannot contain spaces", + "meta": ["id"] + }, + "fastq_1": { + "type": "string", + "format": "file-path", + "exists": true, + "pattern": "^\\S+\\.f(ast)?q\\.gz$", + "errorMessage": "FastQ file for reads 1 must be provided, cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" + }, + "fastq_2": { + "type": "string", + "format": "file-path", + "exists": true, + "pattern": "^\\S+\\.f(ast)?q\\.gz$", + "errorMessage": "FastQ file for reads 2 cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" + } + }, + "required": ["sample", "fastq_1"] + } } ``` From e64de7e8e144ab63a4ec021c0681ccb54caeed9e Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 13:51:41 +0000 Subject: [PATCH 012/113] Fix priority issues in nf-core Part 1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix typo: 'Netxflow' → 'Nextflow' - Remove duplicate line in tree output - Consolidate WoW side quest references (remove redundant mention) - Add explanation of container profiles before first use - Standardize cross-reference paths to use relative URLs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/01_run_demo.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index a579d2a6c9..7d0d847ac7 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -194,6 +194,12 @@ Based on the usage example in the test profile, we know we need to specify `--ou We're also going to specify `-profile docker,test`, which by nf-core convention enables the use of Docker containers, and of course, invokes the test profile. +!!! note "Understanding container profiles" + + The `-profile docker` option tells Nextflow to use Docker containers for running processes. + nf-core pipelines are designed to work with containers (Docker, Singularity, etc.) to ensure reproducibility and eliminate software installation issues. + The profile system allows you to easily switch between different container engines or execution environments. + Let's try it! ```bash @@ -257,7 +263,7 @@ executor > local (7) -[nf-core/demo] Pipeline completed successfully- ``` -You see that there is more console output than when you run a basic Netxflow pipeline. +You see that there is more console output than when you run a basic Nextflow pipeline. There's a header that includes a summary of the pipeline's version, inputs and outputs, and a few elements of configuration. Moving on to the execution output, let's have a look at the lines that tell us what processes were run: @@ -337,7 +343,6 @@ tree -L 1 pipelines/nf-core/demo ```console title="Output (top-level only)" pipelines/nf-core/demo -pipelines/nf-core/demo ├── assets ├── CHANGELOG.md ├── CITATIONS.md @@ -375,7 +380,7 @@ The pipeline code organization follows a modular structure that is designed to m !!! note We won't go over the actual code for how these modular components are connected, because there is some additional complexity associated with the use of subworkflows that can be confusing, and understanding that is not necessary at this stage of the training. - For now, we're going to focus on the logic of this modular organization. If you'd like to learn more about composing workflows with subworkflows, check out the [Workflows of Workflows](../side_quests/workflows_of_workflows/) Side Quest. + For now, we're going to focus on the logic of this modular organization. #### 3.1.1. Overall organization and `main.nf` script @@ -485,7 +490,7 @@ Other pipelines may also use subworkflows as part of the main workflow of intere !!! note - If you would like to learn how to compose workflows with subworkflows, see the [Workflows of Workflows](https://training.nextflow.io/latest/side_quests/workflows_of_workflows/) Side Quest (also known as 'the WoW side quest'). + If you would like to learn how to compose workflows with subworkflows, see the [Workflows of Workflows](../side_quests/workflows_of_workflows/) Side Quest. ### 3.2. Configuration From aba50d7c41fe5fd68612e51f484088fbec98704d Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 13:55:52 +0000 Subject: [PATCH 013/113] Improve style consistency in nf-core Part 1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Standardize terminology: use 'pipeline documentation page' consistently - Improve voice consistency: prefer inclusive 'we' over imperative - Reduce passive voice: make Nextflow the active subject - Fix grammar: 'specifics of what' and 'retrieve pipelines' 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/01_run_demo.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index 7d0d847ac7..a51b5eeab6 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -18,7 +18,7 @@ In your web browser, go to https://nf-co.re/pipelines/ and type `demo` in the se ![search results](./img/search-results.png) -Click on the pipeline name, `demo`, to access the pipeline details page. +Click on the pipeline name, `demo`, to access the pipeline documentation page. Each released pipeline has a dedicated page that includes the following documentation sections: @@ -61,10 +61,10 @@ Let's retrieve the code so we can examine this structure. ### 1.2. Retrieve the pipeline code -Once we've determined the pipeline appears to be suitable for our purposes, we're going to want to try it out. -Fortunately Nextflow makes it easy to retrieve pipeline from correctly-formatted repositories without having to download anything manually. +Once we've determined the pipeline appears to be suitable for our purposes, let's try it out. +Fortunately Nextflow makes it easy to retrieve pipelines from correctly-formatted repositories without having to download anything manually. -Return to your terminal and run the following: +Let's return to the terminal and run the following: ```bash nextflow pull nf-core/demo @@ -91,7 +91,7 @@ nf-core/demo ``` You'll notice that the files are not in your current work directory. -By default, they are saved to `$NXF_HOME/assets`. +By default, Nextflow saves them to `$NXF_HOME/assets`. ```bash tree -L 2 $NXF_HOME/assets/ @@ -107,9 +107,9 @@ tree -L 2 $NXF_HOME/assets/ The full path may differ on your system if you're not using our training environment. -The location of the downloaded source code is intentionally 'out of the way' on the principle that these pipelines should be used more like libraries than code that you would directly interact with. +Nextflow keeps the downloaded source code intentionally 'out of the way' on the principle that these pipelines should be used more like libraries than code that you would directly interact with. -However, for the purposes of this training, we'd like to be able to poke around and see what's in there. +However, for the purposes of this training, we want to be able to poke around and see what's in there. So to make that easier, let's create a symbolic link to that location from our current working directory. ```bash @@ -313,7 +313,7 @@ demo-results/ └── pipeline_dag_2025-03-05_09-44-26.html ``` -If you're curious about the specifics what that all means, check out [the nf-core/demo pipeline documentation page](https://nf-co.re/demo/1.0.2/). +If you're curious about the specifics of what that all means, check out [the nf-core/demo pipeline documentation page](https://nf-co.re/demo/1.0.2/). At this stage, what's important to observe is that the results are organized by module, and there is additionally a directory called `pipeline_info` containing various timestamped reports about the pipeline execution. This is standard for nf-core pipelines. From 8b6378a3ac5124f7c962cf3d0425ea60a8d888ed Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 13:58:35 +0000 Subject: [PATCH 014/113] Add content enhancements to nf-core Part 1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add note about variable console output (timestamps, paths, names) - Add transition paragraph between user and developer perspectives - Clarify focus on file hierarchy vs code syntax These improvements help set learner expectations and smooth pedagogical flow. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/01_run_demo.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index a51b5eeab6..ffa88ada20 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -266,6 +266,10 @@ executor > local (7) You see that there is more console output than when you run a basic Nextflow pipeline. There's a header that includes a summary of the pipeline's version, inputs and outputs, and a few elements of configuration. +!!! note + + Your output will show different timestamps, execution names, and file paths, but the overall structure and process execution should be similar. + Moving on to the execution output, let's have a look at the lines that tell us what processes were run: ```console title="Output (subset)" @@ -330,6 +334,9 @@ Learn how the pipeline code is organized. --- +Now that we've successfully run the pipeline as users, let's shift our perspective to understand how nf-core pipelines are structured internally. +Understanding this organization will prepare you for developing your own nf-core-compatible pipelines in the upcoming parts of this course. + ## 3. Examine the pipeline code structure The nf-core project enforces strong guidelines for how pipelines are structured, and how the code is organized, configured and documented. @@ -371,7 +378,7 @@ We're going to look at the following categories: 2. Configuration, parameters and inputs 3. Documentation and related assets -Let's start with the code proper, though note that for now, we're going to focus on how everything is organized, without looking at the actual code just yet. +Let's start with the code proper, though note that for now, we're going to focus on the file hierarchy and structural organization, rather than diving into the code syntax within individual files. ### 3.1. Pipeline code components From 789c80281ae2da067365af8ebff6753ca4f1d755 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 14:10:28 +0000 Subject: [PATCH 015/113] Fix issues in nf-core Part 2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Priority fixes: - Fix grammar: 'inputs are handle' → 'inputs are handled' - Fix directory typo: 'config' → 'conf' - Remove incorrect line number reference - Remove redundant WoW side quest reference (already mentioned earlier) - Standardize WoW reference format to match Part 1 Style improvements: - Add transitional text between sections 3 and 4 - Add motivation for dummy entrypoint workflow in section 2.6 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/02_rewrite_hello.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index 3061809728..d56615ed3e 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -262,7 +262,7 @@ These are optional features of Nextflow that make the workflow **composable**, m !!! note "Composable workflows in depth" - The [Workflows of Workflows](../side_quests/workflows_of_workflows) side quest explores workflow composition in much greater depth, including how to compose multiple workflows together and manage complex data flows between them. We're introducing composability here because it's a fundamental requirement of the nf-core template architecture, which uses nested workflows to organize pipeline initialization, the main analysis workflow, and completion tasks into separate, reusable components. + The [Workflows of Workflows](../side_quests/workflows_of_workflows/) Side Quest explores workflow composition in much greater depth, including how to compose multiple workflows together and manage complex data flows between them. We're introducing composability here because it's a fundamental requirement of the nf-core template architecture, which uses nested workflows to organize pipeline initialization, the main analysis workflow, and completion tasks into separate, reusable components. We are going to need to plug the relevant logic from our workflow of interest into that structure. The first step for that is to make our original workflow composable. @@ -512,7 +512,8 @@ That is going to be defined in the parent workflow, also called the **entrypoint ### 2.6. Make a dummy entrypoint workflow -We can make a dummy entrypoint workflow to test the composable workflow without yet having to deal with the rest of the complexity of the nf-core pipeline scaffold. +Before integrating our composable workflow into the complex nf-core scaffold, let's verify it works correctly with a simple test harness. +We can make a dummy entrypoint workflow to test the composable workflow in isolation. Create a blank file named `main.nf` in the same`original-hello` directory. @@ -547,7 +548,7 @@ workflow { There are two important observations to make here: -- The syntax for calling the imported workflow (line 16) is essentially the same as the syntax for calling modules. +- The syntax for calling the imported workflow is essentially the same as the syntax for calling modules. - Everything that is related to pulling the inputs into the workflow (input parameter and channel construction) is now declared in this parent workflow. !!! note @@ -595,10 +596,6 @@ This means we've successfully upgraded our HELLO workflow to be composable. You know how to make a workflow composable by giving it a name and adding `take`, `main` and `emit` statements, and how to call it from an entrypoint workflow. -!!! note - - If you're interested in digging deeper into options for composing workflows of workflows, check out the [Workflow of Workflows](https://training.nextflow.io/latest/side_quests/workflows_of_workflows) (a.k.a. WoW) side quest. - ### What's next? Learn how to graft a basic composable workflow onto the nf-core scaffold. @@ -885,10 +882,13 @@ You know how to fit the core pieces of a composable workflow into an nf-core pla ### What's next? -Learn how to adapt how the inputs are handle in the nf-core pipeline scaffold. +Learn how to adapt how the inputs are handled in the nf-core pipeline scaffold. --- +Now that we've successfully integrated our workflow logic into the nf-core scaffold, we need to address one more critical piece: ensuring that our input data is processed correctly. +The nf-core template comes with sophisticated input handling designed for complex genomics datasets, but we can adapt it to work with our simpler greeting data. + ## 4. Adapt the input handling Now that the HELLO workflow is ready to go, we need to adapt how the inputs are handled to make sure our `greetings.csv` will be handled appropriately. @@ -1116,7 +1116,7 @@ For now, we're focused on keeping it as simple as possible to get to something w Speaking of test data and parameters, let's update the test profile for this pipeline to use the `greetings.csv` mini-samplesheet instead of the example samplesheet provided in the template. -Under `core-hello/config`, we find two templated test profiles: `test.config` and `test_full.config`, which are meant to test a small data sample and a full-size one. +Under `core-hello/conf`, we find two templated test profiles: `test.config` and `test_full.config`, which are meant to test a small data sample and a full-size one. Given the purpose of our pipeline, there's not really a point to setting up a full-size test profile, so feel free to ignore or delete `test_full.config`. We're going to focus on setting up `test.config` to run on our `greetings.csv` file with a few default parameters. From 3b7dd22c4bc78800593bbaad537c586d7ee25f25 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 15:46:00 +0000 Subject: [PATCH 016/113] fix workflow listing --- docs/hello_nf-core/02_rewrite_hello.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index d56615ed3e..644241b6a8 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -117,8 +117,7 @@ core-hello/ │ │ ├── main.function.nf.test │ │ ├── main.function.nf.test.snap │ │ ├── main.workflow.nf.test -│ │ ├── nextflow.config -│ │ └── tags.yml +│ │ └── nextflow.config │ ├── utils_nfcore_pipeline │ │ ├── main.nf │ │ ├── meta.yml @@ -127,8 +126,7 @@ core-hello/ │ │ ├── main.function.nf.test.snap │ │ ├── main.workflow.nf.test │ │ ├── main.workflow.nf.test.snap -│ │ ├── nextflow.config -│ │ └── tags.yml +│ │ └── nextflow.config │ └── utils_nfschema_plugin │ ├── main.nf │ ├── meta.yml @@ -139,7 +137,7 @@ core-hello/ └── workflows └── hello.nf -14 directories, 36 files +14 directories, 34 files ``` That's a lot of files! From c0de862a24c8dddff1417c78436b5280bb70a18c Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 15:47:28 +0000 Subject: [PATCH 017/113] Update first run output --- docs/hello_nf-core/02_rewrite_hello.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index 644241b6a8..a3d9b64b4d 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -160,9 +160,9 @@ nextflow run ./core-hello -profile docker,test --outdir core-hello-results ```console title="Output" N E X T F L O W ~ version 25.04.3 -Launching `core-hello/main.nf` [special_ride] DSL2 - revision: c31b966b36 +Launching `./core-hello/main.nf` [insane_davinci] DSL2 - revision: b9e9b3b8de -Downloading plugin nf-schema@2.2.0 +Downloading plugin nf-schema@2.5.1 Input/output options input : https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/samplesheet/samplesheet_test_illumina_amplicon.csv outdir : core-hello-results @@ -172,21 +172,21 @@ Institutional config options config_profile_description: Minimal test dataset to check pipeline function Generic options - trace_report_suffix : 2025-05-14_10-01-18 + trace_report_suffix : 2025-10-30_15-45-16 Core Nextflow options - runName : special_ride + runName : insane_davinci containerEngine : docker launchDir : /workspaces/training/hello-nf-core workDir : /workspaces/training/hello-nf-core/work projectDir : /workspaces/training/hello-nf-core/core-hello userName : root profile : docker,test - configFiles : + configFiles : /workspaces/training/hello-nf-core/core-hello/nextflow.config !! Only displaying parameters that differ from the pipeline defaults !! ------------------------------------------------------ --[core/hello] Pipeline completed successfully +-[core/hello] Pipeline completed successfully- ``` This shows you that all the basic wiring is in place. From 8d33f7c366ddf7cacbbb7d08a53c9b42a1ec0990 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 17:07:28 +0000 Subject: [PATCH 018/113] Clarify section 3 transition in nf-core Part 2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Section 3 was confusing because: - No clear transition from testing composable workflow back to nf-core scaffold - Ambiguous references to "original workflow" without specifying source/destination Improvements: - Add explicit transition at section 3 start explaining return to scaffold - Clarify we're integrating work from section 2 - Make FROM/TO direction clear in section 3.1 (original-hello → core-hello) - Specify directory paths explicitly to reduce ambiguity 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/02_rewrite_hello.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index a3d9b64b4d..0a2546e77f 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -602,10 +602,13 @@ Learn how to graft a basic composable workflow onto the nf-core scaffold. ## 3. Fit the updated workflow logic into the placeholder workflow -This is the current content of the `HELLO` workflow in `core-hello/workflows/hello.nf`. +Now that we've verified our composable workflow works correctly, let's return to the nf-core pipeline scaffold we created in section 1. +We're going to integrate the composable workflow we just developed into the nf-core template structure. + +This is the current content of the `HELLO` workflow in `core-hello/workflows/hello.nf` (the nf-core scaffold). Overall this code does very little aside from some housekeeping that has to do with capturing the version of any software tools that get run in the pipeline. -We need to add the relevant code from the version of the original workflow that we made composable. +We need to add the relevant code from the composable version of the original workflow that we developed in section 2. ```groovy title="core-hello/workflows/hello.nf" linenums="1" /* @@ -667,9 +670,10 @@ We're going to tackle this in the following stages: ### 3.1. Copy the modules and set up module imports -In the original workflow, the four processes are stored in modules, so we need to copy those over to this new project (into a new `local` directory) and add import statements to the workflow file. +The four processes from our Hello Nextflow workflow are stored as modules in `original-hello/modules/`. +We need to copy those modules into the nf-core project structure (under `core-hello/modules/local/`) and add import statements to the nf-core workflow file. -First let's copy the module files over: +First let's copy the module files from `original-hello/` to `core-hello/`: ```bash mkdir -p core-hello/modules/local/ From c8250d2b8735647a1b0754c6c65fa92bee868a3b Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 17:37:42 +0000 Subject: [PATCH 019/113] Add a highlight --- docs/hello_nf-core/02_rewrite_hello.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index 0a2546e77f..bd6a8010f4 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -785,7 +785,7 @@ There is already some code in there that has to do with capturing the versions o === "After" - ```groovy title="core-hello/workflows/hello.nf" linenums="23" + ```groovy title="core-hello/workflows/hello.nf" linenums="23" hl_lines="3-16" main: From dbe6611ea58dad1f43cabd5a3b567a78ead80c6c Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 17:44:21 +0000 Subject: [PATCH 020/113] Reorder section 3.3 to show proper version channel pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move ch_versions initialization before workflow logic rather than after. This reflects the correct pattern where: 1. Initialize empty versions channel 2. Run workflow processes (which would emit versions in real pipeline) 3. Collate versions at the end Add explanation that this ordering allows processes to mix their version outputs into ch_versions as the workflow runs, which is the intended pattern even though we're not implementing version capture yet. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/02_rewrite_hello.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index bd6a8010f4..e256a009f4 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -781,14 +781,16 @@ As a reminder, this is the relevant code in the original workflow, which didn't We need to copy this code into the new version of the workflow (minus the `main:` keyword which is already there). -There is already some code in there that has to do with capturing the versions of the tools that get run by the workflow. We're going to leave that alone for now (we'll deal with the tool versions later) and simply insert our code right after the `main:` line. +There is already some code in there that has to do with capturing the versions of the tools that get run by the workflow. We're going to leave that alone for now (we'll deal with the tool versions later). We'll keep the `ch_versions = channel.empty()` initialization at the top, then insert our workflow logic, keeping the version collation code at the end. This ordering makes sense because in a real pipeline, the processes would emit version information that would be mixed into the `ch_versions` channel as the workflow runs. === "After" - ```groovy title="core-hello/workflows/hello.nf" linenums="23" hl_lines="3-16" + ```groovy title="core-hello/workflows/hello.nf" linenums="23" hl_lines="5-18" main: + ch_versions = channel.empty() + // emit a greeting sayHello(greeting_ch) @@ -804,8 +806,6 @@ There is already some code in there that has to do with capturing the versions o // generate ASCII art of the greetings with cowpy cowpy(collectGreetings.out.outfile, params.character) - ch_versions = channel.empty() - // // Collate and save software versions // From 65e7de7f9912121ce0fd47a7e50d8962592d964a Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 17:48:47 +0000 Subject: [PATCH 021/113] fix line num --- docs/hello_nf-core/02_rewrite_hello.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index e256a009f4..85c6799792 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -1000,7 +1000,7 @@ A bit of poking around reveals that the input handling is done by the `PIPELINE_ If we open up `core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf` and scroll down, we come to this chunk of code: -```groovy title="core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf" linenums="64" +```groovy title="core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf" linenums="76" // // Create channel from input file provided through params.input // From 754f28ec4d7ed3ba336b1871cf62b4202cbbe074 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 17:53:45 +0000 Subject: [PATCH 022/113] Fix highlights, indents --- docs/hello_nf-core/02_rewrite_hello.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index 85c6799792..3a1b220add 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -1056,21 +1056,22 @@ As a reminder, this is what the channel construction looked like (as seen in the ```groovy title="solutions/composable-hello/main.nf" linenums="10" hl_lines="4" // create a channel for inputs from a CSV file greeting_ch = channel.fromPath(params.greeting) - .splitCsv() - .map { line -> line[0] } + .splitCsv() + .map { line -> line[0] } ``` So we just need to plug that into the initialisation workflow, with minor changes: we update the channel name from `greeting_ch` to `ch_samplesheet`, and the parameter name from `params.greeting` to `params.input` (see highlighted line). === "After" - ```groovy title="core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf" linenums="64" hl_lines="4" + ```groovy title="core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf" linenums="76" hl_lines="5-7" // // Create channel from input file provided through params.input // + ch_samplesheet = channel.fromPath(params.input) - .splitCsv() - .map { line -> line[0] } + .splitCsv() + .map { line -> line[0] } emit: samplesheet = ch_samplesheet @@ -1079,7 +1080,7 @@ So we just need to plug that into the initialisation workflow, with minor change === "Before" - ```groovy title="core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf" linenums="64" + ```groovy title="core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf" linenums="76" hl_lines="4-26" // // Create channel from input file provided through params.input // From 2d665aff79ac7ea225f03de6829cfd77c9f94ef4 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 18:01:52 +0000 Subject: [PATCH 023/113] More highlight fixes --- docs/hello_nf-core/02_rewrite_hello.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index 3a1b220add..5df4b6eec3 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -1080,7 +1080,7 @@ So we just need to plug that into the initialisation workflow, with minor change === "Before" - ```groovy title="core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf" linenums="76" hl_lines="4-26" + ```groovy title="core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf" linenums="76" hl_lines="5-23" // // Create channel from input file provided through params.input // @@ -1134,7 +1134,7 @@ Now we can update the `test.config` file as follows: === "After" - ```groovy title="core-hello/conf/test.config" linenums="21" hl_lines="5-10" + ```groovy title="core-hello/conf/test.config" linenums="21" hl_lines="6-10" params { config_profile_name = 'Test profile' config_profile_description = 'Minimal test dataset to check pipeline function' @@ -1150,7 +1150,7 @@ Now we can update the `test.config` file as follows: === "Before" - ```groovy title="core-hello/conf/test.config" linenums="21" + ```groovy title="core-hello/conf/test.config" linenums="21" hl_lines="6-8" params { config_profile_name = 'Test profile' config_profile_description = 'Minimal test dataset to check pipeline function' From 3bd54c5c1a8abf51ff66abb5a89abb00807f5f51 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 18:03:31 +0000 Subject: [PATCH 024/113] Fix indents --- docs/hello_nf-core/02_rewrite_hello.md | 36 +++++++++++++------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index 5df4b6eec3..1801cd19a8 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -1135,31 +1135,31 @@ Now we can update the `test.config` file as follows: === "After" ```groovy title="core-hello/conf/test.config" linenums="21" hl_lines="6-10" - params { - config_profile_name = 'Test profile' - config_profile_description = 'Minimal test dataset to check pipeline function' + params { + config_profile_name = 'Test profile' + config_profile_description = 'Minimal test dataset to check pipeline function' - // Input data - input = "${projectDir}/assets/greetings.csv" + // Input data + input = "${projectDir}/assets/greetings.csv" - // Other parameters - batch = 'test' - character = 'tux' - } + // Other parameters + batch = 'test' + character = 'tux' + } ``` === "Before" ```groovy title="core-hello/conf/test.config" linenums="21" hl_lines="6-8" - params { - config_profile_name = 'Test profile' - config_profile_description = 'Minimal test dataset to check pipeline function' - - // Input data - // TODO nf-core: Specify the paths to your test data on nf-core/test-datasets - // TODO nf-core: Give any required params for the test so that command line flags are not needed - input = params.pipelines_testdata_base_path + 'viralrecon/samplesheet/samplesheet_test_illumina_amplicon.csv' - } + params { + config_profile_name = 'Test profile' + config_profile_description = 'Minimal test dataset to check pipeline function' + + // Input data + // TODO nf-core: Specify the paths to your test data on nf-core/test-datasets + // TODO nf-core: Give any required params for the test so that command line flags are not needed + input = params.pipelines_testdata_base_path + 'viralrecon/samplesheet/samplesheet_test_illumina_amplicon.csv' + } ``` Key points: From 1d9dec3b763709320964c7308387c561df2597ca Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 18:05:28 +0000 Subject: [PATCH 025/113] Add missing highlights --- docs/hello_nf-core/02_rewrite_hello.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index 1801cd19a8..0b6a115021 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -1172,7 +1172,7 @@ And while we're at it, let's lower the default resource limitations: === "After" - ```groovy title="core-hello/conf/test.config" linenums="13" + ```groovy title="core-hello/conf/test.config" linenums="13" hl_lines="3 4" process { resourceLimits = [ cpus: 2, @@ -1184,7 +1184,7 @@ And while we're at it, let's lower the default resource limitations: === "Before" - ```groovy title="core-hello/conf/test.config" linenums="13" + ```groovy title="core-hello/conf/test.config" linenums="13" hl_lines="3 4" process { resourceLimits = [ cpus: 4, From 85976941e6dc17e7da9a7f6b26a0620045165518 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 18:10:04 +0000 Subject: [PATCH 026/113] Clarify .view line removal in section 3.3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make explicit that when copying workflow logic to nf-core scaffold, learners should: - Omit the `main:` keyword (already present) - Remove the `.view` line (console output not needed in scaffold) Update the "After" code block to show the workflow without the .view line, matching the instruction. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/02_rewrite_hello.md | 27 +++++++++++++------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index 0b6a115021..975f62ae3d 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -779,13 +779,15 @@ As a reminder, this is the relevant code in the original workflow, which didn't cowpy(collectGreetings.out.outfile, params.character) ``` -We need to copy this code into the new version of the workflow (minus the `main:` keyword which is already there). +We need to copy this code into the new version of the workflow, with a few modifications: +- Omit the `main:` keyword (it's already there) +- Remove the `.view` line (line 776 above) - this was just for console output in the standalone version There is already some code in there that has to do with capturing the versions of the tools that get run by the workflow. We're going to leave that alone for now (we'll deal with the tool versions later). We'll keep the `ch_versions = channel.empty()` initialization at the top, then insert our workflow logic, keeping the version collation code at the end. This ordering makes sense because in a real pipeline, the processes would emit version information that would be mixed into the `ch_versions` channel as the workflow runs. === "After" - ```groovy title="core-hello/workflows/hello.nf" linenums="23" hl_lines="5-18" + ```groovy title="core-hello/workflows/hello.nf" linenums="23" hl_lines="5-16" main: @@ -800,9 +802,6 @@ There is already some code in there that has to do with capturing the versions o // collect all the greetings into one file collectGreetings(convertToUpper.out.collect(), params.batch) - // emit a message about the size of the batch - collectGreetings.out.count.view { "There were $it greetings in this batch" } - // generate ASCII art of the greetings with cowpy cowpy(collectGreetings.out.outfile, params.character) @@ -1210,10 +1209,10 @@ If you've done all of the modifications correctly, it should run to completion. ```console title="Output" N E X T F L O W ~ version 25.04.3 -Launching `core-hello/main.nf` [agitated_noyce] DSL2 - revision: c31b966b36 +Launching `core-hello/main.nf` [small_torvalds] DSL2 - revision: b9e9b3b8de Input/output options - input : core-hello/assets/greetings.csv + input : /workspaces/training/hello-nf-core/core-hello/assets/greetings.csv outdir : core-hello-results Institutional config options @@ -1222,25 +1221,25 @@ Institutional config options Generic options validate_params : false - trace_report_suffix : 2025-05-14_11-10-22 + trace_report_suffix : 2025-10-30_18-05-47 Core Nextflow options - runName : agitated_noyce + runName : small_torvalds containerEngine : docker launchDir : /workspaces/training/hello-nf-core workDir : /workspaces/training/hello-nf-core/work projectDir : /workspaces/training/hello-nf-core/core-hello userName : root profile : test,docker - configFiles : + configFiles : /workspaces/training/hello-nf-core/core-hello/nextflow.config !! Only displaying parameters that differ from the pipeline defaults !! ------------------------------------------------------ executor > local (8) -[d6/b59dca] CORE_HELLO:HELLO:sayHello (1) | 3 of 3 ✔ -[0b/42f9a1] CORE_HELLO:HELLO:convertToUpper (2) | 3 of 3 ✔ -[73/bec621] CORE_HELLO:HELLO:collectGreetings | 1 of 1 ✔ -[3f/e0a67a] CORE_HELLO:HELLO:cowpy | 1 of 1 ✔ +[da/fe2e20] COR…LLO:sayHello (1) | 3 of 3 ✔ +[f5/4e47cf] COR…nvertToUpper (2) | 3 of 3 ✔ +[22/61caea] COR…collectGreetings | 1 of 1 ✔ +[a8/de5051] COR…ELLO:HELLO:cowpy | 1 of 1 ✔ -[core/hello] Pipeline completed successfully- ``` From 6950066d20d7af4ac04eaf3e73f867420dbaf3a2 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 18:14:00 +0000 Subject: [PATCH 027/113] Remove redundancy at section 4 start MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The transition paragraph and first line of section 4 were saying the same thing. Consolidated into single transition paragraph that flows directly into section heading and first subsection. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/02_rewrite_hello.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index 975f62ae3d..d964b5c894 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -888,12 +888,10 @@ Learn how to adapt how the inputs are handled in the nf-core pipeline scaffold. --- Now that we've successfully integrated our workflow logic into the nf-core scaffold, we need to address one more critical piece: ensuring that our input data is processed correctly. -The nf-core template comes with sophisticated input handling designed for complex genomics datasets, but we can adapt it to work with our simpler greeting data. +The nf-core template comes with sophisticated input handling designed for complex genomics datasets, but we can adapt it to work with our simpler `greetings.csv` file. ## 4. Adapt the input handling -Now that the HELLO workflow is ready to go, we need to adapt how the inputs are handled to make sure our `greetings.csv` will be handled appropriately. - ### 4.1. Identify where inputs are handled The first step is to figure out where the input handling is done. From 8f612633de03398099e3ed3ba5a6f1ecffb38bc2 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 18:17:10 +0000 Subject: [PATCH 028/113] Update solution --- .../solutions/core-hello-part2/README.md | 4 +- .../core-hello-part2/assets/schema_input.json | 4 +- .../core-hello-part2/conf/base.config | 6 +- .../core-hello-part2/conf/test.config | 5 +- .../solutions/core-hello-part2/docs/usage.md | 2 +- .../solutions/core-hello-part2/main.nf | 5 +- .../solutions/core-hello-part2/modules.json | 6 +- .../modules/local/collectGreetings.nf | 2 + .../core-hello-part2/nextflow.config | 46 +++++++++------ .../core-hello-part2/nextflow_schema.json | 14 ++++- .../local/utils_nfcore_hello_pipeline/main.nf | 21 +++++-- .../tests/main.function.nf.test.snap | 2 +- .../utils_nextflow_pipeline/tests/tags.yml | 2 - .../tests/main.function.nf.test.snap | 2 +- .../tests/main.workflow.nf.test.snap | 2 +- .../utils_nfcore_pipeline/tests/tags.yml | 2 - .../nf-core/utils_nfschema_plugin/main.nf | 41 ++++++++++++-- .../utils_nfschema_plugin/tests/main.nf.test | 56 +++++++++++++++++++ .../tests/nextflow.config | 2 +- .../core-hello-part2/workflows/hello.nf | 5 +- 20 files changed, 180 insertions(+), 49 deletions(-) delete mode 100644 hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml delete mode 100644 hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml diff --git a/hello-nf-core/solutions/core-hello-part2/README.md b/hello-nf-core/solutions/core-hello-part2/README.md index 0a533c4c4b..94844f6c5e 100644 --- a/hello-nf-core/solutions/core-hello-part2/README.md +++ b/hello-nf-core/solutions/core-hello-part2/README.md @@ -11,7 +11,7 @@ --> + workflows use the "tube map" design for that. See https://nf-co.re/docs/guidelines/graphic_design/workflow_diagrams#examples for examples. --> ## Usage @@ -51,7 +51,7 @@ nextflow run core/hello \ ## Credits -core/hello was originally written by GG. +core/hello was originally written by pinin4fjords. We thank the following people for their extensive assistance in the development of this pipeline: diff --git a/hello-nf-core/solutions/core-hello-part2/assets/schema_input.json b/hello-nf-core/solutions/core-hello-part2/assets/schema_input.json index bc0261f329..5cb7458161 100644 --- a/hello-nf-core/solutions/core-hello-part2/assets/schema_input.json +++ b/hello-nf-core/solutions/core-hello-part2/assets/schema_input.json @@ -17,14 +17,14 @@ "type": "string", "format": "file-path", "exists": true, - "pattern": "^\\S+\\.f(ast)?q\\.gz$", + "pattern": "^([\\S\\s]*\\/)?[^\\s\\/]+\\.f(ast)?q\\.gz$", "errorMessage": "FastQ file for reads 1 must be provided, cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" }, "fastq_2": { "type": "string", "format": "file-path", "exists": true, - "pattern": "^\\S+\\.f(ast)?q\\.gz$", + "pattern": "^([\\S\\s]*\\/)?[^\\s\\/]+\\.f(ast)?q\\.gz$", "errorMessage": "FastQ file for reads 2 cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" } }, diff --git a/hello-nf-core/solutions/core-hello-part2/conf/base.config b/hello-nf-core/solutions/core-hello-part2/conf/base.config index 1abcd9876f..e0fe40762f 100644 --- a/hello-nf-core/solutions/core-hello-part2/conf/base.config +++ b/hello-nf-core/solutions/core-hello-part2/conf/base.config @@ -15,7 +15,7 @@ process { memory = { 6.GB * task.attempt } time = { 4.h * task.attempt } - errorStrategy = { task.exitStatus in ((130..145) + 104) ? 'retry' : 'finish' } + errorStrategy = { task.exitStatus in ((130..145) + 104 + 175) ? 'retry' : 'finish' } maxRetries = 1 maxErrors = '-1' @@ -59,4 +59,8 @@ process { errorStrategy = 'retry' maxRetries = 2 } + withLabel: process_gpu { + ext.use_gpu = { workflow.profile.contains('gpu') } + accelerator = { workflow.profile.contains('gpu') ? 1 : null } + } } diff --git a/hello-nf-core/solutions/core-hello-part2/conf/test.config b/hello-nf-core/solutions/core-hello-part2/conf/test.config index f82761298d..13ecf2ad4b 100644 --- a/hello-nf-core/solutions/core-hello-part2/conf/test.config +++ b/hello-nf-core/solutions/core-hello-part2/conf/test.config @@ -12,8 +12,9 @@ process { resourceLimits = [ - cpus: 1, - memory: '1.GB' + cpus: 2, + memory: '4.GB', + time: '1.h' ] } diff --git a/hello-nf-core/solutions/core-hello-part2/docs/usage.md b/hello-nf-core/solutions/core-hello-part2/docs/usage.md index 78b55f9afe..bfbc37ab42 100644 --- a/hello-nf-core/solutions/core-hello-part2/docs/usage.md +++ b/hello-nf-core/solutions/core-hello-part2/docs/usage.md @@ -146,7 +146,7 @@ If `-profile` is not specified, the pipeline will run locally and expect all sof - `shifter` - A generic configuration profile to be used with [Shifter](https://nersc.gitlab.io/development/shifter/how-to-use/) - `charliecloud` - - A generic configuration profile to be used with [Charliecloud](https://hpc.github.io/charliecloud/) + - A generic configuration profile to be used with [Charliecloud](https://charliecloud.io/) - `apptainer` - A generic configuration profile to be used with [Apptainer](https://apptainer.org/) - `wave` diff --git a/hello-nf-core/solutions/core-hello-part2/main.nf b/hello-nf-core/solutions/core-hello-part2/main.nf index f72a236660..eb8d91361f 100644 --- a/hello-nf-core/solutions/core-hello-part2/main.nf +++ b/hello-nf-core/solutions/core-hello-part2/main.nf @@ -57,7 +57,10 @@ workflow { params.monochrome_logs, args, params.outdir, - params.input + params.input, + params.help, + params.help_full, + params.show_hidden ) // diff --git a/hello-nf-core/solutions/core-hello-part2/modules.json b/hello-nf-core/solutions/core-hello-part2/modules.json index e36947ce00..8ca44d9da5 100644 --- a/hello-nf-core/solutions/core-hello-part2/modules.json +++ b/hello-nf-core/solutions/core-hello-part2/modules.json @@ -10,17 +10,17 @@ "nf-core": { "utils_nextflow_pipeline": { "branch": "master", - "git_sha": "c2b22d85f30a706a3073387f30380704fcae013b", + "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", "installed_by": ["subworkflows"] }, "utils_nfcore_pipeline": { "branch": "master", - "git_sha": "51ae5406a030d4da1e49e4dab49756844fdd6c7a", + "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", "installed_by": ["subworkflows"] }, "utils_nfschema_plugin": { "branch": "master", - "git_sha": "2fd2cd6d0e7b273747f32e465fdc6bcc3ae0814e", + "git_sha": "4b406a74dc0449c0401ed87d5bfff4252fd277fd", "installed_by": ["subworkflows"] } } diff --git a/hello-nf-core/solutions/core-hello-part2/modules/local/collectGreetings.nf b/hello-nf-core/solutions/core-hello-part2/modules/local/collectGreetings.nf index 0274fec86f..849bba4b6e 100644 --- a/hello-nf-core/solutions/core-hello-part2/modules/local/collectGreetings.nf +++ b/hello-nf-core/solutions/core-hello-part2/modules/local/collectGreetings.nf @@ -11,8 +11,10 @@ process collectGreetings { output: path "COLLECTED-${batch_name}-output.txt" , emit: outfile + val count_greetings , emit: count script: + count_greetings = input_files.size() """ cat ${input_files} > 'COLLECTED-${batch_name}-output.txt' """ diff --git a/hello-nf-core/solutions/core-hello-part2/nextflow.config b/hello-nf-core/solutions/core-hello-part2/nextflow.config index d633adb989..b59ba2175d 100644 --- a/hello-nf-core/solutions/core-hello-part2/nextflow.config +++ b/hello-nf-core/solutions/core-hello-part2/nextflow.config @@ -22,7 +22,9 @@ params { show_hidden = false version = false pipelines_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/' - trace_report_suffix = new java.util.Date().format( 'yyyy-MM-dd_HH-mm-ss')// Config options + trace_report_suffix = new java.util.Date().format( 'yyyy-MM-dd_HH-mm-ss') + + // Config options config_profile_name = null config_profile_description = null @@ -75,7 +77,18 @@ profiles { apptainer.enabled = false docker.runOptions = '-u $(id -u):$(id -g)' } - arm { + arm64 { + process.arch = 'arm64' + // TODO https://github.com/nf-core/modules/issues/6694 + // For now if you're using arm64 you have to use wave for the sake of the maintainers + // wave profile + apptainer.ociAutoPull = true + singularity.ociAutoPull = true + wave.enabled = true + wave.freeze = true + wave.strategy = 'conda,container' + } + emulate_amd64 { docker.runOptions = '-u $(id -u):$(id -g) --platform=linux/amd64' } singularity { @@ -132,16 +145,24 @@ profiles { wave.freeze = true wave.strategy = 'conda,container' } + gpu { + docker.runOptions = '-u $(id -u):$(id -g) --gpus all' + apptainer.runOptions = '--nv' + singularity.runOptions = '--nv' + } test { includeConfig 'conf/test.config' } test_full { includeConfig 'conf/test_full.config' } } +// Load nf-core custom profiles from different institutions + +// If params.custom_config_base is set AND either the NXF_OFFLINE environment variable is not set or params.custom_config_base is a local path, the nfcore_custom.config file from the specified base path is included. +// Load core/hello custom profiles from different institutions. +includeConfig params.custom_config_base && (!System.getenv('NXF_OFFLINE') || !params.custom_config_base.startsWith('http')) ? "${params.custom_config_base}/nfcore_custom.config" : "/dev/null" -// Load nf-core custom profiles from different Institutions -includeConfig !System.getenv('NXF_OFFLINE') && params.custom_config_base ? "${params.custom_config_base}/nfcore_custom.config" : "/dev/null" // Load core/hello custom profiles from different institutions. // TODO nf-core: Optionally, you can add a pipeline-specific nf-core config at https://github.com/nf-core/configs -// includeConfig !System.getenv('NXF_OFFLINE') && params.custom_config_base ? "${params.custom_config_base}/pipeline/hello.config" : "/dev/null" +// includeConfig params.custom_config_base && (!System.getenv('NXF_OFFLINE') || !params.custom_config_base.startsWith('http')) ? "${params.custom_config_base}/pipeline/hello.config" : "/dev/null" // Set default registry for Apptainer, Docker, Podman, Charliecloud and Singularity independent of -profile // Will not be used unless Apptainer / Docker / Podman / Charliecloud / Singularity are enabled @@ -197,11 +218,10 @@ dag { manifest { name = 'core/hello' - author = """GG""" // The author field is deprecated from Nextflow version 24.10.0, use contributors instead contributors = [ // TODO nf-core: Update the field with the details of the contributors to your pipeline. New with Nextflow version 24.10.0 [ - name: 'GG', + name: 'pinin4fjords', affiliation: '', email: '', github: '', @@ -210,28 +230,22 @@ manifest { ], ] homePage = 'https://github.com/core/hello' - description = """basic nf-core style version of Hello Nextflow""" + description = """A basic nf-core style version of Hello Nextflow""" mainScript = 'main.nf' defaultBranch = 'main' - nextflowVersion = '!>=24.04.2' + nextflowVersion = '!>=25.04.0' version = '1.0.0dev' doi = '' } // Nextflow plugins plugins { - id 'nf-schema@2.2.0' // Validation of pipeline parameters and creation of an input channel from a sample sheet + id 'nf-schema@2.5.1' // Validation of pipeline parameters and creation of an input channel from a sample sheet } validation { defaultIgnoreParams = ["genomes"] monochromeLogs = params.monochrome_logs - help { - enabled = true - command = "nextflow run core/hello -profile --input samplesheet.csv --outdir " - fullParameter = "help_full" - showHiddenParameter = "show_hidden" - } } // Load modules.config for DSL2 module specific options diff --git a/hello-nf-core/solutions/core-hello-part2/nextflow_schema.json b/hello-nf-core/solutions/core-hello-part2/nextflow_schema.json index 5ee5ec357f..fc18ba7998 100644 --- a/hello-nf-core/solutions/core-hello-part2/nextflow_schema.json +++ b/hello-nf-core/solutions/core-hello-part2/nextflow_schema.json @@ -2,7 +2,7 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://raw.githubusercontent.com/core/hello/main/nextflow_schema.json", "title": "core/hello pipeline parameters", - "description": "basic nf-core style version of Hello Nextflow", + "description": "A basic nf-core style version of Hello Nextflow", "type": "object", "$defs": { "input_output_options": { @@ -133,6 +133,18 @@ "fa_icon": "far calendar", "description": "Suffix to add to the trace report filename. Default is the date and time in the format yyyy-MM-dd_HH-mm-ss.", "hidden": true + }, + "help": { + "type": ["boolean", "string"], + "description": "Display the help message." + }, + "help_full": { + "type": "boolean", + "description": "Display the full detailed help message." + }, + "show_hidden": { + "type": "boolean", + "description": "Display hidden parameters in the help message (only works when --help or --help_full are provided)." } } } diff --git a/hello-nf-core/solutions/core-hello-part2/subworkflows/local/utils_nfcore_hello_pipeline/main.nf b/hello-nf-core/solutions/core-hello-part2/subworkflows/local/utils_nfcore_hello_pipeline/main.nf index 53ba38fae8..93c9f874cc 100644 --- a/hello-nf-core/solutions/core-hello-part2/subworkflows/local/utils_nfcore_hello_pipeline/main.nf +++ b/hello-nf-core/solutions/core-hello-part2/subworkflows/local/utils_nfcore_hello_pipeline/main.nf @@ -11,6 +11,7 @@ include { UTILS_NFSCHEMA_PLUGIN } from '../../nf-core/utils_nfschema_plugin' include { paramsSummaryMap } from 'plugin/nf-schema' include { samplesheetToList } from 'plugin/nf-schema' +include { paramsHelp } from 'plugin/nf-schema' include { completionSummary } from '../../nf-core/utils_nfcore_pipeline' include { UTILS_NFCORE_PIPELINE } from '../../nf-core/utils_nfcore_pipeline' include { UTILS_NEXTFLOW_PIPELINE } from '../../nf-core/utils_nextflow_pipeline' @@ -30,6 +31,9 @@ workflow PIPELINE_INITIALISATION { nextflow_cli_args // array: List of positional nextflow CLI args outdir // string: The output directory where the results will be saved input // string: Path to input samplesheet + help // boolean: Display help message and exit + help_full // boolean: Show the full help message + show_hidden // boolean: Show hidden parameters in the help message main: @@ -48,10 +52,18 @@ workflow PIPELINE_INITIALISATION { // // Validate parameters and generate parameter summary to stdout // + command = "nextflow run ${workflow.manifest.name} -profile --input samplesheet.csv --outdir " + UTILS_NFSCHEMA_PLUGIN ( workflow, validate_params, - null + null, + help, + help_full, + show_hidden, + "", + "", + command ) // @@ -64,9 +76,10 @@ workflow PIPELINE_INITIALISATION { // // Create channel from input file provided through params.input // - ch_samplesheet = Channel.fromPath(params.input) - .splitCsv() - .map { line -> line[0] } + + ch_samplesheet = channel.fromPath(params.input) + .splitCsv() + .map { line -> line[0] } emit: samplesheet = ch_samplesheet diff --git a/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap b/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap index 846287c417..e3f0baf473 100644 --- a/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap +++ b/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap @@ -17,4 +17,4 @@ }, "timestamp": "2024-02-28T12:02:12.425833" } -} +} \ No newline at end of file diff --git a/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml b/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml deleted file mode 100644 index f84761125a..0000000000 --- a/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml +++ /dev/null @@ -1,2 +0,0 @@ -subworkflows/utils_nextflow_pipeline: - - subworkflows/nf-core/utils_nextflow_pipeline/** diff --git a/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap b/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap index b13b311213..02c6701413 100644 --- a/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap +++ b/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap @@ -133,4 +133,4 @@ }, "timestamp": "2024-02-28T12:03:21.714424" } -} +} \ No newline at end of file diff --git a/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap b/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap index 84ee1e1d1e..859d1030fb 100644 --- a/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap +++ b/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap @@ -16,4 +16,4 @@ }, "timestamp": "2024-02-28T12:03:25.726491" } -} +} \ No newline at end of file diff --git a/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml b/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml deleted file mode 100644 index ac8523c9a2..0000000000 --- a/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml +++ /dev/null @@ -1,2 +0,0 @@ -subworkflows/utils_nfcore_pipeline: - - subworkflows/nf-core/utils_nfcore_pipeline/** diff --git a/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfschema_plugin/main.nf b/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfschema_plugin/main.nf index 93de2a5245..ee4738c8d1 100644 --- a/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfschema_plugin/main.nf +++ b/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfschema_plugin/main.nf @@ -4,6 +4,7 @@ include { paramsSummaryLog } from 'plugin/nf-schema' include { validateParameters } from 'plugin/nf-schema' +include { paramsHelp } from 'plugin/nf-schema' workflow UTILS_NFSCHEMA_PLUGIN { @@ -15,31 +16,59 @@ workflow UTILS_NFSCHEMA_PLUGIN { // when this input is empty it will automatically use the configured schema or // "${projectDir}/nextflow_schema.json" as default. This input should not be empty // for meta pipelines + help // boolean: show help message + help_full // boolean: show full help message + show_hidden // boolean: show hidden parameters in help message + before_text // string: text to show before the help message and parameters summary + after_text // string: text to show after the help message and parameters summary + command // string: an example command of the pipeline main: + if(help || help_full) { + help_options = [ + beforeText: before_text, + afterText: after_text, + command: command, + showHidden: show_hidden, + fullHelp: help_full, + ] + if(parameters_schema) { + help_options << [parametersSchema: parameters_schema] + } + log.info paramsHelp( + help_options, + params.help instanceof String ? params.help : "", + ) + exit 0 + } + // // Print parameter summary to stdout. This will display the parameters // that differ from the default given in the JSON schema // + + summary_options = [:] if(parameters_schema) { - log.info paramsSummaryLog(input_workflow, parameters_schema:parameters_schema) - } else { - log.info paramsSummaryLog(input_workflow) + summary_options << [parametersSchema: parameters_schema] } + log.info before_text + log.info paramsSummaryLog(summary_options, input_workflow) + log.info after_text // // Validate the parameters using nextflow_schema.json or the schema // given via the validation.parametersSchema configuration option // if(validate_params) { + validateOptions = [:] if(parameters_schema) { - validateParameters(parameters_schema:parameters_schema) - } else { - validateParameters() + validateOptions << [parametersSchema: parameters_schema] } + validateParameters(validateOptions) } emit: dummy_emit = true } + diff --git a/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test b/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test index 8fb3016487..c977917aac 100644 --- a/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test +++ b/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test @@ -25,6 +25,12 @@ nextflow_workflow { input[0] = workflow input[1] = validate_params input[2] = "" + input[3] = false + input[4] = false + input[5] = false + input[6] = "" + input[7] = "" + input[8] = "" """ } } @@ -51,6 +57,12 @@ nextflow_workflow { input[0] = workflow input[1] = validate_params input[2] = "" + input[3] = false + input[4] = false + input[5] = false + input[6] = "" + input[7] = "" + input[8] = "" """ } } @@ -77,6 +89,12 @@ nextflow_workflow { input[0] = workflow input[1] = validate_params input[2] = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + input[3] = false + input[4] = false + input[5] = false + input[6] = "" + input[7] = "" + input[8] = "" """ } } @@ -103,6 +121,12 @@ nextflow_workflow { input[0] = workflow input[1] = validate_params input[2] = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + input[3] = false + input[4] = false + input[5] = false + input[6] = "" + input[7] = "" + input[8] = "" """ } } @@ -114,4 +138,36 @@ nextflow_workflow { ) } } + + test("Should create a help message") { + + when { + + params { + test_data = '' + outdir = null + } + + workflow { + """ + validate_params = true + input[0] = workflow + input[1] = validate_params + input[2] = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + input[3] = true + input[4] = false + input[5] = false + input[6] = "Before" + input[7] = "After" + input[8] = "nextflow run test/test" + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } } diff --git a/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config b/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config index 478fb8a05f..8d8c73718a 100644 --- a/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config +++ b/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config @@ -1,5 +1,5 @@ plugins { - id "nf-schema@2.1.0" + id "nf-schema@2.5.1" } validation { diff --git a/hello-nf-core/solutions/core-hello-part2/workflows/hello.nf b/hello-nf-core/solutions/core-hello-part2/workflows/hello.nf index 3e3af1c2a7..7810b24ae6 100644 --- a/hello-nf-core/solutions/core-hello-part2/workflows/hello.nf +++ b/hello-nf-core/solutions/core-hello-part2/workflows/hello.nf @@ -20,8 +20,11 @@ workflow HELLO { take: ch_samplesheet // channel: samplesheet read in from --input + main: + ch_versions = Channel.empty() + // emit a greeting sayHello(ch_samplesheet) @@ -34,8 +37,6 @@ workflow HELLO { // generate ASCII art of the greetings with cowpy cowpy(collectGreetings.out.outfile, params.character) - ch_versions = Channel.empty() - // // Collate and save software versions // From 521618c50af80a2d99143f980ed85a17451bd4cd Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 18:22:35 +0000 Subject: [PATCH 029/113] Fix linting errors in core-hello-part2 solution files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add missing newlines at end of files: - utils_nextflow_pipeline/tests/main.function.nf.test.snap - utils_nfcore_pipeline/tests/main.function.nf.test.snap - utils_nfcore_pipeline/tests/main.workflow.nf.test.snap Remove extra blank line in: - utils_nfschema_plugin/main.nf 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../utils_nextflow_pipeline/tests/main.function.nf.test.snap | 2 +- .../utils_nfcore_pipeline/tests/main.function.nf.test.snap | 2 +- .../utils_nfcore_pipeline/tests/main.workflow.nf.test.snap | 2 +- .../subworkflows/nf-core/utils_nfschema_plugin/main.nf | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap b/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap index e3f0baf473..846287c417 100644 --- a/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap +++ b/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap @@ -17,4 +17,4 @@ }, "timestamp": "2024-02-28T12:02:12.425833" } -} \ No newline at end of file +} diff --git a/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap b/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap index 02c6701413..b13b311213 100644 --- a/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap +++ b/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap @@ -133,4 +133,4 @@ }, "timestamp": "2024-02-28T12:03:21.714424" } -} \ No newline at end of file +} diff --git a/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap b/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap index 859d1030fb..84ee1e1d1e 100644 --- a/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap +++ b/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap @@ -16,4 +16,4 @@ }, "timestamp": "2024-02-28T12:03:25.726491" } -} \ No newline at end of file +} diff --git a/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfschema_plugin/main.nf b/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfschema_plugin/main.nf index ee4738c8d1..acb3972419 100644 --- a/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfschema_plugin/main.nf +++ b/hello-nf-core/solutions/core-hello-part2/subworkflows/nf-core/utils_nfschema_plugin/main.nf @@ -71,4 +71,3 @@ workflow UTILS_NFSCHEMA_PLUGIN { emit: dummy_emit = true } - From 6887f139d03956293007ed7fcfdcf3e4b2b642f4 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 18:22:57 +0000 Subject: [PATCH 030/113] prettier --- docs/hello_nf-core/01_run_demo.md | 62 +++++++++++++------------- docs/hello_nf-core/02_rewrite_hello.md | 1 + 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index ffa88ada20..a56240c353 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -577,37 +577,37 @@ Each column can have a type, pattern, description and help text in a machine rea ```json title="assets/schema_input.json" linenums="1" { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://raw.githubusercontent.com/nf-core/demo/master/assets/schema_input.json", - "title": "nf-core/demo pipeline - params.input schema", - "description": "Schema for the file provided with params.input", - "type": "array", - "items": { - "type": "object", - "properties": { - "sample": { - "type": "string", - "pattern": "^\\S+$", - "errorMessage": "Sample name must be provided and cannot contain spaces", - "meta": ["id"] - }, - "fastq_1": { - "type": "string", - "format": "file-path", - "exists": true, - "pattern": "^\\S+\\.f(ast)?q\\.gz$", - "errorMessage": "FastQ file for reads 1 must be provided, cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" - }, - "fastq_2": { - "type": "string", - "format": "file-path", - "exists": true, - "pattern": "^\\S+\\.f(ast)?q\\.gz$", - "errorMessage": "FastQ file for reads 2 cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" - } - }, - "required": ["sample", "fastq_1"] - } + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/nf-core/demo/master/assets/schema_input.json", + "title": "nf-core/demo pipeline - params.input schema", + "description": "Schema for the file provided with params.input", + "type": "array", + "items": { + "type": "object", + "properties": { + "sample": { + "type": "string", + "pattern": "^\\S+$", + "errorMessage": "Sample name must be provided and cannot contain spaces", + "meta": ["id"] + }, + "fastq_1": { + "type": "string", + "format": "file-path", + "exists": true, + "pattern": "^\\S+\\.f(ast)?q\\.gz$", + "errorMessage": "FastQ file for reads 1 must be provided, cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" + }, + "fastq_2": { + "type": "string", + "format": "file-path", + "exists": true, + "pattern": "^\\S+\\.f(ast)?q\\.gz$", + "errorMessage": "FastQ file for reads 2 cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" + } + }, + "required": ["sample", "fastq_1"] + } } ``` diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index d964b5c894..9eb4901b9b 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -780,6 +780,7 @@ As a reminder, this is the relevant code in the original workflow, which didn't ``` We need to copy this code into the new version of the workflow, with a few modifications: + - Omit the `main:` keyword (it's already there) - Remove the `.view` line (line 776 above) - this was just for console output in the standalone version From 3e4ff9e08d290484071423a7fb1d0efdb1043200 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 18:27:51 +0000 Subject: [PATCH 031/113] Correct attribution of pipeline_info contents in section 4.5 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previous text incorrectly attributed all pipeline_info contents to "nf-core utility subworkflows". In reality: - Execution reports (timeline, trace, report, dag) are produced by Nextflow's built-in reporting configured in nextflow.config - Software versions and params are from nf-core template features Changed to more accurate generic description: "execution reports and metadata" which correctly encompasses both sources without incorrectly attributing everything to utility subworkflows. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/02_rewrite_hello.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index 9eb4901b9b..cc401e2f8d 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -1270,7 +1270,7 @@ results ``` Anything that is hooked up to the nf-core template code gets put into a directory generated automatically, called `core-hello-results/`. -This includes the various reports produced by the nf-core utility subworkflows, which you can find under `core-hello-results/pipeline_info`. +This includes various execution reports and metadata that you can find under `core-hello-results/pipeline_info`. ```bash tree core-hello-results From 675ff555e7feceec394fcb9926e28e8eeb43c112 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 18:33:58 +0000 Subject: [PATCH 032/113] Clarify nf-core module naming conventions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix the module naming convention note to accurately reflect that: - Modules use software/command format when tools have multiple commands (e.g., samtools/view, gatk/haplotypecaller) - Modules use single-level names when tools have only one main command (e.g., fastqc, multiqc) - Remove incorrect fastqc/fastqc example 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/03_use_module.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hello_nf-core/03_use_module.md b/docs/hello_nf-core/03_use_module.md index 85118b8d8d..e37535d933 100644 --- a/docs/hello_nf-core/03_use_module.md +++ b/docs/hello_nf-core/03_use_module.md @@ -29,7 +29,7 @@ The `collectGreetings` process in our pipeline uses the Unix `cat` command to co !!! note "Module naming convention" - nf-core modules follow the naming convention `software/command`. The `cat/cat` module wraps the `cat` command from the `cat` software package. Other examples include `fastqc/fastqc` (FastQC software, fastqc command) or `samtools/view` (samtools software, view command). + nf-core modules follow the naming convention `software/command` when a tool provides multiple commands, like `samtools/view` (samtools package, view command) or `gatk/haplotypecaller` (GATK package, HaplotypeCaller command). For tools that provide only one main command, modules use a single level like `fastqc` or `multiqc`. The `cat/cat` naming reflects the organizational structure in the modules repository. ### 1.1. Browse available modules on the nf-core website From 5ca08773a18ef608a9c342638086006bca7cf134 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 18:35:55 +0000 Subject: [PATCH 033/113] Add info output --- docs/hello_nf-core/03_use_module.md | 49 ++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/docs/hello_nf-core/03_use_module.md b/docs/hello_nf-core/03_use_module.md index e37535d933..ff46c328ed 100644 --- a/docs/hello_nf-core/03_use_module.md +++ b/docs/hello_nf-core/03_use_module.md @@ -72,7 +72,54 @@ To see detailed information about a specific module, use the `info` command: nf-core modules info cat/cat ``` -This displays documentation about the module, including its inputs, outputs, and basic usage information. +This displays documentation about the module, including its inputs, outputs, and basic usage information: + +```console title="Output" + + ,--./,-. + ___ __ __ __ ___ /,-._.--~\ + |\ | |__ __ / ` / \ |__) |__ } { + | \| | \__, \__/ | \ |___ \`-._,-`-, + `._,._,' + + nf-core/tools version 3.4.1 - https://nf-co.re + + +╭─ Module: cat/cat ─────────────────────────────────────────────────╮ +│ 🌐 Repository: https://github.com/nf-core/modules.git │ +│ 🔧 Tools: cat │ +│ 📖 Description: A module for concatenation of gzipped or │ +│ uncompressed files │ +╰────────────────────────────────────────────────────────────────────╯ + ╷ ╷ + 📥 Inputs │Description │Pattern +╺━━━━━━━━━━━━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┿━━━━━━━╸ + input[0] │ │ +╶─────────────────┼──────────────────────────────────────────┼───────╴ + meta (map) │Groovy Map containing sample information │ + │e.g. [ id:'test', single_end:false ] │ +╶─────────────────┼──────────────────────────────────────────┼───────╴ + files_in (file)│List of compressed / uncompressed files │ * + ╵ ╵ + ╷ ╷ + 📥 Outputs │Description │ Pattern +╺━━━━━━━━━━━━━━━━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┿━━━━━━━━━━━━╸ + file_out │ │ +╶─────────────────────┼─────────────────────────────────┼────────────╴ + meta (map) │Groovy Map containing sample │ + │information │ +╶─────────────────────┼─────────────────────────────────┼────────────╴ + ${prefix} (file) │Concatenated file. Will be │ ${file_out} + │gzipped if file_out ends with │ + │".gz" │ +╶─────────────────────┼─────────────────────────────────┼────────────╴ + versions │ │ +╶─────────────────────┼─────────────────────────────────┼────────────╴ + versions.yml (file)│File containing software versions│versions.yml + ╵ ╵ + + 💻 Installation command: nf-core modules install cat/cat +``` ### 1.4. Install and verify the cat/cat module From b0bb38b9f7b84829e86349f2bce928f22caaa733 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 18:38:52 +0000 Subject: [PATCH 034/113] Update some outputs --- docs/hello_nf-core/03_use_module.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/hello_nf-core/03_use_module.md b/docs/hello_nf-core/03_use_module.md index ff46c328ed..7e1caff203 100644 --- a/docs/hello_nf-core/03_use_module.md +++ b/docs/hello_nf-core/03_use_module.md @@ -138,7 +138,9 @@ The tool will prompt you to confirm the installation. Press Enter to accept the ```console title="Output" INFO Installing 'cat/cat' -INFO Include statement: include { CAT_CAT } from '../modules/nf-core/cat/cat/main' +INFO Use the following statement to include this module: + + include { CAT_CAT } from '../modules/nf-core/cat/cat/main' ``` The command automatically: @@ -162,8 +164,10 @@ modules/nf-core/cat └── tests ├── main.nf.test ├── main.nf.test.snap - ├── nextflow.config - └── tags.yml + ├── nextflow_unzipped_zipped.config + └── nextflow_zipped_unzipped.config + +2 directories, 7 files ``` You can also verify the installation by listing locally installed modules: From 373841429e9bebc1fb91c83ae33ae2da59257b9a Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 18:43:52 +0000 Subject: [PATCH 035/113] Fix cat content --- docs/hello_nf-core/03_use_module.md | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/docs/hello_nf-core/03_use_module.md b/docs/hello_nf-core/03_use_module.md index 7e1caff203..3ac12b5ded 100644 --- a/docs/hello_nf-core/03_use_module.md +++ b/docs/hello_nf-core/03_use_module.md @@ -177,13 +177,14 @@ nf-core modules list local ``` ```console title="Output" +INFO Repository type: pipeline INFO Modules installed in '.': -┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -┃ Module Name ┃ Repository ┃ -┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ -│ cat/cat │ nf-core/modules │ -└────────────────────────────┴─────────────────────────────┘ +┏━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓ +┃ Module Name ┃ Repository ┃ Version SHA ┃ Message ┃ Date ┃ +┡━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩ +│ cat/cat │ nf-core/modules │ 41dfa3f │ update meta.yml of all modules (#8747) │ 2025-07-07 │ +└─────────────┴─────────────────┴─────────────┴────────────────────────────────────────┴────────────┘ ``` ### 1.5. Add the import statement to your workflow @@ -240,17 +241,22 @@ head -30 modules/nf-core/cat/cat/main.nf The key parts of the module are: -```groovy title="modules/nf-core/cat/cat/main.nf (excerpt)" linenums="1" hl_lines="6 9" +```groovy title="modules/nf-core/cat/cat/main.nf (excerpt)" linenums="1" hl_lines="12 25" process CAT_CAT { tag "$meta.id" - label 'process_single' + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/pigz:2.3.4' : + 'biocontainers/pigz:2.3.4' }" input: tuple val(meta), path(files_in) output: tuple val(meta), path("${prefix}"), emit: file_out - path "versions.yml" , emit: versions + path "versions.yml" , emit: versions ``` The module expects: From 80d8d4e834ee5ed373695d6320014444ce0d72cc Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 18:53:48 +0000 Subject: [PATCH 036/113] update some outputs --- docs/hello_nf-core/03_use_module.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/hello_nf-core/03_use_module.md b/docs/hello_nf-core/03_use_module.md index 3ac12b5ded..185dec3d35 100644 --- a/docs/hello_nf-core/03_use_module.md +++ b/docs/hello_nf-core/03_use_module.md @@ -291,7 +291,7 @@ The main differences are: You've just seen that `CAT_CAT` expects inputs and outputs structured as tuples with metadata: -```groovy +```groovy name="modules/nf-core/cat/cat/main.nf (excerpt)" linenums="1" hl_lines="2 5" input: tuple val(meta), path(files_in) @@ -517,10 +517,10 @@ nextflow run . --outdir core-hello-results -profile test,docker --validate_param ```console title="Output" N E X T F L O W ~ version 25.04.3 -Launching `./main.nf` [extravagant_volhard] DSL2 - revision: 6aa79210e6 +Launching `./main.nf` [evil_pike] DSL2 - revision: b9e9b3b8de Input/output options - input : /workspaces/training/hello-nf-core/nf-core-hello/assets/greetings.csv + input : /workspaces/training/hello-nf-core/core-hello/assets/greetings.csv outdir : core-hello-results Institutional config options @@ -529,26 +529,26 @@ Institutional config options Generic options validate_params : false - trace_report_suffix : 2025-10-17_19-51-31 + trace_report_suffix : 2025-10-30_18-50-58 Core Nextflow options - runName : extravagant_volhard + runName : evil_pike containerEngine : docker - launchDir : /workspaces/training/hello-nf-core/nf-core-hello - workDir : /workspaces/training/hello-nf-core/nf-core-hello/work - projectDir : /workspaces/training/hello-nf-core/nf-core-hello + launchDir : /workspaces/training/hello-nf-core/core-hello + workDir : /workspaces/training/hello-nf-core/core-hello/work + projectDir : /workspaces/training/hello-nf-core/core-hello userName : root profile : test,docker - configFiles : /workspaces/training/hello-nf-core/nf-core-hello/nextflow.config + configFiles : /workspaces/training/hello-nf-core/core-hello/nextflow.config !! Only displaying parameters that differ from the pipeline defaults !! ------------------------------------------------------ executor > local (8) -[60/3ac109] NFCORE_HELLO:HELLO:sayHello (3) [100%] 3 of 3 ✔ -[58/073077] NFCORE_HELLO:HELLO:convertToUpper (3) [100%] 3 of 3 ✔ -[00/4f3d32] NFCORE_HELLO:HELLO:CAT_CAT (test) [100%] 1 of 1 ✔ -[98/afab8b] NFCORE_HELLO:HELLO:cowpy [100%] 1 of 1 ✔ --[nf-core/hello] Pipeline completed successfully- +[b3/f005fd] CORE_HELLO:HELLO:sayHello (3) [100%] 3 of 3 ✔ +[08/f923d0] CORE_HELLO:HELLO:convertToUpper (3) [100%] 3 of 3 ✔ +[34/3729a9] CORE_HELLO:HELLO:CAT_CAT (test) [100%] 1 of 1 ✔ +[24/df918a] CORE_HELLO:HELLO:cowpy [100%] 1 of 1 ✔ +-[core/hello] Pipeline completed successfully- ``` Notice that `CAT_CAT` now appears in the process execution list instead of `collectGreetings`. From b93e362ddcb678904e57912b307c388f8797ed4e Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 18:56:26 +0000 Subject: [PATCH 037/113] Fix highlight --- docs/hello_nf-core/03_use_module.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hello_nf-core/03_use_module.md b/docs/hello_nf-core/03_use_module.md index 185dec3d35..77dcd8b390 100644 --- a/docs/hello_nf-core/03_use_module.md +++ b/docs/hello_nf-core/03_use_module.md @@ -241,7 +241,7 @@ head -30 modules/nf-core/cat/cat/main.nf The key parts of the module are: -```groovy title="modules/nf-core/cat/cat/main.nf (excerpt)" linenums="1" hl_lines="12 25" +```groovy title="modules/nf-core/cat/cat/main.nf (excerpt)" linenums="1" hl_lines="11 14" process CAT_CAT { tag "$meta.id" label 'process_low' From abd223be02e213c1d3acdb71054cd50ac74960be Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 19:05:13 +0000 Subject: [PATCH 038/113] Update part 3 solution --- .../solutions/core-hello-part3/.nf-core.yml | 106 ++++++++++++++++++ .../solutions/core-hello-part3/README.md | 4 +- .../core-hello-part3/assets/schema_input.json | 4 +- .../core-hello-part3/conf/base.config | 6 +- .../core-hello-part3/conf/test.config | 5 +- .../core-hello-part3/conf/test_full.config | 24 ++++ .../solutions/core-hello-part3/docs/usage.md | 2 +- .../solutions/core-hello-part3/main.nf | 5 +- .../solutions/core-hello-part3/modules.json | 64 +++++------ .../modules/local/collectGreetings.nf | 2 + .../nf-core/cat/cat/tests/main.nf.test.snap | 4 +- .../core-hello-part3/nextflow.config | 46 +++++--- .../core-hello-part3/nextflow_schema.json | 14 ++- .../local/utils_nfcore_hello_pipeline/main.nf | 21 +++- .../tests/main.function.nf.test.snap | 2 +- .../utils_nextflow_pipeline/tests/tags.yml | 2 - .../tests/main.function.nf.test.snap | 2 +- .../tests/main.workflow.nf.test.snap | 2 +- .../utils_nfcore_pipeline/tests/tags.yml | 2 - .../nf-core/utils_nfschema_plugin/main.nf | 41 ++++++- .../utils_nfschema_plugin/tests/main.nf.test | 56 +++++++++ .../tests/nextflow.config | 2 +- .../core-hello-part3/workflows/hello.nf | 9 +- 23 files changed, 344 insertions(+), 81 deletions(-) create mode 100644 hello-nf-core/solutions/core-hello-part3/.nf-core.yml create mode 100644 hello-nf-core/solutions/core-hello-part3/conf/test_full.config delete mode 100644 hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml delete mode 100644 hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml diff --git a/hello-nf-core/solutions/core-hello-part3/.nf-core.yml b/hello-nf-core/solutions/core-hello-part3/.nf-core.yml new file mode 100644 index 0000000000..1a638d8288 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part3/.nf-core.yml @@ -0,0 +1,106 @@ +repository_type: pipeline + +nf_core_version: 3.4.1 + +lint: + files_unchanged: + - .github/CONTRIBUTING.md + - .prettierignore + - .prettierignore + - .prettierignore + - CODE_OF_CONDUCT.md + - assets/nf-core-hello_logo_light.png + - docs/images/nf-core-hello_logo_light.png + - docs/images/nf-core-hello_logo_dark.png + - .github/ISSUE_TEMPLATE/bug_report.yml + - .github/CONTRIBUTING.md + - .github/PULL_REQUEST_TEMPLATE.md + - assets/email_template.txt + - docs/README.md + - .github/ISSUE_TEMPLATE/bug_report.yml + - .github/ISSUE_TEMPLATE/config.yml + - .github/ISSUE_TEMPLATE/feature_request.yml + - .github/PULL_REQUEST_TEMPLATE.md + - .github/workflows/branch.yml + - .github/workflows/linting_comment.yml + - .github/workflows/linting.yml + - .github/CONTRIBUTING.md + - .github/.dockstore.yml + - .github/CONTRIBUTING.md + - assets/sendmail_template.txt + - .prettierignore + - LICENSE + nextflow_config: + - manifest.name + - manifest.homePage + nf_test_content: false + multiqc_config: false + files_exist: + - .github/workflows/nf-test.yml + - .github/actions/get-shards/action.yml + - .github/actions/nf-test/action.yml + - nf-test.config + - tests/default.nf.test + - assets/email_template.html + - assets/sendmail_template.txt + - assets/email_template.txt + - CODE_OF_CONDUCT.md + - assets/nf-core-hello_logo_light.png + - docs/images/nf-core-hello_logo_light.png + - docs/images/nf-core-hello_logo_dark.png + - .github/ISSUE_TEMPLATE/config.yml + - .github/workflows/awstest.yml + - .github/workflows/awsfulltest.yml + - .github/ISSUE_TEMPLATE/bug_report.yml + - .github/ISSUE_TEMPLATE/feature_request.yml + - .github/PULL_REQUEST_TEMPLATE.md + - .github/CONTRIBUTING.md + - .github/.dockstore.yml + - CHANGELOG.md + - assets/multiqc_config.yml + - .github/workflows/branch.yml + - .github/workflows/nf-test.yml + - .github/actions/get-shards/action.yml + - .github/actions/nf-test/action.yml + - .github/workflows/linting_comment.yml + - .github/workflows/linting.yml + - .prettierignore + - .prettierrc.yml + - conf/igenomes.config + - conf/igenomes_ignored.config + - CITATIONS.md + - LICENSE + readme: + - nextflow_badge + - nextflow_badge + - nfcore_template_badge + +template: + org: core + name: hello + description: A basic nf-core style version of Hello Nextflow + author: pinin4fjords + version: 1.0.0dev + force: true + outdir: . + skip_features: + - github + - github_badges + - changelog + - license + - ci + - nf-test + - igenomes + - multiqc + - fastqc + - seqera_platform + - gpu + - codespaces + - vscode + - code_linters + - citations + - rocrate + - email + - adaptivecard + - slackreport + is_nfcore: false diff --git a/hello-nf-core/solutions/core-hello-part3/README.md b/hello-nf-core/solutions/core-hello-part3/README.md index 0a533c4c4b..94844f6c5e 100644 --- a/hello-nf-core/solutions/core-hello-part3/README.md +++ b/hello-nf-core/solutions/core-hello-part3/README.md @@ -11,7 +11,7 @@ --> + workflows use the "tube map" design for that. See https://nf-co.re/docs/guidelines/graphic_design/workflow_diagrams#examples for examples. --> ## Usage @@ -51,7 +51,7 @@ nextflow run core/hello \ ## Credits -core/hello was originally written by GG. +core/hello was originally written by pinin4fjords. We thank the following people for their extensive assistance in the development of this pipeline: diff --git a/hello-nf-core/solutions/core-hello-part3/assets/schema_input.json b/hello-nf-core/solutions/core-hello-part3/assets/schema_input.json index bc0261f329..5cb7458161 100644 --- a/hello-nf-core/solutions/core-hello-part3/assets/schema_input.json +++ b/hello-nf-core/solutions/core-hello-part3/assets/schema_input.json @@ -17,14 +17,14 @@ "type": "string", "format": "file-path", "exists": true, - "pattern": "^\\S+\\.f(ast)?q\\.gz$", + "pattern": "^([\\S\\s]*\\/)?[^\\s\\/]+\\.f(ast)?q\\.gz$", "errorMessage": "FastQ file for reads 1 must be provided, cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" }, "fastq_2": { "type": "string", "format": "file-path", "exists": true, - "pattern": "^\\S+\\.f(ast)?q\\.gz$", + "pattern": "^([\\S\\s]*\\/)?[^\\s\\/]+\\.f(ast)?q\\.gz$", "errorMessage": "FastQ file for reads 2 cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" } }, diff --git a/hello-nf-core/solutions/core-hello-part3/conf/base.config b/hello-nf-core/solutions/core-hello-part3/conf/base.config index 1abcd9876f..e0fe40762f 100644 --- a/hello-nf-core/solutions/core-hello-part3/conf/base.config +++ b/hello-nf-core/solutions/core-hello-part3/conf/base.config @@ -15,7 +15,7 @@ process { memory = { 6.GB * task.attempt } time = { 4.h * task.attempt } - errorStrategy = { task.exitStatus in ((130..145) + 104) ? 'retry' : 'finish' } + errorStrategy = { task.exitStatus in ((130..145) + 104 + 175) ? 'retry' : 'finish' } maxRetries = 1 maxErrors = '-1' @@ -59,4 +59,8 @@ process { errorStrategy = 'retry' maxRetries = 2 } + withLabel: process_gpu { + ext.use_gpu = { workflow.profile.contains('gpu') } + accelerator = { workflow.profile.contains('gpu') ? 1 : null } + } } diff --git a/hello-nf-core/solutions/core-hello-part3/conf/test.config b/hello-nf-core/solutions/core-hello-part3/conf/test.config index f82761298d..13ecf2ad4b 100644 --- a/hello-nf-core/solutions/core-hello-part3/conf/test.config +++ b/hello-nf-core/solutions/core-hello-part3/conf/test.config @@ -12,8 +12,9 @@ process { resourceLimits = [ - cpus: 1, - memory: '1.GB' + cpus: 2, + memory: '4.GB', + time: '1.h' ] } diff --git a/hello-nf-core/solutions/core-hello-part3/conf/test_full.config b/hello-nf-core/solutions/core-hello-part3/conf/test_full.config new file mode 100644 index 0000000000..ceeaf40cac --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part3/conf/test_full.config @@ -0,0 +1,24 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Nextflow config file for running full-size tests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Defines input files and everything required to run a full size pipeline test. + + Use as follows: + nextflow run core/hello -profile test_full, --outdir + +---------------------------------------------------------------------------------------- +*/ + +params { + config_profile_name = 'Full test profile' + config_profile_description = 'Full test dataset to check pipeline function' + + // Input data for full size test + // TODO nf-core: Specify the paths to your full test data ( on nf-core/test-datasets or directly in repositories, e.g. SRA) + // TODO nf-core: Give any required params for the test so that command line flags are not needed + input = params.pipelines_testdata_base_path + 'viralrecon/samplesheet/samplesheet_full_illumina_amplicon.csv' + + // Fasta references + fasta = params.pipelines_testdata_base_path + 'viralrecon/genome/NC_045512.2/GCF_009858895.2_ASM985889v3_genomic.200409.fna.gz' +} diff --git a/hello-nf-core/solutions/core-hello-part3/docs/usage.md b/hello-nf-core/solutions/core-hello-part3/docs/usage.md index 78b55f9afe..bfbc37ab42 100644 --- a/hello-nf-core/solutions/core-hello-part3/docs/usage.md +++ b/hello-nf-core/solutions/core-hello-part3/docs/usage.md @@ -146,7 +146,7 @@ If `-profile` is not specified, the pipeline will run locally and expect all sof - `shifter` - A generic configuration profile to be used with [Shifter](https://nersc.gitlab.io/development/shifter/how-to-use/) - `charliecloud` - - A generic configuration profile to be used with [Charliecloud](https://hpc.github.io/charliecloud/) + - A generic configuration profile to be used with [Charliecloud](https://charliecloud.io/) - `apptainer` - A generic configuration profile to be used with [Apptainer](https://apptainer.org/) - `wave` diff --git a/hello-nf-core/solutions/core-hello-part3/main.nf b/hello-nf-core/solutions/core-hello-part3/main.nf index f72a236660..eb8d91361f 100644 --- a/hello-nf-core/solutions/core-hello-part3/main.nf +++ b/hello-nf-core/solutions/core-hello-part3/main.nf @@ -57,7 +57,10 @@ workflow { params.monochrome_logs, args, params.outdir, - params.input + params.input, + params.help, + params.help_full, + params.show_hidden ) // diff --git a/hello-nf-core/solutions/core-hello-part3/modules.json b/hello-nf-core/solutions/core-hello-part3/modules.json index 85169da597..3e65b38609 100644 --- a/hello-nf-core/solutions/core-hello-part3/modules.json +++ b/hello-nf-core/solutions/core-hello-part3/modules.json @@ -1,36 +1,36 @@ { - "name": "core/hello", - "homePage": "https://github.com/core/hello", - "repos": { - "https://github.com/nf-core/modules.git": { - "modules": { - "nf-core": { - "cat/cat": { - "branch": "master", - "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", - "installed_by": ["modules"] - } - } - }, - "subworkflows": { - "nf-core": { - "utils_nextflow_pipeline": { - "branch": "master", - "git_sha": "c2b22d85f30a706a3073387f30380704fcae013b", - "installed_by": ["subworkflows"] - }, - "utils_nfcore_pipeline": { - "branch": "master", - "git_sha": "51ae5406a030d4da1e49e4dab49756844fdd6c7a", - "installed_by": ["subworkflows"] - }, - "utils_nfschema_plugin": { - "branch": "master", - "git_sha": "2fd2cd6d0e7b273747f32e465fdc6bcc3ae0814e", - "installed_by": ["subworkflows"] - } - } - } + "name": "core/hello", + "homePage": "https://github.com/core/hello", + "repos": { + "https://github.com/nf-core/modules.git": { + "modules": { + "nf-core": { + "cat/cat": { + "branch": "master", + "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", + "installed_by": ["modules"] + } } + }, + "subworkflows": { + "nf-core": { + "utils_nextflow_pipeline": { + "branch": "master", + "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "installed_by": ["subworkflows"] + }, + "utils_nfcore_pipeline": { + "branch": "master", + "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "installed_by": ["subworkflows"] + }, + "utils_nfschema_plugin": { + "branch": "master", + "git_sha": "4b406a74dc0449c0401ed87d5bfff4252fd277fd", + "installed_by": ["subworkflows"] + } + } + } } + } } diff --git a/hello-nf-core/solutions/core-hello-part3/modules/local/collectGreetings.nf b/hello-nf-core/solutions/core-hello-part3/modules/local/collectGreetings.nf index 0274fec86f..849bba4b6e 100644 --- a/hello-nf-core/solutions/core-hello-part3/modules/local/collectGreetings.nf +++ b/hello-nf-core/solutions/core-hello-part3/modules/local/collectGreetings.nf @@ -11,8 +11,10 @@ process collectGreetings { output: path "COLLECTED-${batch_name}-output.txt" , emit: outfile + val count_greetings , emit: count script: + count_greetings = input_files.size() """ cat ${input_files} > 'COLLECTED-${batch_name}-output.txt' """ diff --git a/hello-nf-core/solutions/core-hello-part3/modules/nf-core/cat/cat/tests/main.nf.test.snap b/hello-nf-core/solutions/core-hello-part3/modules/nf-core/cat/cat/tests/main.nf.test.snap index e2381ca20b..b7623ee650 100644 --- a/hello-nf-core/solutions/core-hello-part3/modules/nf-core/cat/cat/tests/main.nf.test.snap +++ b/hello-nf-core/solutions/core-hello-part3/modules/nf-core/cat/cat/tests/main.nf.test.snap @@ -93,7 +93,7 @@ "test_cat_name_conflict": { "content": [ [ - + ] ], "meta": { @@ -144,4 +144,4 @@ }, "timestamp": "2024-07-22T11:51:57.581523" } -} +} \ No newline at end of file diff --git a/hello-nf-core/solutions/core-hello-part3/nextflow.config b/hello-nf-core/solutions/core-hello-part3/nextflow.config index d633adb989..b59ba2175d 100644 --- a/hello-nf-core/solutions/core-hello-part3/nextflow.config +++ b/hello-nf-core/solutions/core-hello-part3/nextflow.config @@ -22,7 +22,9 @@ params { show_hidden = false version = false pipelines_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/' - trace_report_suffix = new java.util.Date().format( 'yyyy-MM-dd_HH-mm-ss')// Config options + trace_report_suffix = new java.util.Date().format( 'yyyy-MM-dd_HH-mm-ss') + + // Config options config_profile_name = null config_profile_description = null @@ -75,7 +77,18 @@ profiles { apptainer.enabled = false docker.runOptions = '-u $(id -u):$(id -g)' } - arm { + arm64 { + process.arch = 'arm64' + // TODO https://github.com/nf-core/modules/issues/6694 + // For now if you're using arm64 you have to use wave for the sake of the maintainers + // wave profile + apptainer.ociAutoPull = true + singularity.ociAutoPull = true + wave.enabled = true + wave.freeze = true + wave.strategy = 'conda,container' + } + emulate_amd64 { docker.runOptions = '-u $(id -u):$(id -g) --platform=linux/amd64' } singularity { @@ -132,16 +145,24 @@ profiles { wave.freeze = true wave.strategy = 'conda,container' } + gpu { + docker.runOptions = '-u $(id -u):$(id -g) --gpus all' + apptainer.runOptions = '--nv' + singularity.runOptions = '--nv' + } test { includeConfig 'conf/test.config' } test_full { includeConfig 'conf/test_full.config' } } +// Load nf-core custom profiles from different institutions + +// If params.custom_config_base is set AND either the NXF_OFFLINE environment variable is not set or params.custom_config_base is a local path, the nfcore_custom.config file from the specified base path is included. +// Load core/hello custom profiles from different institutions. +includeConfig params.custom_config_base && (!System.getenv('NXF_OFFLINE') || !params.custom_config_base.startsWith('http')) ? "${params.custom_config_base}/nfcore_custom.config" : "/dev/null" -// Load nf-core custom profiles from different Institutions -includeConfig !System.getenv('NXF_OFFLINE') && params.custom_config_base ? "${params.custom_config_base}/nfcore_custom.config" : "/dev/null" // Load core/hello custom profiles from different institutions. // TODO nf-core: Optionally, you can add a pipeline-specific nf-core config at https://github.com/nf-core/configs -// includeConfig !System.getenv('NXF_OFFLINE') && params.custom_config_base ? "${params.custom_config_base}/pipeline/hello.config" : "/dev/null" +// includeConfig params.custom_config_base && (!System.getenv('NXF_OFFLINE') || !params.custom_config_base.startsWith('http')) ? "${params.custom_config_base}/pipeline/hello.config" : "/dev/null" // Set default registry for Apptainer, Docker, Podman, Charliecloud and Singularity independent of -profile // Will not be used unless Apptainer / Docker / Podman / Charliecloud / Singularity are enabled @@ -197,11 +218,10 @@ dag { manifest { name = 'core/hello' - author = """GG""" // The author field is deprecated from Nextflow version 24.10.0, use contributors instead contributors = [ // TODO nf-core: Update the field with the details of the contributors to your pipeline. New with Nextflow version 24.10.0 [ - name: 'GG', + name: 'pinin4fjords', affiliation: '', email: '', github: '', @@ -210,28 +230,22 @@ manifest { ], ] homePage = 'https://github.com/core/hello' - description = """basic nf-core style version of Hello Nextflow""" + description = """A basic nf-core style version of Hello Nextflow""" mainScript = 'main.nf' defaultBranch = 'main' - nextflowVersion = '!>=24.04.2' + nextflowVersion = '!>=25.04.0' version = '1.0.0dev' doi = '' } // Nextflow plugins plugins { - id 'nf-schema@2.2.0' // Validation of pipeline parameters and creation of an input channel from a sample sheet + id 'nf-schema@2.5.1' // Validation of pipeline parameters and creation of an input channel from a sample sheet } validation { defaultIgnoreParams = ["genomes"] monochromeLogs = params.monochrome_logs - help { - enabled = true - command = "nextflow run core/hello -profile --input samplesheet.csv --outdir " - fullParameter = "help_full" - showHiddenParameter = "show_hidden" - } } // Load modules.config for DSL2 module specific options diff --git a/hello-nf-core/solutions/core-hello-part3/nextflow_schema.json b/hello-nf-core/solutions/core-hello-part3/nextflow_schema.json index 5ee5ec357f..fc18ba7998 100644 --- a/hello-nf-core/solutions/core-hello-part3/nextflow_schema.json +++ b/hello-nf-core/solutions/core-hello-part3/nextflow_schema.json @@ -2,7 +2,7 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://raw.githubusercontent.com/core/hello/main/nextflow_schema.json", "title": "core/hello pipeline parameters", - "description": "basic nf-core style version of Hello Nextflow", + "description": "A basic nf-core style version of Hello Nextflow", "type": "object", "$defs": { "input_output_options": { @@ -133,6 +133,18 @@ "fa_icon": "far calendar", "description": "Suffix to add to the trace report filename. Default is the date and time in the format yyyy-MM-dd_HH-mm-ss.", "hidden": true + }, + "help": { + "type": ["boolean", "string"], + "description": "Display the help message." + }, + "help_full": { + "type": "boolean", + "description": "Display the full detailed help message." + }, + "show_hidden": { + "type": "boolean", + "description": "Display hidden parameters in the help message (only works when --help or --help_full are provided)." } } } diff --git a/hello-nf-core/solutions/core-hello-part3/subworkflows/local/utils_nfcore_hello_pipeline/main.nf b/hello-nf-core/solutions/core-hello-part3/subworkflows/local/utils_nfcore_hello_pipeline/main.nf index 53ba38fae8..93c9f874cc 100644 --- a/hello-nf-core/solutions/core-hello-part3/subworkflows/local/utils_nfcore_hello_pipeline/main.nf +++ b/hello-nf-core/solutions/core-hello-part3/subworkflows/local/utils_nfcore_hello_pipeline/main.nf @@ -11,6 +11,7 @@ include { UTILS_NFSCHEMA_PLUGIN } from '../../nf-core/utils_nfschema_plugin' include { paramsSummaryMap } from 'plugin/nf-schema' include { samplesheetToList } from 'plugin/nf-schema' +include { paramsHelp } from 'plugin/nf-schema' include { completionSummary } from '../../nf-core/utils_nfcore_pipeline' include { UTILS_NFCORE_PIPELINE } from '../../nf-core/utils_nfcore_pipeline' include { UTILS_NEXTFLOW_PIPELINE } from '../../nf-core/utils_nextflow_pipeline' @@ -30,6 +31,9 @@ workflow PIPELINE_INITIALISATION { nextflow_cli_args // array: List of positional nextflow CLI args outdir // string: The output directory where the results will be saved input // string: Path to input samplesheet + help // boolean: Display help message and exit + help_full // boolean: Show the full help message + show_hidden // boolean: Show hidden parameters in the help message main: @@ -48,10 +52,18 @@ workflow PIPELINE_INITIALISATION { // // Validate parameters and generate parameter summary to stdout // + command = "nextflow run ${workflow.manifest.name} -profile --input samplesheet.csv --outdir " + UTILS_NFSCHEMA_PLUGIN ( workflow, validate_params, - null + null, + help, + help_full, + show_hidden, + "", + "", + command ) // @@ -64,9 +76,10 @@ workflow PIPELINE_INITIALISATION { // // Create channel from input file provided through params.input // - ch_samplesheet = Channel.fromPath(params.input) - .splitCsv() - .map { line -> line[0] } + + ch_samplesheet = channel.fromPath(params.input) + .splitCsv() + .map { line -> line[0] } emit: samplesheet = ch_samplesheet diff --git a/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap b/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap index 846287c417..e3f0baf473 100644 --- a/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap +++ b/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap @@ -17,4 +17,4 @@ }, "timestamp": "2024-02-28T12:02:12.425833" } -} +} \ No newline at end of file diff --git a/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml b/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml deleted file mode 100644 index f84761125a..0000000000 --- a/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml +++ /dev/null @@ -1,2 +0,0 @@ -subworkflows/utils_nextflow_pipeline: - - subworkflows/nf-core/utils_nextflow_pipeline/** diff --git a/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap b/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap index b13b311213..02c6701413 100644 --- a/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap +++ b/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap @@ -133,4 +133,4 @@ }, "timestamp": "2024-02-28T12:03:21.714424" } -} +} \ No newline at end of file diff --git a/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap b/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap index 84ee1e1d1e..859d1030fb 100644 --- a/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap +++ b/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap @@ -16,4 +16,4 @@ }, "timestamp": "2024-02-28T12:03:25.726491" } -} +} \ No newline at end of file diff --git a/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml b/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml deleted file mode 100644 index ac8523c9a2..0000000000 --- a/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml +++ /dev/null @@ -1,2 +0,0 @@ -subworkflows/utils_nfcore_pipeline: - - subworkflows/nf-core/utils_nfcore_pipeline/** diff --git a/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfschema_plugin/main.nf b/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfschema_plugin/main.nf index 93de2a5245..ee4738c8d1 100644 --- a/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfschema_plugin/main.nf +++ b/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfschema_plugin/main.nf @@ -4,6 +4,7 @@ include { paramsSummaryLog } from 'plugin/nf-schema' include { validateParameters } from 'plugin/nf-schema' +include { paramsHelp } from 'plugin/nf-schema' workflow UTILS_NFSCHEMA_PLUGIN { @@ -15,31 +16,59 @@ workflow UTILS_NFSCHEMA_PLUGIN { // when this input is empty it will automatically use the configured schema or // "${projectDir}/nextflow_schema.json" as default. This input should not be empty // for meta pipelines + help // boolean: show help message + help_full // boolean: show full help message + show_hidden // boolean: show hidden parameters in help message + before_text // string: text to show before the help message and parameters summary + after_text // string: text to show after the help message and parameters summary + command // string: an example command of the pipeline main: + if(help || help_full) { + help_options = [ + beforeText: before_text, + afterText: after_text, + command: command, + showHidden: show_hidden, + fullHelp: help_full, + ] + if(parameters_schema) { + help_options << [parametersSchema: parameters_schema] + } + log.info paramsHelp( + help_options, + params.help instanceof String ? params.help : "", + ) + exit 0 + } + // // Print parameter summary to stdout. This will display the parameters // that differ from the default given in the JSON schema // + + summary_options = [:] if(parameters_schema) { - log.info paramsSummaryLog(input_workflow, parameters_schema:parameters_schema) - } else { - log.info paramsSummaryLog(input_workflow) + summary_options << [parametersSchema: parameters_schema] } + log.info before_text + log.info paramsSummaryLog(summary_options, input_workflow) + log.info after_text // // Validate the parameters using nextflow_schema.json or the schema // given via the validation.parametersSchema configuration option // if(validate_params) { + validateOptions = [:] if(parameters_schema) { - validateParameters(parameters_schema:parameters_schema) - } else { - validateParameters() + validateOptions << [parametersSchema: parameters_schema] } + validateParameters(validateOptions) } emit: dummy_emit = true } + diff --git a/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test b/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test index 8fb3016487..c977917aac 100644 --- a/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test +++ b/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test @@ -25,6 +25,12 @@ nextflow_workflow { input[0] = workflow input[1] = validate_params input[2] = "" + input[3] = false + input[4] = false + input[5] = false + input[6] = "" + input[7] = "" + input[8] = "" """ } } @@ -51,6 +57,12 @@ nextflow_workflow { input[0] = workflow input[1] = validate_params input[2] = "" + input[3] = false + input[4] = false + input[5] = false + input[6] = "" + input[7] = "" + input[8] = "" """ } } @@ -77,6 +89,12 @@ nextflow_workflow { input[0] = workflow input[1] = validate_params input[2] = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + input[3] = false + input[4] = false + input[5] = false + input[6] = "" + input[7] = "" + input[8] = "" """ } } @@ -103,6 +121,12 @@ nextflow_workflow { input[0] = workflow input[1] = validate_params input[2] = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + input[3] = false + input[4] = false + input[5] = false + input[6] = "" + input[7] = "" + input[8] = "" """ } } @@ -114,4 +138,36 @@ nextflow_workflow { ) } } + + test("Should create a help message") { + + when { + + params { + test_data = '' + outdir = null + } + + workflow { + """ + validate_params = true + input[0] = workflow + input[1] = validate_params + input[2] = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + input[3] = true + input[4] = false + input[5] = false + input[6] = "Before" + input[7] = "After" + input[8] = "nextflow run test/test" + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } } diff --git a/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config b/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config index 478fb8a05f..8d8c73718a 100644 --- a/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config +++ b/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config @@ -1,5 +1,5 @@ plugins { - id "nf-schema@2.1.0" + id "nf-schema@2.5.1" } validation { diff --git a/hello-nf-core/solutions/core-hello-part3/workflows/hello.nf b/hello-nf-core/solutions/core-hello-part3/workflows/hello.nf index 170a754fa9..8f732b7eda 100644 --- a/hello-nf-core/solutions/core-hello-part3/workflows/hello.nf +++ b/hello-nf-core/solutions/core-hello-part3/workflows/hello.nf @@ -7,6 +7,7 @@ include { paramsSummaryMap } from 'plugin/nf-schema' include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' include { sayHello } from '../modules/local/sayHello.nf' include { convertToUpper } from '../modules/local/convertToUpper.nf' +include { collectGreetings } from '../modules/local/collectGreetings.nf' include { cowpy } from '../modules/local/cowpy.nf' include { CAT_CAT } from '../modules/nf-core/cat/cat/main' @@ -20,19 +21,23 @@ workflow HELLO { take: ch_samplesheet // channel: samplesheet read in from --input + main: + ch_versions = Channel.empty() + // emit a greeting sayHello(ch_samplesheet) // convert the greeting to uppercase convertToUpper(sayHello.out) - // collect all the greetings into one file using nf-core cat/cat module // create metadata map with batch name as the ID def cat_meta = [ id: params.batch ] + // create a channel with metadata and files in tuple format ch_for_cat = convertToUpper.out.collect().map { files -> tuple(cat_meta, files) } + // concatenate files using the nf-core cat/cat module CAT_CAT(ch_for_cat) // generate ASCII art of the greetings with cowpy @@ -40,8 +45,6 @@ workflow HELLO { ch_for_cowpy = CAT_CAT.out.file_out.map{ meta, file -> file } cowpy(ch_for_cowpy, params.character) - ch_versions = Channel.empty() - // // Collate and save software versions // From 5ed52507b344587eb4d9945868882d6964eb2625 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 19:12:41 +0000 Subject: [PATCH 039/113] Remove legacy collectGreetings module from part 3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changes: - Add Step 4 to lesson instructing removal of collectGreetings import - Remove collectGreetings import from part3 solution - Delete collectGreetings.nf module file from part3 solution - Fix code fence syntax: change name= to title= for consistency - Renumber final step from 4 to 5 The lesson now properly teaches users to clean up the replaced module rather than leaving unused imports and files in the codebase. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/03_use_module.md | 41 ++++++++++++++++++- .../modules/local/collectGreetings.nf | 21 ---------- .../core-hello-part3/workflows/hello.nf | 1 - 3 files changed, 39 insertions(+), 24 deletions(-) delete mode 100644 hello-nf-core/solutions/core-hello-part3/modules/local/collectGreetings.nf diff --git a/docs/hello_nf-core/03_use_module.md b/docs/hello_nf-core/03_use_module.md index 77dcd8b390..df04897494 100644 --- a/docs/hello_nf-core/03_use_module.md +++ b/docs/hello_nf-core/03_use_module.md @@ -291,7 +291,7 @@ The main differences are: You've just seen that `CAT_CAT` expects inputs and outputs structured as tuples with metadata: -```groovy name="modules/nf-core/cat/cat/main.nf (excerpt)" linenums="1" hl_lines="2 5" +```groovy title="modules/nf-core/cat/cat/main.nf (excerpt)" linenums="1" hl_lines="2 5" input: tuple val(meta), path(files_in) @@ -456,7 +456,44 @@ Now call `CAT_CAT` with the properly formatted channel: cowpy(collectGreetings.out.outfile, params.character) ``` -#### Step 4: Update cowpy to use CAT_CAT output +#### Step 4: Remove the legacy collectGreetings import + +Since we're no longer using the `collectGreetings` module, remove its import statement from the top of the file: + +=== "After" + + ```groovy title="core-hello/workflows/hello.nf" linenums="1" hl_lines="10" + /* + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + include { paramsSummaryMap } from 'plugin/nf-schema' + include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' + include { sayHello } from '../modules/local/sayHello.nf' + include { convertToUpper } from '../modules/local/convertToUpper.nf' + include { cowpy } from '../modules/local/cowpy.nf' + include { CAT_CAT } from '../modules/nf-core/cat/cat/main' + ``` + +=== "Before" + + ```groovy title="core-hello/workflows/hello.nf" linenums="1" hl_lines="10" + /* + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + include { paramsSummaryMap } from 'plugin/nf-schema' + include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' + include { sayHello } from '../modules/local/sayHello.nf' + include { convertToUpper } from '../modules/local/convertToUpper.nf' + include { collectGreetings } from '../modules/local/collectGreetings.nf' + include { cowpy } from '../modules/local/cowpy.nf' + include { CAT_CAT } from '../modules/nf-core/cat/cat/main' + ``` + +#### Step 5: Update cowpy to use CAT_CAT output Finally, update the `cowpy` call to use the output from `CAT_CAT`. Since `cowpy` doesn't accept metadata tuples yet (we'll fix this in the next section), we need to extract just the file: diff --git a/hello-nf-core/solutions/core-hello-part3/modules/local/collectGreetings.nf b/hello-nf-core/solutions/core-hello-part3/modules/local/collectGreetings.nf deleted file mode 100644 index 849bba4b6e..0000000000 --- a/hello-nf-core/solutions/core-hello-part3/modules/local/collectGreetings.nf +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Collect uppercase greetings into a single output file - */ -process collectGreetings { - - publishDir 'results', mode: 'copy' - - input: - path input_files - val batch_name - - output: - path "COLLECTED-${batch_name}-output.txt" , emit: outfile - val count_greetings , emit: count - - script: - count_greetings = input_files.size() - """ - cat ${input_files} > 'COLLECTED-${batch_name}-output.txt' - """ -} diff --git a/hello-nf-core/solutions/core-hello-part3/workflows/hello.nf b/hello-nf-core/solutions/core-hello-part3/workflows/hello.nf index 8f732b7eda..1549833cdc 100644 --- a/hello-nf-core/solutions/core-hello-part3/workflows/hello.nf +++ b/hello-nf-core/solutions/core-hello-part3/workflows/hello.nf @@ -7,7 +7,6 @@ include { paramsSummaryMap } from 'plugin/nf-schema' include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' include { sayHello } from '../modules/local/sayHello.nf' include { convertToUpper } from '../modules/local/convertToUpper.nf' -include { collectGreetings } from '../modules/local/collectGreetings.nf' include { cowpy } from '../modules/local/cowpy.nf' include { CAT_CAT } from '../modules/nf-core/cat/cat/main' From a4ed3ff4c02115852774dc6cf2b86d72fe2483ec Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 19:39:15 +0000 Subject: [PATCH 040/113] Improve learning flow with contextual transitions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part 2: - Add brief context before composability section explaining why it's needed for nf-core template integration - Clarify that composable workflows must be called from parent workflow Part 3: - Add motivation for replacing custom module with nf-core module - Emphasize benefits: community testing, maintenance, learning value These additions provide smoother conceptual transitions without duplicating content covered in side quests. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/02_rewrite_hello.md | 3 +++ docs/hello_nf-core/03_use_module.md | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index cc401e2f8d..11795f0792 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -277,6 +277,9 @@ Learn how to make a simple workflow composable as a prelude to making it nf-core ## 2. Make the original Hello Nextflow workflow composable +Before we can integrate our workflow into the nf-core scaffold, we need to make it **composable**. +A composable workflow must be called from a parent workflow—it cannot run on its own—which is exactly how the nf-core template is structured. + We provide you with a clean, fully functional copy of the completed Hello Nextflow workflow in the directory `original-hello` along with its modules and the default CSV file it expects to use as input. ```bash diff --git a/docs/hello_nf-core/03_use_module.md b/docs/hello_nf-core/03_use_module.md index df04897494..d99135553a 100644 --- a/docs/hello_nf-core/03_use_module.md +++ b/docs/hello_nf-core/03_use_module.md @@ -25,7 +25,9 @@ In this section, we'll replace the custom `collectGreetings` module with the `ca First, let's learn how to find, install, and use an existing nf-core module in our pipeline. -The `collectGreetings` process in our pipeline uses the Unix `cat` command to concatenate multiple greeting files into one. This is a perfect use case for the nf-core `cat/cat` module, which is designed specifically for concatenating files. +The `collectGreetings` process in our pipeline uses the Unix `cat` command to concatenate multiple greeting files into one. +This is a perfect use case for the nf-core `cat/cat` module, which is designed specifically for concatenating files. +Replacing our custom module with an nf-core module gives us the benefit of community testing and maintenance, while also demonstrating the module system. !!! note "Module naming convention" From b03393638a9ab70c419c7116ea6d9d587b354e78 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 19:51:27 +0000 Subject: [PATCH 041/113] Reframe Part 4 as 'Make an nf-core module' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renamed file: 04_adapt_module.md → 04_make_module.md Changes to emphasize module creation over adaptation: Title and Introduction: - Changed from "Adapt local modules" to "Make an nf-core module" - Explicitly acknowledge nf-core modules create exists - Frame manual approach as "learning by doing" for deeper understanding - Preview that official tooling will be shown at the end Section 1: - Renamed to "Transform cowpy into an nf-core module" - Added intro emphasizing transformation into community standards Section 2: - Updated: "Use nf-core tooling to create modules" - Better transition connecting manual learning to practical tooling - Enhanced code comments: "Pattern 1/2/3" linking to what was learned - Added: "Notice how all three patterns you applied manually are there!" Takeaway: - Opens with: "You now know how to create nf-core modules!" - Emphasizes manual learning equips you to work with, debug, and create - Reinforces learning-by-doing → practical tooling progression Cross-references: - Updated Part 3 link text to match new framing - Updated Part 5 prerequisite note with new title - Updated mkdocs.yml navigation The reframing maintains all technical content while shifting emphasis from "fixing local modules" to "learning to create nf-core modules." 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/03_use_module.md | 2 +- .../{04_adapt_module.md => 04_make_module.md} | 43 ++++++++++++------- docs/hello_nf-core/05_input_validation.md | 2 +- mkdocs.yml | 2 +- 4 files changed, 30 insertions(+), 19 deletions(-) rename docs/hello_nf-core/{04_adapt_module.md => 04_make_module.md} (90%) diff --git a/docs/hello_nf-core/03_use_module.md b/docs/hello_nf-core/03_use_module.md index d99135553a..14df901813 100644 --- a/docs/hello_nf-core/03_use_module.md +++ b/docs/hello_nf-core/03_use_module.md @@ -607,4 +607,4 @@ Adapt your local modules to follow nf-core conventions. --- -In [Part 4](./04_adapt_module.md), we'll adapt your local `cowpy` module to follow nf-core conventions. +In [Part 4](./04_make_module.md), we'll show you how to make an nf-core module. diff --git a/docs/hello_nf-core/04_adapt_module.md b/docs/hello_nf-core/04_make_module.md similarity index 90% rename from docs/hello_nf-core/04_adapt_module.md rename to docs/hello_nf-core/04_make_module.md index 8b958d7345..afd188ddd5 100644 --- a/docs/hello_nf-core/04_adapt_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -1,12 +1,18 @@ -# Part 4: Adapt local modules to nf-core conventions +# Part 4: Make an nf-core module -In this fourth part of the Hello nf-core training course, we show you how to adapt your local modules to follow nf-core conventions. +In this fourth part of the Hello nf-core training course, we show you how to create an nf-core module by learning the key conventions that make modules portable and maintainable. -Now that we've successfully integrated the nf-core `CAT_CAT` module in [Part 3](./03_use_module.md), let's adapt our local `cowpy` module to follow the same nf-core patterns. We'll do this incrementally, introducing one pattern at a time: +The nf-core project provides a command (`nf-core modules create`) that generates properly structured module templates automatically. +However, for teaching purposes, we're going to **learn by doing**: transforming our local `cowpy` module into an nf-core-style module step-by-step. +This hands-on approach will help you understand the patterns deeply, making you better equipped to work with nf-core modules in practice. -1. First, we'll update `cowpy` to accept and propagate metadata tuples -2. Then, we'll simplify its interface using `ext.args` -3. Finally, we'll add configurable output naming with `ext.prefix` +We'll apply three essential nf-core patterns incrementally: + +1. **Metadata tuples**: Accept and propagate sample metadata through the workflow +2. **`ext.args`**: Simplify the module interface by handling optional arguments via configuration +3. **`ext.prefix`**: Standardize output file naming with configurable prefixes + +Once you understand these patterns, we'll show you how to use the official nf-core tooling to create modules efficiently. !!! note @@ -23,7 +29,9 @@ Now that we've successfully integrated the nf-core `CAT_CAT` module in [Part 3]( --- -## 1. Adapt local modules to nf-core conventions +## 1. Transform cowpy into an nf-core module + +In this section, we'll apply nf-core conventions to our local `cowpy` module, transforming it into a module that follows community standards. ### 1.1. Update cowpy to use metadata tuples @@ -490,9 +498,10 @@ However, you might want to keep it as a reference for understanding the differen --- -## 2. Creating modules with nf-core tooling +## 2. Use nf-core tooling to create modules -In this tutorial, we manually adapted the `cowpy` module step-by-step to teach the nf-core conventions. However, **in practice, you'd use the nf-core tooling to generate properly structured modules from the start**. +Now that you understand the nf-core module patterns by applying them manually, let's look at how you'd create modules in practice. +The nf-core project provides the `nf-core modules create` command that generates properly structured module templates with all these patterns built in from the start. ### 2.1. Using nf-core modules create @@ -529,7 +538,7 @@ modules/nf-core/cowpy/ └── tags.yml # Test tags ``` -The generated `main.nf` includes all the patterns automatically: +The generated `main.nf` includes all the patterns you just learned: ```groovy process COWPY { @@ -540,15 +549,15 @@ process COWPY { container "..." input: - tuple val(meta), path(input_file) // Metadata tuples ✓ + tuple val(meta), path(input_file) // Pattern 1: Metadata tuples ✓ output: tuple val(meta), path("${prefix}.*"), emit: output // Metadata propagation ✓ path "versions.yml" , emit: versions script: - def args = task.ext.args ?: '' // ext.args pattern ✓ - def prefix = task.ext.prefix ?: "${meta.id}" // ext.prefix pattern ✓ + def args = task.ext.args ?: '' // Pattern 2: ext.args ✓ + def prefix = task.ext.prefix ?: "${meta.id}" // Pattern 3: ext.prefix ✓ """ # TODO: Add your command here cowpy $args < $input_file > ${prefix}.txt @@ -561,7 +570,8 @@ process COWPY { } ``` -You fill in the command logic and the module is ready to test! +Notice how all three patterns you applied manually are already there! +You just fill in the command logic and the module is ready to test. ### 2.2. Contributing modules back to nf-core @@ -600,14 +610,15 @@ For detailed instructions, see the [nf-core components tutorial](https://nf-co.r ## Takeaway -You now understand the key patterns that make nf-core modules portable and maintainable: +You now know how to create nf-core modules! You learned the three key patterns that make modules portable and maintainable: - **Metadata tuples** track sample information through the workflow - **`ext.args`** simplifies module interfaces by handling optional arguments via configuration - **`ext.prefix`** standardizes output file naming - **Centralized configuration** in `modules.config` keeps modules reusable -You learned these patterns by manually adapting a local module, which gives you the foundation to understand and debug modules. In practice, you'll use `nf-core modules create` to generate properly structured modules from the start. +By transforming `cowpy` step-by-step, you developed a deep understanding of these patterns—making you equipped to work with, debug, and create nf-core modules. +In practice, you'll use `nf-core modules create` to generate properly structured modules with these patterns built in from the start. Finally, you learned how to contribute modules to the nf-core community, making tools available to researchers worldwide while benefiting from ongoing community maintenance. diff --git a/docs/hello_nf-core/05_input_validation.md b/docs/hello_nf-core/05_input_validation.md index 248095e139..3eef5e415e 100644 --- a/docs/hello_nf-core/05_input_validation.md +++ b/docs/hello_nf-core/05_input_validation.md @@ -54,7 +54,7 @@ Both types of validation happen **before** the pipeline executes any processes, !!! note - This section assumes you have completed [Part 4: Adapt local modules to nf-core conventions](./04_adapt_module.md) and have a working `core-hello` pipeline with adapted nf-core modules. + This section assumes you have completed [Part 4: Make an nf-core module](./04_make_module.md) and have a working `core-hello` pipeline with nf-core-style modules. If you didn't complete Part 4 or want to start fresh for this section, you can use the `core-hello-part4` solution as your starting point: diff --git a/mkdocs.yml b/mkdocs.yml index 3f27c7c0cf..0288469261 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -33,7 +33,7 @@ nav: - hello_nf-core/01_run_demo.md - hello_nf-core/02_rewrite_hello.md - hello_nf-core/03_use_module.md - - hello_nf-core/04_adapt_module.md + - hello_nf-core/04_make_module.md - hello_nf-core/05_input_validation.md - hello_nf-core/survey.md - hello_nf-core/next_steps.md From 6896d72c262909a265535e9ad6c9390c39f2959f Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 19:53:57 +0000 Subject: [PATCH 042/113] Highlight additional nf-core conventions in template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added note in section 2.1 explaining conventions that appear in the nf-core modules create template but weren't covered in the hands-on section: - tag "$meta.id" - for process tracking in logs - label 'process_single' - for resource configuration - versions.yml output - for reproducibility tracking - UPPERCASE process names - naming convention This helps students understand that the template provides more than just the three patterns they learned manually, preparing them for working with real nf-core modules. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/04_make_module.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index afd188ddd5..0b89f832ad 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -571,7 +571,15 @@ process COWPY { ``` Notice how all three patterns you applied manually are already there! -You just fill in the command logic and the module is ready to test. +The template also includes several additional nf-core conventions that we didn't cover in the hands-on section: + +- **`tag "$meta.id"`**: Adds sample ID to process names in logs for easier tracking +- **`label 'process_single'`**: Resource label for configuring CPU/memory requirements +- **`versions.yml` output**: Captures software version information for reproducibility +- **Process name `COWPY`**: Uppercase naming convention for nf-core modules + +These additional conventions make modules more maintainable and provide better visibility into pipeline execution. +You just fill in the command logic and the module is ready to test! ### 2.2. Contributing modules back to nf-core From f05fa10f52a6b736cad6f8c9984d0d6c6608b31c Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 19:58:28 +0000 Subject: [PATCH 043/113] Clarify working within core-hello pipeline context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added explicit reminders that students are working within their core-hello pipeline: Introduction: - Changed "our local cowpy module" to "the local cowpy module in your core-hello pipeline" to establish context early Section 1: - Updated intro to specify "in your core-hello pipeline" - Added prominent tip box reminding students to cd into core-hello - Provides explicit command: cd core-hello This addresses potential confusion about where students should be running commands and editing files, especially for those starting from a solution or returning after a break. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/04_make_module.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index 0b89f832ad..14a27e095c 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -3,7 +3,7 @@ In this fourth part of the Hello nf-core training course, we show you how to create an nf-core module by learning the key conventions that make modules portable and maintainable. The nf-core project provides a command (`nf-core modules create`) that generates properly structured module templates automatically. -However, for teaching purposes, we're going to **learn by doing**: transforming our local `cowpy` module into an nf-core-style module step-by-step. +However, for teaching purposes, we're going to **learn by doing**: transforming the local `cowpy` module in your `core-hello` pipeline into an nf-core-style module step-by-step. This hands-on approach will help you understand the patterns deeply, making you better equipped to work with nf-core modules in practice. We'll apply three essential nf-core patterns incrementally: @@ -31,7 +31,15 @@ Once you understand these patterns, we'll show you how to use the official nf-co ## 1. Transform cowpy into an nf-core module -In this section, we'll apply nf-core conventions to our local `cowpy` module, transforming it into a module that follows community standards. +In this section, we'll apply nf-core conventions to the local `cowpy` module in your `core-hello` pipeline, transforming it into a module that follows community standards. + +!!! tip "Working directory" + + Make sure you're in the `core-hello` directory (your pipeline root) for all the commands and file edits in this section. + + ```bash + cd core-hello + ``` ### 1.1. Update cowpy to use metadata tuples From 782fdc67c9d42c10cf3d277e1fee47ad4c8f3419 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 20:08:01 +0000 Subject: [PATCH 044/113] Minor changes to nf-core part 4 --- docs/hello_nf-core/04_make_module.md | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index 14a27e095c..a91e5fde17 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -111,14 +111,14 @@ Now update the workflow to pass the tuple directly instead of extracting the fil === "After" - ```groovy title="core-hello/workflows/hello.nf" linenums="39" hl_lines="2" + ```groovy title="core-hello/workflows/hello.nf" linenums="43" hl_lines="2" // generate ASCII art of the greetings with cowpy cowpy(CAT_CAT.out.file_out, params.character) ``` === "Before" - ```groovy title="core-hello/workflows/hello.nf" linenums="39" hl_lines="2-4" + ```groovy title="core-hello/workflows/hello.nf" linenums="43" hl_lines="2-4" // generate ASCII art of the greetings with cowpy // Extract the file from the tuple since cowpy doesn't use metadata yet ch_for_cowpy = CAT_CAT.out.file_out.map{ meta, file -> file } @@ -129,7 +129,7 @@ Also update the emit block to use the named emit: === "After" - ```groovy title="core-hello/workflows/hello.nf" linenums="58" hl_lines="2" + ```groovy title="core-hello/workflows/hello.nf" linenums="60" hl_lines="2" emit: cowpy_hellos = cowpy.out.cowpy_output versions = ch_versions // channel: [ path(versions.yml) ] @@ -137,7 +137,7 @@ Also update the emit block to use the named emit: === "Before" - ```groovy title="core-hello/workflows/hello.nf" linenums="58" hl_lines="2" + ```groovy title="core-hello/workflows/hello.nf" linenums="60" hl_lines="2" emit: cowpy_hellos = cowpy.out versions = ch_versions // channel: [ path(versions.yml) ] @@ -149,7 +149,16 @@ Test the workflow to ensure metadata flows through correctly: nextflow run . --outdir core-hello-results -profile test,docker --validate_params false ``` -The pipeline should run successfully with metadata now flowing from `CAT_CAT` through `cowpy`. +The pipeline should run successfully with metadata now flowing from `CAT_CAT` through `cowpy`: + +```console title="Output (excerpt)" +executor > local (8) +[b2/4cf633] CORE_HELLO:HELLO:sayHello (2) [100%] 3 of 3 ✔ +[ed/ef4d69] CORE_HELLO:HELLO:convertToUpper (3) [100%] 3 of 3 ✔ +[2d/32c93e] CORE_HELLO:HELLO:CAT_CAT (test) [100%] 1 of 1 ✔ +[da/6f3246] CORE_HELLO:HELLO:cowpy [100%] 1 of 1 ✔ +-[core/hello] Pipeline completed successfully- +``` ### 1.2. Simplify the interface with ext.args @@ -182,11 +191,11 @@ Let's update the cowpy module to use `ext.args` instead of the `character` input - **Consistency**: All modules follow the same publishing pattern - **No conflicts**: Avoids having two separate publishing locations (local and centralized) -Open [core-hello/modules/local/cowpy.nf](core-hello/modules/local/cowpy.nf): +Open `modules/local/cowpy.nf`: === "After" - ```groovy title="core-hello/modules/local/cowpy.nf" linenums="1" hl_lines="16 18" + ```groovy title="modules/local/cowpy.nf" linenums="1" hl_lines="16 18" #!/usr/bin/env nextflow // Generate ASCII art with cowpy (https://github.com/jeffbuttars/cowpy) From f4c06d5f1e35275e160f5f574ce2af5cfdda8463 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 20:12:27 +0000 Subject: [PATCH 045/113] Clarify ext.args usage and flexibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed inaccurate description of when to use ext.args: Before: Implied ext.args is only for "optional" arguments and suggested a hard distinction between channel inputs and ext.args. After: - Clarified ext.args is for passing command-line arguments through configuration rather than as process inputs - Added note explaining ext.args can be dynamic and sample-specific using closures that access metadata - Example: ext.args = { meta.single_end ? '--single' : '--paired' } - Updated benefits to mention "including sample-specific values" This correction is important because: 1. ext.args is commonly used with closures for dynamic, per-sample args 2. The distinction isn't "mandatory vs optional" but rather "data inputs vs tool configuration" 3. Prevents students from thinking ext.args is limited to static values 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/04_make_module.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index a91e5fde17..db88b3c10a 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -164,19 +164,23 @@ executor > local (8) Now let's address another nf-core pattern: simplifying module interfaces by using `ext.args` for optional command-line arguments. -Currently, our `cowpy` module requires the `character` parameter to be passed as a separate input. While this works, nf-core modules follow a convention of keeping interfaces minimal - only essential inputs (metadata and files) should be declared. Optional tool arguments are instead passed via configuration. +Currently, our `cowpy` module requires the `character` parameter to be passed as a separate input. While this works, nf-core modules use a different approach for **optional tool arguments**: instead of adding input parameters for every possible tool option, they use `ext.args` to pass these via configuration. This keeps the module interface focused on essential data inputs (metadata and files), while tool-specific options are handled through configuration. #### Understanding ext.args -The `task.ext.args` pattern is an nf-core convention for passing optional command-line arguments to tools. Instead of adding multiple input parameters for every possible tool option, nf-core modules accept optional arguments through the `ext.args` configuration directive. +The `task.ext.args` pattern is an nf-core convention for passing command-line arguments to tools through configuration rather than as process inputs. Instead of adding input parameters for tool options, nf-core modules accept arguments through the `ext.args` configuration directive. + +!!! note "ext.args can be dynamic" + + While `ext.args` is configured outside the module, it can access metadata to provide sample-specific values using closures. For example: `ext.args = { meta.single_end ? '--single' : '--paired' }` Benefits of this approach: -- **Minimal interface**: The module only requires essential inputs (metadata and files) -- **Flexibility**: Users can specify any tool arguments via configuration +- **Clean interface**: The module focuses on essential data inputs (metadata and files) +- **Flexibility**: Users can specify tool arguments via configuration, including sample-specific values - **Consistency**: All nf-core modules follow this pattern -- **Portability**: Modules can be reused in other pipelines without expecting specific parameter names -- **No workflow changes**: Adding new tool options doesn't require updating workflow code +- **Portability**: Modules can be reused without hardcoded tool options +- **No workflow changes**: Adding or changing tool options doesn't require updating workflow code #### Update the module From da5c6a4de4da428934d527f76e3127dee02afb3d Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 20:19:43 +0000 Subject: [PATCH 046/113] Elevate and clarify centralized publishDir explanation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made the publishDir centralization more prominent and clear: Before: Mentioned in a collapsed note, easy to miss After: Full section explaining the concept properly Key improvements: 1. New section header: "Centralized publishing configuration" 2. Shows the actual default publishDir config from modules.config: - path template using task.process - mode from params - saveAs to filter versions.yml 3. Explains the default behavior: auto-publishes to outdir// 4. Clarifies customization is still possible via withName: blocks 5. Clear benefits: - Useful default that "just works" - Easy customization in config (not module code) - Portable modules without hardcoded paths This is a fundamental nf-core convention that deserves proper emphasis, not just a side note. Students need to understand why removing publishDir from the module is the right approach. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/04_make_module.md | 36 +++++++++++++++++++++------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index db88b3c10a..373a3c0633 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -182,18 +182,38 @@ Benefits of this approach: - **Portability**: Modules can be reused without hardcoded tool options - **No workflow changes**: Adding or changing tool options doesn't require updating workflow code -#### Update the module +#### Centralized publishing configuration + +Before we update the module to use `ext.args`, let's address an important nf-core convention: **modules should not contain hardcoded `publishDir` directives**. + +Currently, our `cowpy` module has `publishDir 'results', mode: 'copy'` which hardcodes the output location. +In nf-core pipelines, publishing is instead configured in `conf/modules.config`. -Let's update the cowpy module to use `ext.args` instead of the `character` input parameter. We'll also remove the local `publishDir` directive to rely on the centralized configuration in `modules.config`. +The nf-core template includes a **default publishDir configuration** that applies to all processes: -!!! note "Why remove the local publishDir?" +```groovy +process { + publishDir = [ + path: { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] +} +``` - nf-core modules should not contain hardcoded `publishDir` directives. Instead, publishing is configured centrally in `conf/modules.config`. This provides several benefits: +This default automatically publishes outputs to `${params.outdir}//` for every process. +Individual processes can customize their publishing using `withName:` blocks in the same config file. + +Benefits of this approach: + +- **Single source of truth**: All publishing configuration lives in `modules.config` +- **Useful default**: Processes work out-of-the-box without per-module configuration +- **Easy customization**: Override publishing behavior in config, not in module code +- **Portable modules**: Modules don't hardcode output locations + +#### Update the module - - **Single source of truth**: All output paths are configured in one place - - **Flexibility**: Users can easily customize where outputs are published - - **Consistency**: All modules follow the same publishing pattern - - **No conflicts**: Avoids having two separate publishing locations (local and centralized) +Now let's update the cowpy module to use `ext.args` and remove the local `publishDir`. Open `modules/local/cowpy.nf`: From ba7585a7f1f4b6bcb6f1ea36021fb69e29b53b91 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 20:20:19 +0000 Subject: [PATCH 047/113] tweak --- docs/hello_nf-core/04_make_module.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index 373a3c0633..cd1dffedc9 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -164,7 +164,7 @@ executor > local (8) Now let's address another nf-core pattern: simplifying module interfaces by using `ext.args` for optional command-line arguments. -Currently, our `cowpy` module requires the `character` parameter to be passed as a separate input. While this works, nf-core modules use a different approach for **optional tool arguments**: instead of adding input parameters for every possible tool option, they use `ext.args` to pass these via configuration. This keeps the module interface focused on essential data inputs (metadata and files), while tool-specific options are handled through configuration. +Currently, our `cowpy` module requires the `character` parameter to be passed as a separate input. While this works, nf-core modules use a different approach for **optional tool arguments**: instead of adding input parameters for every possible tool option, they use `ext.args` to pass these via configuration. This keeps the module interface focused on essential data inputs (metadata plus mandatory value and file inputs), while tool-specific options are handled through configuration. #### Understanding ext.args From 95677569883e3691bff253c8f35d059ef93e27d2 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 20:37:06 +0000 Subject: [PATCH 048/113] Add subtle Babylon 5 reference with kosh character MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed the test example to use 'kosh' as the cowpy character with a subtle hint about its enigmatic nature. For B5 fans: You know exactly who Kosh is. Understanding dawns. Yes. For everyone else: It's just an unusual cowsay character option. This maintains accessibility while adding a fun easter egg for those in the know. The word "enigmatic" is appropriate whether or not you recognize the reference. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/04_make_module.md | 45 ++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index cd1dffedc9..9176756e18 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -282,7 +282,7 @@ The module interface is now simpler - it only accepts the essential metadata and Now we need to configure the `ext.args` to pass the character option. This allows us to keep the module interface simple while still providing the character option at the pipeline level. -Open [core-hello/conf/modules.config](core-hello/conf/modules.config) and add the cowpy configuration: +Open `conf/modules.config` and add the cowpy configuration: === "After" @@ -325,7 +325,7 @@ Key points: Since the cowpy module no longer requires the `character` parameter as an input, we need to update the workflow call. -Open [core-hello/workflows/hello.nf](core-hello/workflows/hello.nf) and update the cowpy call: +Open `workflows/hello.nf` and update the cowpy call: === "After" @@ -345,22 +345,21 @@ The workflow code is now cleaner - we don't need to pass `params.character` dire #### Test -Test that the workflow still works with the ext.args configuration. Let's specify a different character to verify the configuration is working: +Test that the workflow still works with the ext.args configuration. Let's specify a different character to verify the configuration is working (using `kosh`, one of the more... enigmatic options): ```bash -nextflow run . --outdir core-hello-results -profile test,docker --validate_params false --character cow +nextflow run . --outdir core-hello-results -profile test,docker --validate_params false --character kosh ``` The pipeline should run successfully. In the output, look for the cowpy process execution line which will show something like: ```console title="Output (excerpt)" -[f3/abc123] process > CORE_HELLO:HELLO:cowpy [100%] 1 of 1 ✔ -``` +[bd/0abaf8] CORE_HELLO:HELLO:cowpy [100%] 1 of 1 ✔``` -Now let's verify that the `ext.args` configuration actually passed the character argument to the cowpy command. Use the task hash (the `f3/abc123` part) to inspect the `.command.sh` file in the work directory: +Now let's verify that the `ext.args` configuration actually passed the character argument to the cowpy command. Use the task hash (the `bd/0abaf8` part) to inspect the `.command.sh` file in the work directory: ```bash -cat work/f3/abc123*/command.sh +cat work/bd/0abaf8*/.command.sh ``` You should see the cowpy command with the `-c cow` argument: @@ -368,11 +367,39 @@ You should see the cowpy command with the `-c cow` argument: ```console title="Output" #!/usr/bin/env bash ... -cat test.txt | cowpy -c cow > cowpy-test.txt +cat test.txt | cowpy -c kosh > cowpy-test.txt ``` This confirms that `task.ext.args` successfully passed the character parameter through the configuration rather than requiring it as a process input. +We can also check the output: + +```bash +cat work/bd/0abaf8*/cowpy-test.txt +``` + +```console title="Output" +/ HELLO \ +| HOLà | +\ BONJOUR / + --------- + \ + \ + \ + ___ _____ ___ + / \ / /| / \ +| | / / | | | +| | /____/ | | | +| | | | | | | +| | | {} | / | | +| | |____|/ | | +| | |==| | | +| \___________/ | +| | +| | +``` + + ### 1.3. Add configurable output naming with ext.prefix There's one more nf-core pattern we can apply: using `ext.prefix` for configurable output file naming. From f0a2dff490c91e0ada52bcd1f6f193c678f338e7 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 20:57:04 +0000 Subject: [PATCH 049/113] Enrich nf-core modules create section with environment/container details MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated section 2.1 based on actual command behavior: Interactive prompts: - Changed from generic list to qualitative description - Explains tool looks up info from Bioconda and bio.tools automatically - Describes the three key prompts: author, resource label, meta requirement - Emphasizes tool handles complexity, user focuses on logic Generated structure: - Shows actual output format: "INFO Created following files:" - Clarifies creates in modules/local/ (or modules/nf-core/ in modules repo) - Lists all 4 files with clear purpose explanations New section: Completing environment and container setup - Explains cowpy is in conda-forge (not Bioconda) - Shows how to write environment.yml for conda-forge packages - Introduces Seqera Containers for building containers from Conda packages - Tip box explaining Bioconda (BioContainers) vs conda-forge (Seqera Containers) - Links to https://seqera.io/containers/ This addresses the real-world scenario that not all tools are in Bioconda, and shows students how to handle conda-forge packages using Seqera Containers for containerization. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/04_make_module.md | 80 ++++++++++++++++++++-------- 1 file changed, 57 insertions(+), 23 deletions(-) diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index 9176756e18..446e4cd786 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -379,6 +379,7 @@ cat work/bd/0abaf8*/cowpy-test.txt ``` ```console title="Output" + _________ / HELLO \ | HOLà | \ BONJOUR / @@ -419,7 +420,7 @@ Benefits: Let's update the cowpy module to use `ext.prefix` for output file naming. -Open [core-hello/modules/local/cowpy.nf](core-hello/modules/local/cowpy.nf): +Open `modules/local/cowpy.nf` and change as follows: === "After" @@ -484,7 +485,7 @@ Note that the local `publishDir` has already been removed in the previous step, To maintain the same output file naming as before (`cowpy-.txt`), we can configure `ext.prefix` in modules.config. -Update [core-hello/conf/modules.config](core-hello/conf/modules.config): +Update `conf/modules.config`: === "After" @@ -514,13 +515,14 @@ Note that we use a closure (`{ "cowpy-${meta.id}" }`) which has access to `meta` Test the workflow once more: ```bash +rm -rf core-hello-results nextflow run . --outdir core-hello-results -profile test,docker --validate_params false ``` Check the outputs: ```bash -ls results/ +ls core-hello-results/cowpy/ ``` You should see the cowpy output files with the same naming as before: `cowpy-test.txt` (based on the batch name). This demonstrates how `ext.prefix` allows you to maintain your preferred naming convention while keeping the module interface flexible. @@ -546,7 +548,7 @@ Clean up by optionally removing the now-unused local module. Now that we're using the nf-core `cat/cat` module, the local `collectGreetings` module is no longer needed. -Remove or comment out the import line for `collectGreetings` in [core-hello/workflows/hello.nf](core-hello/workflows/hello.nf): +Remove or comment out the import line for `collectGreetings` in `workflows/hello.nf`: ```groovy title="core-hello/workflows/hello.nf" linenums="10" include { sayHello } from '../modules/local/sayHello.nf' @@ -573,12 +575,7 @@ The nf-core project provides the `nf-core modules create` command that generates ### 2.1. Using nf-core modules create -The `nf-core modules create` command generates a module template that already follows all the conventions you've learned: - -```bash -# In the nf-core/modules repository -nf-core modules create tool/subtool -``` +The `nf-core modules create` command generates a module template that already follows all the conventions you've learned. For example, to create the `cowpy` module: @@ -586,26 +583,36 @@ For example, to create the `cowpy` module: nf-core modules create cowpy ``` -The command will interactively ask for: +The command runs interactively, guiding you through the setup. It automatically looks up tool information from package repositories like Bioconda and bio.tools to pre-populate metadata. + +You'll be prompted for: + +- **Author information**: Your GitHub username for attribution +- **Resource label**: The computational requirements (e.g., `process_single` for lightweight tools, `process_high` for demanding ones) +- **Metadata requirement**: Whether the module needs sample-specific information via a `meta` map (usually yes for data processing modules) -- Tool name and optional subtool/subcommand -- Author information -- Resource requirements (CPU/memory estimates) +The tool handles the complexity of finding package information and setting up the structure, allowing you to focus on implementing the tool's specific logic. #### What gets generated -The tool creates a complete module structure: +The tool creates a complete module structure in `modules/local/` (or `modules/nf-core/` if you're in the nf-core/modules repository): ```console -modules/nf-core/cowpy/ -├── main.nf # Process definition with TODO comments -├── meta.yml # Module documentation -├── environment.yml # Conda environment -└── tests/ - ├── main.nf.test # nf-test test cases - └── tags.yml # Test tags +INFO Created component template: 'cowpy' +INFO Created following files: + modules/local/cowpy/main.nf + modules/local/cowpy/meta.yml + modules/local/cowpy/environment.yml + modules/local/cowpy/tests/main.nf.test ``` +Each file serves a specific purpose: + +- **`main.nf`**: Process definition with all the nf-core patterns built in +- **`meta.yml`**: Module documentation describing inputs, outputs, and the tool +- **`environment.yml`**: Conda environment specification for dependencies +- **`tests/main.nf.test`**: nf-test test cases to validate the module works + The generated `main.nf` includes all the patterns you just learned: ```groovy @@ -647,7 +654,34 @@ The template also includes several additional nf-core conventions that we didn't - **Process name `COWPY`**: Uppercase naming convention for nf-core modules These additional conventions make modules more maintainable and provide better visibility into pipeline execution. -You just fill in the command logic and the module is ready to test! + +#### Completing the environment and container setup + +In the case of cowpy, the tool warned that it couldn't find the package in Bioconda (the primary channel for bioinformatics tools). +However, cowpy is available in conda-forge, so you would complete the `environment.yml` like this: + +```yaml title="modules/local/cowpy/environment.yml" +name: cowpy +channels: + - conda-forge +dependencies: + - cowpy=1.1.5 +``` + +For the container, you can use [Seqera Containers](https://seqera.io/containers/) to automatically build a container from any Conda package, including conda-forge packages: + +```groovy +container "community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273" +``` + +!!! tip "Bioconda vs conda-forge packages" + + - **Bioconda packages**: Automatically get BioContainers built, providing ready-to-use containers + - **conda-forge packages**: Can use Seqera Containers to build containers on-demand from the Conda recipe + + Most bioinformatics tools are in Bioconda, but for conda-forge tools, Seqera Containers provides an easy solution for containerization. + +Once you've completed the environment setup and filled in the command logic, the module is ready to test! ### 2.2. Contributing modules back to nf-core From 9abb837b1ac590c712e64cb462efa27a31d825d8 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 21:08:13 +0000 Subject: [PATCH 050/113] Use --empty-template flag for cleaner module creation example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated the nf-core modules create command to include --empty-template: ```bash nf-core modules create --empty-template cowpy ``` Benefits: - Creates a minimal template without extra boilerplate - Makes it easier for learners to see the essential structure - Avoids overwhelming students with auto-generated comments and TODOs - Cleaner starting point for understanding the patterns Added brief explanation that this flag provides a clean starter template, making the essential nf-core conventions more visible. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/04_make_module.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index 446e4cd786..59c28c5a26 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -577,12 +577,14 @@ The nf-core project provides the `nf-core modules create` command that generates The `nf-core modules create` command generates a module template that already follows all the conventions you've learned. -For example, to create the `cowpy` module: +For example, to create the `cowpy` module with a minimal template: ```bash -nf-core modules create cowpy +nf-core modules create --empty-template cowpy ``` +The `--empty-template` flag creates a clean starter template without extra boilerplate, making it easier to see the essential structure. + The command runs interactively, guiding you through the setup. It automatically looks up tool information from package repositories like Bioconda and bio.tools to pre-populate metadata. You'll be prompted for: @@ -598,12 +600,12 @@ The tool handles the complexity of finding package information and setting up th The tool creates a complete module structure in `modules/local/` (or `modules/nf-core/` if you're in the nf-core/modules repository): ```console -INFO Created component template: 'cowpy' -INFO Created following files: - modules/local/cowpy/main.nf - modules/local/cowpy/meta.yml - modules/local/cowpy/environment.yml - modules/local/cowpy/tests/main.nf.test +modules/local/cowpy +├── environment.yml +├── main.nf +├── meta.yml +└── tests + └── main.nf.test ``` Each file serves a specific purpose: From 45995973fd057f3ff4f17f05df9e3281fb179b7c Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 21:11:48 +0000 Subject: [PATCH 051/113] Update module template to match actual --empty-template output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced the example template with the actual output from: nf-core modules create --empty-template cowpy Key changes: Template now shows: - Fixed typo: "pprocess" → "process" - Actual container placeholder structure for Singularity/Docker - when: block for conditional execution (task.ext.when) - stub: block for fast mock testing - More realistic placeholder comments Enhanced bullet list to include newly visible features: - when: block - conditional execution via task.ext.when - stub: block - fast mock implementation for testing without running tool - Container placeholders - template structure for both engines Added syntax highlighting to emphasize: - Lines 10-11: Metadata tuple input (Pattern 1) - Line 19: when block - Lines 25-26: ext.args and ext.prefix (Patterns 2 & 3) - Lines 40-41: stub block versions This gives students an accurate view of what they'll actually see when running the command, including important features like stub blocks that enable fast pipeline testing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/04_make_module.md | 45 ++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index 59c28c5a26..b465560b19 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -615,33 +615,52 @@ Each file serves a specific purpose: - **`environment.yml`**: Conda environment specification for dependencies - **`tests/main.nf.test`**: nf-test test cases to validate the module works -The generated `main.nf` includes all the patterns you just learned: +The generated `main.nf` includes all the patterns you just learned, plus some additional features: -```groovy +```groovy title="modules/local/cowpy/main.nf" hl_lines="10-11 19 25-26 40-41" process COWPY { tag "$meta.id" label 'process_single' conda "${moduleDir}/environment.yml" - container "..." + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/YOUR-TOOL-HERE': + 'biocontainers/YOUR-TOOL-HERE' }" input: - tuple val(meta), path(input_file) // Pattern 1: Metadata tuples ✓ + tuple val(meta), path(input) // Pattern 1: Metadata tuples ✓ output: - tuple val(meta), path("${prefix}.*"), emit: output // Metadata propagation ✓ - path "versions.yml" , emit: versions + tuple val(meta), path("*"), emit: output + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when script: - def args = task.ext.args ?: '' // Pattern 2: ext.args ✓ - def prefix = task.ext.prefix ?: "${meta.id}" // Pattern 3: ext.prefix ✓ + def args = task.ext.args ?: '' // Pattern 2: ext.args ✓ + def prefix = task.ext.prefix ?: "${meta.id}" // Pattern 3: ext.prefix ✓ + + """ + // Add your tool command here + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + cowpy: \$(cowpy --version) + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + """ - # TODO: Add your command here - cowpy $args < $input_file > ${prefix}.txt + echo $args + touch ${prefix}.txt cat <<-END_VERSIONS > versions.yml "${task.process}": - cowpy: \$(cowpy --version | sed 's/cowpy //') + cowpy: \$(cowpy --version) END_VERSIONS """ } @@ -652,8 +671,10 @@ The template also includes several additional nf-core conventions that we didn't - **`tag "$meta.id"`**: Adds sample ID to process names in logs for easier tracking - **`label 'process_single'`**: Resource label for configuring CPU/memory requirements +- **`when:` block**: Allows conditional execution via `task.ext.when` configuration +- **`stub:` block**: Provides a fast mock implementation for testing pipeline logic without running the actual tool - **`versions.yml` output**: Captures software version information for reproducibility -- **Process name `COWPY`**: Uppercase naming convention for nf-core modules +- **Container placeholders**: Template structure for Singularity and Docker containers These additional conventions make modules more maintainable and provide better visibility into pipeline execution. From fecc859f13d17d7ca3279ff2e30128feddff7242 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 21:15:21 +0000 Subject: [PATCH 052/113] Add reference to nf-test side quest for testing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added tip box after the generated files list that points students to the nf-test side quest for learning about module testing. The nf-core modules create command generates test files using nf-test, but we don't cover testing in this lesson. The tip box: - Explains the generated test file uses nf-test - Provides clear link to the nf-test side quest - Appears right after listing the test files, when students are naturally curious about them This helps students understand what the test files are for and where to learn more, without derailing the main lesson about module patterns. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/04_make_module.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index b465560b19..b8e3c29ea4 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -615,6 +615,10 @@ Each file serves a specific purpose: - **`environment.yml`**: Conda environment specification for dependencies - **`tests/main.nf.test`**: nf-test test cases to validate the module works +!!! tip "Learn more about testing" + + The generated test file uses nf-test, a testing framework for Nextflow pipelines and modules. To learn how to write and run these tests, see the [nf-test side quest](../../side_quests/nf_test/). + The generated `main.nf` includes all the patterns you just learned, plus some additional features: ```groovy title="modules/local/cowpy/main.nf" hl_lines="10-11 19 25-26 40-41" From 0cd2ae1bcd9cc7f7ad7a8690245ea2cfdc5b773f Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 21:17:53 +0000 Subject: [PATCH 053/113] Fix remaining file path links to use backticks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed markdown link syntax to simple backticks for file paths where the link doesn't make sense: - Line 48: [core-hello/modules/local/cowpy.nf](...) → `core-hello/modules/local/cowpy.nf` - Line 110: [core-hello/workflows/hello.nf](...) → `core-hello/workflows/hello.nf` These are file paths that students should open in their editor, not clickable navigation links within the training site. Backticks are more appropriate for indicating file paths to edit. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/04_make_module.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index b8e3c29ea4..b5ca728f68 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -45,7 +45,7 @@ In this section, we'll apply nf-core conventions to the local `cowpy` module in Currently, we're extracting the file from `CAT_CAT`'s output tuple to pass to `cowpy`. It would be better to have `cowpy` accept metadata tuples directly, allowing metadata to flow through the entire workflow. -Open [core-hello/modules/local/cowpy.nf](core-hello/modules/local/cowpy.nf) and modify it to accept metadata tuples: +Open `core-hello/modules/local/cowpy.nf` and modify it to accept metadata tuples: === "After" @@ -107,7 +107,7 @@ Key changes: 2. **Output**: Changed to emit a tuple with metadata: `tuple val(meta), path("cowpy-${input_file}"), emit: cowpy_output` 3. **Named emit**: Added `emit: cowpy_output` to give the output channel a descriptive name -Now update the workflow to pass the tuple directly instead of extracting the file. Open [core-hello/workflows/hello.nf](core-hello/workflows/hello.nf): +Now update the workflow to pass the tuple directly instead of extracting the file. Open `core-hello/workflows/hello.nf`: === "After" From 1e3002434ccf33ba463cc9c04be74b20aebbc5c7 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 21:21:32 +0000 Subject: [PATCH 054/113] Clarify distinction between channel inputs and ext.args MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed line 167 to better explain when to use channel inputs vs ext.args: Before: Confusing phrasing about "optional tool arguments" and "metadata plus mandatory value and file inputs" After: Clearer distinction: - Module interface focused on "essential data (files, metadata, and any mandatory per-sample parameters)" - these go via channels - "Tool configuration options" go through ext.args This clarifies that: - Files and metadata always go through channels - Mandatory per-sample parameters also go through channels - Tool configuration/options (whether optional or not) use ext.args The distinction is about data vs configuration, not mandatory vs optional. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/04_make_module.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index b5ca728f68..82f8b17a75 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -164,7 +164,7 @@ executor > local (8) Now let's address another nf-core pattern: simplifying module interfaces by using `ext.args` for optional command-line arguments. -Currently, our `cowpy` module requires the `character` parameter to be passed as a separate input. While this works, nf-core modules use a different approach for **optional tool arguments**: instead of adding input parameters for every possible tool option, they use `ext.args` to pass these via configuration. This keeps the module interface focused on essential data inputs (metadata plus mandatory value and file inputs), while tool-specific options are handled through configuration. +Currently, our `cowpy` module requires the `character` parameter to be passed as a separate input. While this works, nf-core modules use a different approach for **tool configuration arguments**: instead of adding input parameters for every tool option, they use `ext.args` to pass these via configuration. This keeps the module interface focused on essential data (files, metadata, and any mandatory per-sample parameters), while tool configuration options are handled through `ext.args`. #### Understanding ext.args From 07eaae880f846928d6c17199b0ecdf96c54ade23 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 21:24:52 +0000 Subject: [PATCH 055/113] Fix highlight --- docs/hello_nf-core/04_make_module.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index 82f8b17a75..d0cc39caa0 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -621,7 +621,7 @@ Each file serves a specific purpose: The generated `main.nf` includes all the patterns you just learned, plus some additional features: -```groovy title="modules/local/cowpy/main.nf" hl_lines="10-11 19 25-26 40-41" +```groovy title="modules/local/cowpy/main.nf" hl_lines="12 21 22" process COWPY { tag "$meta.id" label 'process_single' From 28d55a399ea662b9efad383ba97d399429c2a9fa Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 21:31:44 +0000 Subject: [PATCH 056/113] Fix highlight --- docs/hello_nf-core/04_make_module.md | 2 +- .../solutions/core-hello-part4/README.md | 75 -- .../core-hello-part4/assets/greetings.csv | 4 - .../core-hello-part4/assets/samplesheet.csv | 3 - .../core-hello-part4/assets/schema_input.json | 18 - .../core-hello-part4/conf/base.config | 62 - .../core-hello-part4/conf/modules.config | 26 - .../core-hello-part4/conf/test.config | 30 - .../solutions/core-hello-part4/docs/README.md | 8 - .../solutions/core-hello-part4/docs/output.md | 29 - .../solutions/core-hello-part4/docs/usage.md | 211 ---- .../solutions/core-hello-part4/main.nf | 82 -- .../solutions/core-hello-part4/modules.json | 36 - .../modules/local/collectGreetings.nf | 19 - .../modules/local/convertToUpper.nf | 20 - .../core-hello-part4/modules/local/cowpy.nf | 21 - .../modules/local/sayHello.nf | 20 - .../modules/nf-core/cat/cat/environment.yml | 7 - .../modules/nf-core/cat/cat/main.nf | 78 -- .../modules/nf-core/cat/cat/meta.yml | 46 - .../nf-core/cat/cat/tests/main.nf.test | 191 --- .../nf-core/cat/cat/tests/main.nf.test.snap | 147 --- .../cat/tests/nextflow_unzipped_zipped.config | 6 - .../cat/tests/nextflow_zipped_unzipped.config | 8 - .../core-hello-part4/nextflow.config | 238 ---- .../core-hello-part4/nextflow_schema.json | 156 --- .../local/utils_nfcore_hello_pipeline/main.nf | 125 -- .../nf-core/utils_nextflow_pipeline/main.nf | 126 -- .../nf-core/utils_nextflow_pipeline/meta.yml | 38 - .../tests/main.function.nf.test | 54 - .../tests/main.function.nf.test.snap | 20 - .../tests/main.workflow.nf.test | 113 -- .../tests/nextflow.config | 9 - .../utils_nextflow_pipeline/tests/tags.yml | 2 - .../nf-core/utils_nfcore_pipeline/main.nf | 419 ------- .../nf-core/utils_nfcore_pipeline/meta.yml | 24 - .../tests/main.function.nf.test | 126 -- .../tests/main.function.nf.test.snap | 136 --- .../tests/main.workflow.nf.test | 29 - .../tests/main.workflow.nf.test.snap | 19 - .../tests/nextflow.config | 9 - .../utils_nfcore_pipeline/tests/tags.yml | 2 - .../nf-core/utils_nfschema_plugin/main.nf | 45 - .../nf-core/utils_nfschema_plugin/meta.yml | 35 - .../utils_nfschema_plugin/tests/main.nf.test | 117 -- .../tests/nextflow.config | 8 - .../tests/nextflow_schema.json | 103 -- .../test-results/cat/test.txt | 3 + .../test-results/cowpy/cowpy-test.txt | 14 + .../execution_report_2025-10-24_10-21-35.html | 1040 +++++++++++++++++ ...xecution_timeline_2025-10-24_10-21-35.html | 230 ++++ .../execution_trace_2025-10-24_10-21-35.txt | 9 + .../pipeline_info/hello_software_versions.yml | 3 + .../params_2025-10-24_10-21-36.json | 21 + .../pipeline_dag_2025-10-24_10-21-35.html | 71 ++ .../core-hello-part4/workflows/hello.nf | 65 -- 56 files changed, 1392 insertions(+), 3166 deletions(-) delete mode 100644 hello-nf-core/solutions/core-hello-part4/README.md delete mode 100644 hello-nf-core/solutions/core-hello-part4/assets/greetings.csv delete mode 100644 hello-nf-core/solutions/core-hello-part4/assets/samplesheet.csv delete mode 100644 hello-nf-core/solutions/core-hello-part4/assets/schema_input.json delete mode 100644 hello-nf-core/solutions/core-hello-part4/conf/base.config delete mode 100644 hello-nf-core/solutions/core-hello-part4/conf/modules.config delete mode 100644 hello-nf-core/solutions/core-hello-part4/conf/test.config delete mode 100644 hello-nf-core/solutions/core-hello-part4/docs/README.md delete mode 100644 hello-nf-core/solutions/core-hello-part4/docs/output.md delete mode 100644 hello-nf-core/solutions/core-hello-part4/docs/usage.md delete mode 100644 hello-nf-core/solutions/core-hello-part4/main.nf delete mode 100644 hello-nf-core/solutions/core-hello-part4/modules.json delete mode 100644 hello-nf-core/solutions/core-hello-part4/modules/local/collectGreetings.nf delete mode 100644 hello-nf-core/solutions/core-hello-part4/modules/local/convertToUpper.nf delete mode 100644 hello-nf-core/solutions/core-hello-part4/modules/local/cowpy.nf delete mode 100644 hello-nf-core/solutions/core-hello-part4/modules/local/sayHello.nf delete mode 100644 hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/environment.yml delete mode 100644 hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/main.nf delete mode 100644 hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/meta.yml delete mode 100644 hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/main.nf.test delete mode 100644 hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/main.nf.test.snap delete mode 100644 hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/nextflow_unzipped_zipped.config delete mode 100644 hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/nextflow_zipped_unzipped.config delete mode 100644 hello-nf-core/solutions/core-hello-part4/nextflow.config delete mode 100644 hello-nf-core/solutions/core-hello-part4/nextflow_schema.json delete mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/local/utils_nfcore_hello_pipeline/main.nf delete mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/main.nf delete mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/meta.yml delete mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test delete mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap delete mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test delete mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config delete mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml delete mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/main.nf delete mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/meta.yml delete mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test delete mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap delete mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test delete mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap delete mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config delete mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml delete mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/main.nf delete mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/meta.yml delete mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test delete mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config delete mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json create mode 100644 hello-nf-core/solutions/core-hello-part4/test-results/cat/test.txt create mode 100644 hello-nf-core/solutions/core-hello-part4/test-results/cowpy/cowpy-test.txt create mode 100644 hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/execution_report_2025-10-24_10-21-35.html create mode 100644 hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/execution_timeline_2025-10-24_10-21-35.html create mode 100644 hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/execution_trace_2025-10-24_10-21-35.txt create mode 100644 hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/hello_software_versions.yml create mode 100644 hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/params_2025-10-24_10-21-36.json create mode 100644 hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/pipeline_dag_2025-10-24_10-21-35.html delete mode 100644 hello-nf-core/solutions/core-hello-part4/workflows/hello.nf diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index d0cc39caa0..cdd19ceab0 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -621,7 +621,7 @@ Each file serves a specific purpose: The generated `main.nf` includes all the patterns you just learned, plus some additional features: -```groovy title="modules/local/cowpy/main.nf" hl_lines="12 21 22" +```groovy title="modules/local/cowpy/main.nf" hl_lines="11 21 22" process COWPY { tag "$meta.id" label 'process_single' diff --git a/hello-nf-core/solutions/core-hello-part4/README.md b/hello-nf-core/solutions/core-hello-part4/README.md deleted file mode 100644 index 0a533c4c4b..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/README.md +++ /dev/null @@ -1,75 +0,0 @@ -# core/hello - -## Introduction - -**core/hello** is a bioinformatics pipeline that ... - - - - - - -## Usage - -> [!NOTE] -> If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/usage/installation) on how to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/usage/introduction#how-to-run-a-pipeline) with `-profile test` before running the workflow on actual data. - - - -Now, you can run the pipeline using: - - - -```bash -nextflow run core/hello \ - -profile \ - --input samplesheet.csv \ - --outdir -``` - -> [!WARNING] -> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; see [docs](https://nf-co.re/docs/usage/getting_started/configuration#custom-configuration-files). - -## Credits - -core/hello was originally written by GG. - -We thank the following people for their extensive assistance in the development of this pipeline: - - - -## Contributions and Support - -If you would like to contribute to this pipeline, please see the [contributing guidelines](.github/CONTRIBUTING.md). - -## Citations - - - - -This pipeline uses code and infrastructure developed and maintained by the [nf-core](https://nf-co.re) community, reused here under the [MIT license](https://github.com/nf-core/tools/blob/main/LICENSE). - -> **The nf-core framework for community-curated bioinformatics pipelines.** -> -> Philip Ewels, Alexander Peltzer, Sven Fillinger, Harshil Patel, Johannes Alneberg, Andreas Wilm, Maxime Ulysse Garcia, Paolo Di Tommaso & Sven Nahnsen. -> -> _Nat Biotechnol._ 2020 Feb 13. doi: [10.1038/s41587-020-0439-x](https://dx.doi.org/10.1038/s41587-020-0439-x). diff --git a/hello-nf-core/solutions/core-hello-part4/assets/greetings.csv b/hello-nf-core/solutions/core-hello-part4/assets/greetings.csv deleted file mode 100644 index f5c9849604..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/assets/greetings.csv +++ /dev/null @@ -1,4 +0,0 @@ -greeting -Hello -Bonjour -Holà diff --git a/hello-nf-core/solutions/core-hello-part4/assets/samplesheet.csv b/hello-nf-core/solutions/core-hello-part4/assets/samplesheet.csv deleted file mode 100644 index 5f653ab7bf..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/assets/samplesheet.csv +++ /dev/null @@ -1,3 +0,0 @@ -sample,fastq_1,fastq_2 -SAMPLE_PAIRED_END,/path/to/fastq/files/AEG588A1_S1_L002_R1_001.fastq.gz,/path/to/fastq/files/AEG588A1_S1_L002_R2_001.fastq.gz -SAMPLE_SINGLE_END,/path/to/fastq/files/AEG588A4_S4_L003_R1_001.fastq.gz, diff --git a/hello-nf-core/solutions/core-hello-part4/assets/schema_input.json b/hello-nf-core/solutions/core-hello-part4/assets/schema_input.json deleted file mode 100644 index c7a4df997d..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/assets/schema_input.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://raw.githubusercontent.com/core/hello/main/assets/schema_input.json", - "title": "core/hello pipeline - params.input schema", - "description": "Schema for the greetings file provided with params.input", - "type": "array", - "items": { - "type": "object", - "properties": { - "greeting": { - "type": "string", - "pattern": "^\\S.*$", - "errorMessage": "Greeting must be provided and cannot be empty or start with whitespace" - } - }, - "required": ["greeting"] - } -} diff --git a/hello-nf-core/solutions/core-hello-part4/conf/base.config b/hello-nf-core/solutions/core-hello-part4/conf/base.config deleted file mode 100644 index 1abcd9876f..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/conf/base.config +++ /dev/null @@ -1,62 +0,0 @@ -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - core/hello Nextflow base config file -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - A 'blank slate' config file, appropriate for general use on most high performance - compute environments. Assumes that all software is installed and available on - the PATH. Runs in `local` mode - all jobs will be run on the logged in environment. ----------------------------------------------------------------------------------------- -*/ - -process { - - // TODO nf-core: Check the defaults for all processes - cpus = { 1 * task.attempt } - memory = { 6.GB * task.attempt } - time = { 4.h * task.attempt } - - errorStrategy = { task.exitStatus in ((130..145) + 104) ? 'retry' : 'finish' } - maxRetries = 1 - maxErrors = '-1' - - // Process-specific resource requirements - // NOTE - Please try and reuse the labels below as much as possible. - // These labels are used and recognised by default in DSL2 files hosted on nf-core/modules. - // If possible, it would be nice to keep the same label naming convention when - // adding in your local modules too. - // TODO nf-core: Customise requirements for specific processes. - // See https://www.nextflow.io/docs/latest/config.html#config-process-selectors - withLabel:process_single { - cpus = { 1 } - memory = { 6.GB * task.attempt } - time = { 4.h * task.attempt } - } - withLabel:process_low { - cpus = { 2 * task.attempt } - memory = { 12.GB * task.attempt } - time = { 4.h * task.attempt } - } - withLabel:process_medium { - cpus = { 6 * task.attempt } - memory = { 36.GB * task.attempt } - time = { 8.h * task.attempt } - } - withLabel:process_high { - cpus = { 12 * task.attempt } - memory = { 72.GB * task.attempt } - time = { 16.h * task.attempt } - } - withLabel:process_long { - time = { 20.h * task.attempt } - } - withLabel:process_high_memory { - memory = { 200.GB * task.attempt } - } - withLabel:error_ignore { - errorStrategy = 'ignore' - } - withLabel:error_retry { - errorStrategy = 'retry' - maxRetries = 2 - } -} diff --git a/hello-nf-core/solutions/core-hello-part4/conf/modules.config b/hello-nf-core/solutions/core-hello-part4/conf/modules.config deleted file mode 100644 index 51b19b4a1e..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/conf/modules.config +++ /dev/null @@ -1,26 +0,0 @@ -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Config file for defining DSL2 per module options and publishing paths -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Available keys to override module options: - ext.args = Additional arguments appended to command in module. - ext.args2 = Second set of arguments appended to command in module (multi-tool modules). - ext.args3 = Third set of arguments appended to command in module (multi-tool modules). - ext.prefix = File name prefix for output files. ----------------------------------------------------------------------------------------- -*/ - -process { - - publishDir = [ - path: { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - - withName: 'cowpy' { - ext.args = { "-c ${params.character}" } - ext.prefix = { "cowpy-${meta.id}" } - } - -} diff --git a/hello-nf-core/solutions/core-hello-part4/conf/test.config b/hello-nf-core/solutions/core-hello-part4/conf/test.config deleted file mode 100644 index f82761298d..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/conf/test.config +++ /dev/null @@ -1,30 +0,0 @@ -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Nextflow config file for running minimal tests -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Defines input files and everything required to run a fast and simple pipeline test. - - Use as follows: - nextflow run core/hello -profile test, --outdir - ----------------------------------------------------------------------------------------- -*/ - -process { - resourceLimits = [ - cpus: 1, - memory: '1.GB' - ] -} - -params { - config_profile_name = 'Test profile' - config_profile_description = 'Minimal test dataset to check pipeline function' - - // Input data - input = "${projectDir}/assets/greetings.csv" - - // Other parameters - batch = 'test' - character = 'tux' -} diff --git a/hello-nf-core/solutions/core-hello-part4/docs/README.md b/hello-nf-core/solutions/core-hello-part4/docs/README.md deleted file mode 100644 index 593e4a39e8..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/docs/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# core/hello: Documentation - -The core/hello documentation is split into the following pages: - -- [Usage](usage.md) - - An overview of how the pipeline works, how to run it and a description of all of the different command-line flags. -- [Output](output.md) - - An overview of the different results produced by the pipeline and how to interpret them. diff --git a/hello-nf-core/solutions/core-hello-part4/docs/output.md b/hello-nf-core/solutions/core-hello-part4/docs/output.md deleted file mode 100644 index 7a49820c81..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/docs/output.md +++ /dev/null @@ -1,29 +0,0 @@ -# core/hello: Output - -## Introduction - -This document describes the output produced by the pipeline. - -The directories listed below will be created in the results directory after the pipeline has finished. All paths are relative to the top-level results directory. - - - -## Pipeline overview - -The pipeline is built using [Nextflow](https://www.nextflow.io/) and processes data using the following steps: - -- [Pipeline information](#pipeline-information) - Report metrics generated during the workflow execution - -### Pipeline information - -
-Output files - -- `pipeline_info/` - - Reports generated by Nextflow: `execution_report.html`, `execution_timeline.html`, `execution_trace.txt` and `pipeline_dag.dot`/`pipeline_dag.svg`. - - Reformatted samplesheet files used as input to the pipeline: `samplesheet.valid.csv`. - - Parameters used by the pipeline run: `params.json`. - -
- -[Nextflow](https://www.nextflow.io/docs/latest/tracing.html) provides excellent functionality for generating various reports relevant to the running and execution of the pipeline. This will allow you to troubleshoot errors with the running of the pipeline, and also provide you with other information such as launch commands, run times and resource usage. diff --git a/hello-nf-core/solutions/core-hello-part4/docs/usage.md b/hello-nf-core/solutions/core-hello-part4/docs/usage.md deleted file mode 100644 index 78b55f9afe..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/docs/usage.md +++ /dev/null @@ -1,211 +0,0 @@ -# core/hello: Usage - -> _Documentation of pipeline parameters is generated automatically from the pipeline schema and can no longer be found in markdown files._ - -## Introduction - - - -## Samplesheet input - -You will need to create a samplesheet with information about the samples you would like to analyse before running the pipeline. Use this parameter to specify its location. It has to be a comma-separated file with 3 columns, and a header row as shown in the examples below. - -```bash ---input '[path to samplesheet file]' -``` - -### Multiple runs of the same sample - -The `sample` identifiers have to be the same when you have re-sequenced the same sample more than once e.g. to increase sequencing depth. The pipeline will concatenate the raw reads before performing any downstream analysis. Below is an example for the same sample sequenced across 3 lanes: - -```csv title="samplesheet.csv" -sample,fastq_1,fastq_2 -CONTROL_REP1,AEG588A1_S1_L002_R1_001.fastq.gz,AEG588A1_S1_L002_R2_001.fastq.gz -CONTROL_REP1,AEG588A1_S1_L003_R1_001.fastq.gz,AEG588A1_S1_L003_R2_001.fastq.gz -CONTROL_REP1,AEG588A1_S1_L004_R1_001.fastq.gz,AEG588A1_S1_L004_R2_001.fastq.gz -``` - -### Full samplesheet - -The pipeline will auto-detect whether a sample is single- or paired-end using the information provided in the samplesheet. The samplesheet can have as many columns as you desire, however, there is a strict requirement for the first 3 columns to match those defined in the table below. - -A final samplesheet file consisting of both single- and paired-end data may look something like the one below. This is for 6 samples, where `TREATMENT_REP3` has been sequenced twice. - -```csv title="samplesheet.csv" -sample,fastq_1,fastq_2 -CONTROL_REP1,AEG588A1_S1_L002_R1_001.fastq.gz,AEG588A1_S1_L002_R2_001.fastq.gz -CONTROL_REP2,AEG588A2_S2_L002_R1_001.fastq.gz,AEG588A2_S2_L002_R2_001.fastq.gz -CONTROL_REP3,AEG588A3_S3_L002_R1_001.fastq.gz,AEG588A3_S3_L002_R2_001.fastq.gz -TREATMENT_REP1,AEG588A4_S4_L003_R1_001.fastq.gz, -TREATMENT_REP2,AEG588A5_S5_L003_R1_001.fastq.gz, -TREATMENT_REP3,AEG588A6_S6_L003_R1_001.fastq.gz, -TREATMENT_REP3,AEG588A6_S6_L004_R1_001.fastq.gz, -``` - -| Column | Description | -| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `sample` | Custom sample name. This entry will be identical for multiple sequencing libraries/runs from the same sample. Spaces in sample names are automatically converted to underscores (`_`). | -| `fastq_1` | Full path to FastQ file for Illumina short reads 1. File has to be gzipped and have the extension ".fastq.gz" or ".fq.gz". | -| `fastq_2` | Full path to FastQ file for Illumina short reads 2. File has to be gzipped and have the extension ".fastq.gz" or ".fq.gz". | - -An [example samplesheet](../assets/samplesheet.csv) has been provided with the pipeline. - -## Running the pipeline - -The typical command for running the pipeline is as follows: - -```bash -nextflow run core/hello --input ./samplesheet.csv --outdir ./results -profile docker -``` - -This will launch the pipeline with the `docker` configuration profile. See below for more information about profiles. - -Note that the pipeline will create the following files in your working directory: - -```bash -work # Directory containing the nextflow working files - # Finished results in specified location (defined with --outdir) -.nextflow_log # Log file from Nextflow -# Other nextflow hidden files, eg. history of pipeline runs and old logs. -``` - -If you wish to repeatedly use the same parameters for multiple runs, rather than specifying each flag in the command, you can specify these in a params file. - -Pipeline settings can be provided in a `yaml` or `json` file via `-params-file `. - -> [!WARNING] -> Do not use `-c ` to specify parameters as this will result in errors. Custom config files specified with `-c` must only be used for [tuning process resource specifications](https://nf-co.re/docs/usage/configuration#tuning-workflow-resources), other infrastructural tweaks (such as output directories), or module arguments (args). - -The above pipeline run specified with a params file in yaml format: - -```bash -nextflow run core/hello -profile docker -params-file params.yaml -``` - -with: - -```yaml title="params.yaml" -input: './samplesheet.csv' -outdir: './results/' -<...> -``` - -You can also generate such `YAML`/`JSON` files via [nf-core/launch](https://nf-co.re/launch). - -### Updating the pipeline - -When you run the above command, Nextflow automatically pulls the pipeline code from GitHub and stores it as a cached version. When running the pipeline after this, it will always use the cached version if available - even if the pipeline has been updated since. To make sure that you're running the latest version of the pipeline, make sure that you regularly update the cached version of the pipeline: - -```bash -nextflow pull core/hello -``` - -### Reproducibility - -It is a good idea to specify the pipeline version when running the pipeline on your data. This ensures that a specific version of the pipeline code and software are used when you run your pipeline. If you keep using the same tag, you'll be running the same version of the pipeline, even if there have been changes to the code since. - -First, go to the [core/hello releases page](https://github.com/core/hello/releases) and find the latest pipeline version - numeric only (eg. `1.3.1`). Then specify this when running the pipeline with `-r` (one hyphen) - eg. `-r 1.3.1`. Of course, you can switch to another version by changing the number after the `-r` flag. - -This version number will be logged in reports when you run the pipeline, so that you'll know what you used when you look back in the future. - -To further assist in reproducibility, you can use share and reuse [parameter files](#running-the-pipeline) to repeat pipeline runs with the same settings without having to write out a command with every single parameter. - -> [!TIP] -> If you wish to share such profile (such as upload as supplementary material for academic publications), make sure to NOT include cluster specific paths to files, nor institutional specific profiles. - -## Core Nextflow arguments - -> [!NOTE] -> These options are part of Nextflow and use a _single_ hyphen (pipeline parameters use a double-hyphen) - -### `-profile` - -Use this parameter to choose a configuration profile. Profiles can give configuration presets for different compute environments. - -Several generic profiles are bundled with the pipeline which instruct the pipeline to use software packaged using different methods (Docker, Singularity, Podman, Shifter, Charliecloud, Apptainer, Conda) - see below. - -> [!IMPORTANT] -> We highly recommend the use of Docker or Singularity containers for full pipeline reproducibility, however when this is not possible, Conda is also supported. - -The pipeline also dynamically loads configurations from [https://github.com/nf-core/configs](https://github.com/nf-core/configs) when it runs, making multiple config profiles for various institutional clusters available at run time. For more information and to check if your system is supported, please see the [nf-core/configs documentation](https://github.com/nf-core/configs#documentation). - -Note that multiple profiles can be loaded, for example: `-profile test,docker` - the order of arguments is important! -They are loaded in sequence, so later profiles can overwrite earlier profiles. - -If `-profile` is not specified, the pipeline will run locally and expect all software to be installed and available on the `PATH`. This is _not_ recommended, since it can lead to different results on different machines dependent on the computer environment. - -- `test` - - A profile with a complete configuration for automated testing - - Includes links to test data so needs no other parameters -- `docker` - - A generic configuration profile to be used with [Docker](https://docker.com/) -- `singularity` - - A generic configuration profile to be used with [Singularity](https://sylabs.io/docs/) -- `podman` - - A generic configuration profile to be used with [Podman](https://podman.io/) -- `shifter` - - A generic configuration profile to be used with [Shifter](https://nersc.gitlab.io/development/shifter/how-to-use/) -- `charliecloud` - - A generic configuration profile to be used with [Charliecloud](https://hpc.github.io/charliecloud/) -- `apptainer` - - A generic configuration profile to be used with [Apptainer](https://apptainer.org/) -- `wave` - - A generic configuration profile to enable [Wave](https://seqera.io/wave/) containers. Use together with one of the above (requires Nextflow ` 24.03.0-edge` or later). -- `conda` - - A generic configuration profile to be used with [Conda](https://conda.io/docs/). Please only use Conda as a last resort i.e. when it's not possible to run the pipeline with Docker, Singularity, Podman, Shifter, Charliecloud, or Apptainer. - -### `-resume` - -Specify this when restarting a pipeline. Nextflow will use cached results from any pipeline steps where the inputs are the same, continuing from where it got to previously. For input to be considered the same, not only the names must be identical but the files' contents as well. For more info about this parameter, see [this blog post](https://www.nextflow.io/blog/2019/demystifying-nextflow-resume.html). - -You can also supply a run name to resume a specific run: `-resume [run-name]`. Use the `nextflow log` command to show previous run names. - -### `-c` - -Specify the path to a specific config file (this is a core Nextflow command). See the [nf-core website documentation](https://nf-co.re/usage/configuration) for more information. - -## Custom configuration - -### Resource requests - -Whilst the default requirements set within the pipeline will hopefully work for most people and with most input data, you may find that you want to customise the compute resources that the pipeline requests. Each step in the pipeline has a default set of requirements for number of CPUs, memory and time. For most of the pipeline steps, if the job exits with any of the error codes specified [here](https://github.com/nf-core/rnaseq/blob/4c27ef5610c87db00c3c5a3eed10b1d161abf575/conf/base.config#L18) it will automatically be resubmitted with higher resources request (2 x original, then 3 x original). If it still fails after the third attempt then the pipeline execution is stopped. - -To change the resource requests, please see the [max resources](https://nf-co.re/docs/usage/configuration#max-resources) and [tuning workflow resources](https://nf-co.re/docs/usage/configuration#tuning-workflow-resources) section of the nf-core website. - -### Custom Containers - -In some cases, you may wish to change the container or conda environment used by a pipeline steps for a particular tool. By default, nf-core pipelines use containers and software from the [biocontainers](https://biocontainers.pro/) or [bioconda](https://bioconda.github.io/) projects. However, in some cases the pipeline specified version maybe out of date. - -To use a different container from the default container or conda environment specified in a pipeline, please see the [updating tool versions](https://nf-co.re/docs/usage/configuration#updating-tool-versions) section of the nf-core website. - -### Custom Tool Arguments - -A pipeline might not always support every possible argument or option of a particular tool used in pipeline. Fortunately, nf-core pipelines provide some freedom to users to insert additional parameters that the pipeline does not include by default. - -To learn how to provide additional arguments to a particular tool of the pipeline, please see the [customising tool arguments](https://nf-co.re/docs/usage/configuration#customising-tool-arguments) section of the nf-core website. - -### nf-core/configs - -In most cases, you will only need to create a custom config as a one-off but if you and others within your organisation are likely to be running nf-core pipelines regularly and need to use the same settings regularly it may be a good idea to request that your custom config file is uploaded to the `nf-core/configs` git repository. Before you do this please can you test that the config file works with your pipeline of choice using the `-c` parameter. You can then create a pull request to the `nf-core/configs` repository with the addition of your config file, associated documentation file (see examples in [`nf-core/configs/docs`](https://github.com/nf-core/configs/tree/master/docs)), and amending [`nfcore_custom.config`](https://github.com/nf-core/configs/blob/master/nfcore_custom.config) to include your custom profile. - -See the main [Nextflow documentation](https://www.nextflow.io/docs/latest/config.html) for more information about creating your own configuration files. - -If you have any questions or issues please send us a message on [Slack](https://nf-co.re/join/slack) on the [`#configs` channel](https://nfcore.slack.com/channels/configs). - -## Running in the background - -Nextflow handles job submissions and supervises the running jobs. The Nextflow process must run until the pipeline is finished. - -The Nextflow `-bg` flag launches Nextflow in the background, detached from your terminal so that the workflow does not stop if you log out of your session. The logs are saved to a file. - -Alternatively, you can use `screen` / `tmux` or similar tool to create a detached session which you can log back into at a later time. -Some HPC setups also allow you to run nextflow within a cluster job submitted your job scheduler (from where it submits more jobs). - -## Nextflow memory requirements - -In some cases, the Nextflow Java virtual machines can start to request a large amount of memory. -We recommend adding the following line to your environment to limit this (typically in `~/.bashrc` or `~./bash_profile`): - -```bash -NXF_OPTS='-Xms1g -Xmx4g' -``` diff --git a/hello-nf-core/solutions/core-hello-part4/main.nf b/hello-nf-core/solutions/core-hello-part4/main.nf deleted file mode 100644 index f72a236660..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/main.nf +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env nextflow -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - core/hello -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Github : https://github.com/core/hello ----------------------------------------------------------------------------------------- -*/ - -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - IMPORT FUNCTIONS / MODULES / SUBWORKFLOWS / WORKFLOWS -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ - -include { HELLO } from './workflows/hello' -include { PIPELINE_INITIALISATION } from './subworkflows/local/utils_nfcore_hello_pipeline' -include { PIPELINE_COMPLETION } from './subworkflows/local/utils_nfcore_hello_pipeline' -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - NAMED WORKFLOWS FOR PIPELINE -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ - -// -// WORKFLOW: Run main analysis pipeline depending on type of input -// -workflow CORE_HELLO { - - take: - samplesheet // channel: samplesheet read in from --input - - main: - - // - // WORKFLOW: Run pipeline - // - HELLO ( - samplesheet - ) -} -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - RUN MAIN WORKFLOW -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ - -workflow { - - main: - // - // SUBWORKFLOW: Run initialisation tasks - // - PIPELINE_INITIALISATION ( - params.version, - params.validate_params, - params.monochrome_logs, - args, - params.outdir, - params.input - ) - - // - // WORKFLOW: Run main workflow - // - CORE_HELLO ( - PIPELINE_INITIALISATION.out.samplesheet - ) - // - // SUBWORKFLOW: Run completion tasks - // - PIPELINE_COMPLETION ( - params.outdir, - params.monochrome_logs, - ) -} - -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - THE END -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ diff --git a/hello-nf-core/solutions/core-hello-part4/modules.json b/hello-nf-core/solutions/core-hello-part4/modules.json deleted file mode 100644 index 85169da597..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/modules.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "core/hello", - "homePage": "https://github.com/core/hello", - "repos": { - "https://github.com/nf-core/modules.git": { - "modules": { - "nf-core": { - "cat/cat": { - "branch": "master", - "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", - "installed_by": ["modules"] - } - } - }, - "subworkflows": { - "nf-core": { - "utils_nextflow_pipeline": { - "branch": "master", - "git_sha": "c2b22d85f30a706a3073387f30380704fcae013b", - "installed_by": ["subworkflows"] - }, - "utils_nfcore_pipeline": { - "branch": "master", - "git_sha": "51ae5406a030d4da1e49e4dab49756844fdd6c7a", - "installed_by": ["subworkflows"] - }, - "utils_nfschema_plugin": { - "branch": "master", - "git_sha": "2fd2cd6d0e7b273747f32e465fdc6bcc3ae0814e", - "installed_by": ["subworkflows"] - } - } - } - } - } -} diff --git a/hello-nf-core/solutions/core-hello-part4/modules/local/collectGreetings.nf b/hello-nf-core/solutions/core-hello-part4/modules/local/collectGreetings.nf deleted file mode 100644 index 0274fec86f..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/modules/local/collectGreetings.nf +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Collect uppercase greetings into a single output file - */ -process collectGreetings { - - publishDir 'results', mode: 'copy' - - input: - path input_files - val batch_name - - output: - path "COLLECTED-${batch_name}-output.txt" , emit: outfile - - script: - """ - cat ${input_files} > 'COLLECTED-${batch_name}-output.txt' - """ -} diff --git a/hello-nf-core/solutions/core-hello-part4/modules/local/convertToUpper.nf b/hello-nf-core/solutions/core-hello-part4/modules/local/convertToUpper.nf deleted file mode 100644 index b2689e8e9c..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/modules/local/convertToUpper.nf +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env nextflow - -/* - * Use a text replacement tool to convert the greeting to uppercase - */ -process convertToUpper { - - publishDir 'results', mode: 'copy' - - input: - path input_file - - output: - path "UPPER-${input_file}" - - script: - """ - cat '$input_file' | tr '[a-z]' '[A-Z]' > 'UPPER-${input_file}' - """ -} diff --git a/hello-nf-core/solutions/core-hello-part4/modules/local/cowpy.nf b/hello-nf-core/solutions/core-hello-part4/modules/local/cowpy.nf deleted file mode 100644 index ec00520a66..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/modules/local/cowpy.nf +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env nextflow - -// Generate ASCII art with cowpy (https://github.com/jeffbuttars/cowpy) -process cowpy { - - container 'community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273' - conda 'conda-forge::cowpy==1.1.5' - - input: - tuple val(meta), path(input_file) - - output: - tuple val(meta), path("${prefix}.txt"), emit: cowpy_output - - script: - def args = task.ext.args ?: '' - prefix = task.ext.prefix ?: "${meta.id}" - """ - cat $input_file | cowpy $args > ${prefix}.txt - """ -} diff --git a/hello-nf-core/solutions/core-hello-part4/modules/local/sayHello.nf b/hello-nf-core/solutions/core-hello-part4/modules/local/sayHello.nf deleted file mode 100644 index 6005ad54c9..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/modules/local/sayHello.nf +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env nextflow - -/* - * Use echo to print 'Hello World!' to a file - */ -process sayHello { - - publishDir 'results', mode: 'copy' - - input: - val greeting - - output: - path "${greeting}-output.txt" - - script: - """ - echo '$greeting' > '$greeting-output.txt' - """ -} diff --git a/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/environment.yml b/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/environment.yml deleted file mode 100644 index 50c2059afb..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/environment.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json -channels: - - conda-forge - - bioconda -dependencies: - - conda-forge::pigz=2.3.4 diff --git a/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/main.nf b/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/main.nf deleted file mode 100644 index 2862c64cd9..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/main.nf +++ /dev/null @@ -1,78 +0,0 @@ -process CAT_CAT { - tag "$meta.id" - label 'process_low' - - conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/pigz:2.3.4' : - 'biocontainers/pigz:2.3.4' }" - - input: - tuple val(meta), path(files_in) - - output: - tuple val(meta), path("${prefix}"), emit: file_out - path "versions.yml" , emit: versions - - when: - task.ext.when == null || task.ext.when - - script: - def args = task.ext.args ?: '' - def args2 = task.ext.args2 ?: '' - def file_list = files_in.collect { it.toString() } - - // choose appropriate concatenation tool depending on input and output format - - // | input | output | command1 | command2 | - // |-----------|------------|----------|----------| - // | gzipped | gzipped | cat | | - // | ungzipped | ungzipped | cat | | - // | gzipped | ungzipped | zcat | | - // | ungzipped | gzipped | cat | pigz | - - // Use input file ending as default - prefix = task.ext.prefix ?: "${meta.id}${getFileSuffix(file_list[0])}" - out_zip = prefix.endsWith('.gz') - in_zip = file_list[0].endsWith('.gz') - command1 = (in_zip && !out_zip) ? 'zcat' : 'cat' - command2 = (!in_zip && out_zip) ? "| pigz -c -p $task.cpus $args2" : '' - if(file_list.contains(prefix.trim())) { - error "The name of the input file can't be the same as for the output prefix in the " + - "module CAT_CAT (currently `$prefix`). Please choose a different one." - } - """ - $command1 \\ - $args \\ - ${file_list.join(' ')} \\ - $command2 \\ - > ${prefix} - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - pigz: \$( pigz --version 2>&1 | sed 's/pigz //g' ) - END_VERSIONS - """ - - stub: - def file_list = files_in.collect { it.toString() } - prefix = task.ext.prefix ?: "${meta.id}${file_list[0].substring(file_list[0].lastIndexOf('.'))}" - if(file_list.contains(prefix.trim())) { - error "The name of the input file can't be the same as for the output prefix in the " + - "module CAT_CAT (currently `$prefix`). Please choose a different one." - } - """ - touch $prefix - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - pigz: \$( pigz --version 2>&1 | sed 's/pigz //g' ) - END_VERSIONS - """ -} - -// for .gz files also include the second to last extension if it is present. E.g., .fasta.gz -def getFileSuffix(filename) { - def match = filename =~ /^.*?((\.\w{1,5})?(\.\w{1,5}\.gz$))/ - return match ? match[0][1] : filename.substring(filename.lastIndexOf('.')) -} diff --git a/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/meta.yml b/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/meta.yml deleted file mode 100644 index 2a9284d7f1..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/meta.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: cat_cat -description: A module for concatenation of gzipped or uncompressed files -keywords: - - concatenate - - gzip - - cat -tools: - - cat: - description: Just concatenation - documentation: https://man7.org/linux/man-pages/man1/cat.1.html - licence: ["GPL-3.0-or-later"] - identifier: "" -input: - - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - - files_in: - type: file - description: List of compressed / uncompressed files - pattern: "*" - ontologies: [] -output: - file_out: - - - meta: - type: map - description: Groovy Map containing sample information - - ${prefix}: - type: file - description: Concatenated file. Will be gzipped if file_out ends with ".gz" - pattern: "${file_out}" - ontologies: [] - versions: - - versions.yml: - type: file - description: File containing software versions - pattern: "versions.yml" - ontologies: - - edam: http://edamontology.org/format_3750 # YAML -authors: - - "@erikrikarddaniel" - - "@FriederikeHanssen" -maintainers: - - "@erikrikarddaniel" - - "@FriederikeHanssen" diff --git a/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/main.nf.test b/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/main.nf.test deleted file mode 100644 index 9cb1617883..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/main.nf.test +++ /dev/null @@ -1,191 +0,0 @@ -nextflow_process { - - name "Test Process CAT_CAT" - script "../main.nf" - process "CAT_CAT" - tag "modules" - tag "modules_nfcore" - tag "cat" - tag "cat/cat" - - test("test_cat_name_conflict") { - when { - params { - outdir = "${outputDir}" - } - process { - """ - input[0] = - [ - [ id:'genome', single_end:true ], - [ - file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.sizes', checkIfExists: true) - ] - ] - """ - } - } - then { - assertAll( - { assert !process.success }, - { assert process.stdout.toString().contains("The name of the input file can't be the same as for the output prefix") }, - { assert snapshot(process.out.versions).match() } - ) - } - } - - test("test_cat_unzipped_unzipped") { - when { - params { - outdir = "${outputDir}" - } - process { - """ - input[0] = - [ - [ id:'test', single_end:true ], - [ - file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.sizes', checkIfExists: true) - ] - ] - """ - } - } - then { - assertAll( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - } - - - test("test_cat_zipped_zipped") { - when { - params { - outdir = "${outputDir}" - } - process { - """ - input[0] = - [ - [ id:'test', single_end:true ], - [ - file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.gff3.gz', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/alignment/last/contigs.genome.maf.gz', checkIfExists: true) - ] - ] - """ - } - } - then { - def lines = path(process.out.file_out.get(0).get(1)).linesGzip - assertAll( - { assert process.success }, - { assert snapshot( - lines[0..5], - lines.size(), - process.out.versions - ).match() - } - ) - } - } - - test("test_cat_zipped_unzipped") { - config './nextflow_zipped_unzipped.config' - - when { - params { - outdir = "${outputDir}" - } - process { - """ - input[0] = - [ - [ id:'test', single_end:true ], - [ - file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.gff3.gz', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/alignment/last/contigs.genome.maf.gz', checkIfExists: true) - ] - ] - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - - } - - test("test_cat_unzipped_zipped") { - config './nextflow_unzipped_zipped.config' - when { - params { - outdir = "${outputDir}" - } - process { - """ - input[0] = - [ - [ id:'test', single_end:true ], - [ - file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.sizes', checkIfExists: true) - ] - ] - """ - } - } - then { - def lines = path(process.out.file_out.get(0).get(1)).linesGzip - assertAll( - { assert process.success }, - { assert snapshot( - lines[0..5], - lines.size(), - process.out.versions - ).match() - } - ) - } - } - - test("test_cat_one_file_unzipped_zipped") { - config './nextflow_unzipped_zipped.config' - when { - params { - outdir = "${outputDir}" - } - process { - """ - input[0] = - [ - [ id:'test', single_end:true ], - [ - file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) - ] - ] - """ - } - } - then { - def lines = path(process.out.file_out.get(0).get(1)).linesGzip - assertAll( - { assert process.success }, - { assert snapshot( - lines[0..5], - lines.size(), - process.out.versions - ).match() - } - ) - } - } -} diff --git a/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/main.nf.test.snap b/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/main.nf.test.snap deleted file mode 100644 index e2381ca20b..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/main.nf.test.snap +++ /dev/null @@ -1,147 +0,0 @@ -{ - "test_cat_unzipped_unzipped": { - "content": [ - { - "0": [ - [ - { - "id": "test", - "single_end": true - }, - "test.fasta:md5,f44b33a0e441ad58b2d3700270e2dbe2" - ] - ], - "1": [ - "versions.yml:md5,115ed6177ebcff24eb99d503fa5ef894" - ], - "file_out": [ - [ - { - "id": "test", - "single_end": true - }, - "test.fasta:md5,f44b33a0e441ad58b2d3700270e2dbe2" - ] - ], - "versions": [ - "versions.yml:md5,115ed6177ebcff24eb99d503fa5ef894" - ] - } - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "24.04.3" - }, - "timestamp": "2023-10-16T14:32:18.500464399" - }, - "test_cat_zipped_unzipped": { - "content": [ - { - "0": [ - [ - { - "id": "test", - "single_end": true - }, - "cat.txt:md5,c439d3b60e7bc03e8802a451a0d9a5d9" - ] - ], - "1": [ - "versions.yml:md5,115ed6177ebcff24eb99d503fa5ef894" - ], - "file_out": [ - [ - { - "id": "test", - "single_end": true - }, - "cat.txt:md5,c439d3b60e7bc03e8802a451a0d9a5d9" - ] - ], - "versions": [ - "versions.yml:md5,115ed6177ebcff24eb99d503fa5ef894" - ] - } - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "24.04.3" - }, - "timestamp": "2023-10-16T14:32:49.642741302" - }, - "test_cat_zipped_zipped": { - "content": [ - [ - "MT192765.1\tGenbank\ttranscript\t259\t29667\t.\t+\t.\tID=unknown_transcript_1;geneID=orf1ab;gene_name=orf1ab", - "MT192765.1\tGenbank\tgene\t259\t21548\t.\t+\t.\tParent=unknown_transcript_1", - "MT192765.1\tGenbank\tCDS\t259\t13461\t.\t+\t0\tParent=unknown_transcript_1;exception=\"ribosomal slippage\";gbkey=CDS;gene=orf1ab;note=\"pp1ab;translated=by -1 ribosomal frameshift\";product=\"orf1ab polyprotein\";protein_id=QIK50426.1", - "MT192765.1\tGenbank\tCDS\t13461\t21548\t.\t+\t0\tParent=unknown_transcript_1;exception=\"ribosomal slippage\";gbkey=CDS;gene=orf1ab;note=\"pp1ab;translated=by -1 ribosomal frameshift\";product=\"orf1ab polyprotein\";protein_id=QIK50426.1", - "MT192765.1\tGenbank\tCDS\t21556\t25377\t.\t+\t0\tParent=unknown_transcript_1;gbkey=CDS;gene=S;note=\"structural protein\";product=\"surface glycoprotein\";protein_id=QIK50427.1", - "MT192765.1\tGenbank\tgene\t21556\t25377\t.\t+\t.\tParent=unknown_transcript_1" - ], - 78, - [ - "versions.yml:md5,115ed6177ebcff24eb99d503fa5ef894" - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "24.04.3" - }, - "timestamp": "2024-07-22T11:51:46.802978" - }, - "test_cat_name_conflict": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "24.04.3" - }, - "timestamp": "2024-07-22T11:51:29.45394" - }, - "test_cat_one_file_unzipped_zipped": { - "content": [ - [ - ">MT192765.1 Severe acute respiratory syndrome coronavirus 2 isolate SARS-CoV-2/human/USA/PC00101P/2020, complete genome", - "GTTTATACCTTCCCAGGTAACAAACCAACCAACTTTCGATCTCTTGTAGATCTGTTCTCTAAACGAACTTTAAAATCTGT", - "GTGGCTGTCACTCGGCTGCATGCTTAGTGCACTCACGCAGTATAATTAATAACTAATTACTGTCGTTGACAGGACACGAG", - "TAACTCGTCTATCTTCTGCAGGCTGCTTACGGTTTCGTCCGTGTTGCAGCCGATCATCAGCACATCTAGGTTTTGTCCGG", - "GTGTGACCGAAAGGTAAGATGGAGAGCCTTGTCCCTGGTTTCAACGAGAAAACACACGTCCAACTCAGTTTGCCTGTTTT", - "ACAGGTTCGCGACGTGCTCGTACGTGGCTTTGGAGACTCCGTGGAGGAGGTCTTATCAGAGGCACGTCAACATCTTAAAG" - ], - 374, - [ - "versions.yml:md5,115ed6177ebcff24eb99d503fa5ef894" - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "24.04.3" - }, - "timestamp": "2024-07-22T11:52:02.774016" - }, - "test_cat_unzipped_zipped": { - "content": [ - [ - ">MT192765.1 Severe acute respiratory syndrome coronavirus 2 isolate SARS-CoV-2/human/USA/PC00101P/2020, complete genome", - "GTTTATACCTTCCCAGGTAACAAACCAACCAACTTTCGATCTCTTGTAGATCTGTTCTCTAAACGAACTTTAAAATCTGT", - "GTGGCTGTCACTCGGCTGCATGCTTAGTGCACTCACGCAGTATAATTAATAACTAATTACTGTCGTTGACAGGACACGAG", - "TAACTCGTCTATCTTCTGCAGGCTGCTTACGGTTTCGTCCGTGTTGCAGCCGATCATCAGCACATCTAGGTTTTGTCCGG", - "GTGTGACCGAAAGGTAAGATGGAGAGCCTTGTCCCTGGTTTCAACGAGAAAACACACGTCCAACTCAGTTTGCCTGTTTT", - "ACAGGTTCGCGACGTGCTCGTACGTGGCTTTGGAGACTCCGTGGAGGAGGTCTTATCAGAGGCACGTCAACATCTTAAAG" - ], - 375, - [ - "versions.yml:md5,115ed6177ebcff24eb99d503fa5ef894" - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "24.04.3" - }, - "timestamp": "2024-07-22T11:51:57.581523" - } -} diff --git a/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/nextflow_unzipped_zipped.config b/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/nextflow_unzipped_zipped.config deleted file mode 100644 index ec26b0fdc6..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/nextflow_unzipped_zipped.config +++ /dev/null @@ -1,6 +0,0 @@ - -process { - withName: CAT_CAT { - ext.prefix = 'cat.txt.gz' - } -} diff --git a/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/nextflow_zipped_unzipped.config b/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/nextflow_zipped_unzipped.config deleted file mode 100644 index fbc79783d5..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/nextflow_zipped_unzipped.config +++ /dev/null @@ -1,8 +0,0 @@ - -process { - - withName: CAT_CAT { - ext.prefix = 'cat.txt' - } - -} diff --git a/hello-nf-core/solutions/core-hello-part4/nextflow.config b/hello-nf-core/solutions/core-hello-part4/nextflow.config deleted file mode 100644 index d633adb989..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/nextflow.config +++ /dev/null @@ -1,238 +0,0 @@ -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - core/hello Nextflow config file -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Default config options for all compute environments ----------------------------------------------------------------------------------------- -*/ - -// Global default params, used in configs -params { - - // TODO nf-core: Specify your pipeline's command line flags - // Input options - input = null - - // Boilerplate options - outdir = null - publish_dir_mode = 'copy' - monochrome_logs = false - help = false - help_full = false - show_hidden = false - version = false - pipelines_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/' - trace_report_suffix = new java.util.Date().format( 'yyyy-MM-dd_HH-mm-ss')// Config options - config_profile_name = null - config_profile_description = null - - custom_config_version = 'master' - custom_config_base = "https://raw.githubusercontent.com/nf-core/configs/${params.custom_config_version}" - config_profile_contact = null - config_profile_url = null - - // Schema validation default options - validate_params = true -} - -// Load base.config by default for all pipelines -includeConfig 'conf/base.config' - -profiles { - debug { - dumpHashes = true - process.beforeScript = 'echo $HOSTNAME' - cleanup = false - nextflow.enable.configProcessNamesValidation = true - } - conda { - conda.enabled = true - docker.enabled = false - singularity.enabled = false - podman.enabled = false - shifter.enabled = false - charliecloud.enabled = false - conda.channels = ['conda-forge', 'bioconda'] - apptainer.enabled = false - } - mamba { - conda.enabled = true - conda.useMamba = true - docker.enabled = false - singularity.enabled = false - podman.enabled = false - shifter.enabled = false - charliecloud.enabled = false - apptainer.enabled = false - } - docker { - docker.enabled = true - conda.enabled = false - singularity.enabled = false - podman.enabled = false - shifter.enabled = false - charliecloud.enabled = false - apptainer.enabled = false - docker.runOptions = '-u $(id -u):$(id -g)' - } - arm { - docker.runOptions = '-u $(id -u):$(id -g) --platform=linux/amd64' - } - singularity { - singularity.enabled = true - singularity.autoMounts = true - conda.enabled = false - docker.enabled = false - podman.enabled = false - shifter.enabled = false - charliecloud.enabled = false - apptainer.enabled = false - } - podman { - podman.enabled = true - conda.enabled = false - docker.enabled = false - singularity.enabled = false - shifter.enabled = false - charliecloud.enabled = false - apptainer.enabled = false - } - shifter { - shifter.enabled = true - conda.enabled = false - docker.enabled = false - singularity.enabled = false - podman.enabled = false - charliecloud.enabled = false - apptainer.enabled = false - } - charliecloud { - charliecloud.enabled = true - conda.enabled = false - docker.enabled = false - singularity.enabled = false - podman.enabled = false - shifter.enabled = false - apptainer.enabled = false - } - apptainer { - apptainer.enabled = true - apptainer.autoMounts = true - conda.enabled = false - docker.enabled = false - singularity.enabled = false - podman.enabled = false - shifter.enabled = false - charliecloud.enabled = false - } - wave { - apptainer.ociAutoPull = true - singularity.ociAutoPull = true - wave.enabled = true - wave.freeze = true - wave.strategy = 'conda,container' - } - test { includeConfig 'conf/test.config' } - test_full { includeConfig 'conf/test_full.config' } -} - -// Load nf-core custom profiles from different Institutions -includeConfig !System.getenv('NXF_OFFLINE') && params.custom_config_base ? "${params.custom_config_base}/nfcore_custom.config" : "/dev/null" - -// Load core/hello custom profiles from different institutions. -// TODO nf-core: Optionally, you can add a pipeline-specific nf-core config at https://github.com/nf-core/configs -// includeConfig !System.getenv('NXF_OFFLINE') && params.custom_config_base ? "${params.custom_config_base}/pipeline/hello.config" : "/dev/null" - -// Set default registry for Apptainer, Docker, Podman, Charliecloud and Singularity independent of -profile -// Will not be used unless Apptainer / Docker / Podman / Charliecloud / Singularity are enabled -// Set to your registry if you have a mirror of containers -apptainer.registry = 'quay.io' -docker.registry = 'quay.io' -podman.registry = 'quay.io' -singularity.registry = 'quay.io' -charliecloud.registry = 'quay.io' - - - -// Export these variables to prevent local Python/R libraries from conflicting with those in the container -// The JULIA depot path has been adjusted to a fixed path `/usr/local/share/julia` that needs to be used for packages in the container. -// See https://apeltzer.github.io/post/03-julia-lang-nextflow/ for details on that. Once we have a common agreement on where to keep Julia packages, this is adjustable. - -env { - PYTHONNOUSERSITE = 1 - R_PROFILE_USER = "/.Rprofile" - R_ENVIRON_USER = "/.Renviron" - JULIA_DEPOT_PATH = "/usr/local/share/julia" -} - -// Set bash options -process.shell = [ - "bash", - "-C", // No clobber - prevent output redirection from overwriting files. - "-e", // Exit if a tool returns a non-zero status/exit code - "-u", // Treat unset variables and parameters as an error - "-o", // Returns the status of the last command to exit.. - "pipefail" // ..with a non-zero status or zero if all successfully execute -] - -// Disable process selector warnings by default. Use debug profile to enable warnings. -nextflow.enable.configProcessNamesValidation = false - -timeline { - enabled = true - file = "${params.outdir}/pipeline_info/execution_timeline_${params.trace_report_suffix}.html" -} -report { - enabled = true - file = "${params.outdir}/pipeline_info/execution_report_${params.trace_report_suffix}.html" -} -trace { - enabled = true - file = "${params.outdir}/pipeline_info/execution_trace_${params.trace_report_suffix}.txt" -} -dag { - enabled = true - file = "${params.outdir}/pipeline_info/pipeline_dag_${params.trace_report_suffix}.html" -} - -manifest { - name = 'core/hello' - author = """GG""" // The author field is deprecated from Nextflow version 24.10.0, use contributors instead - contributors = [ - // TODO nf-core: Update the field with the details of the contributors to your pipeline. New with Nextflow version 24.10.0 - [ - name: 'GG', - affiliation: '', - email: '', - github: '', - contribution: [], // List of contribution types ('author', 'maintainer' or 'contributor') - orcid: '' - ], - ] - homePage = 'https://github.com/core/hello' - description = """basic nf-core style version of Hello Nextflow""" - mainScript = 'main.nf' - defaultBranch = 'main' - nextflowVersion = '!>=24.04.2' - version = '1.0.0dev' - doi = '' -} - -// Nextflow plugins -plugins { - id 'nf-schema@2.2.0' // Validation of pipeline parameters and creation of an input channel from a sample sheet -} - -validation { - defaultIgnoreParams = ["genomes"] - monochromeLogs = params.monochrome_logs - help { - enabled = true - command = "nextflow run core/hello -profile --input samplesheet.csv --outdir " - fullParameter = "help_full" - showHiddenParameter = "show_hidden" - } -} - -// Load modules.config for DSL2 module specific options -includeConfig 'conf/modules.config' diff --git a/hello-nf-core/solutions/core-hello-part4/nextflow_schema.json b/hello-nf-core/solutions/core-hello-part4/nextflow_schema.json deleted file mode 100644 index 93bd733448..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/nextflow_schema.json +++ /dev/null @@ -1,156 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://raw.githubusercontent.com/core/hello/main/nextflow_schema.json", - "title": "core/hello pipeline parameters", - "description": "basic nf-core style version of Hello Nextflow", - "type": "object", - "$defs": { - "input_output_options": { - "title": "Input/output options", - "type": "object", - "fa_icon": "fas fa-terminal", - "description": "Define where the pipeline should find input data and save output data.", - "required": ["input", "outdir"], - "properties": { - "input": { - "type": "string", - "format": "file-path", - "exists": true, - "schema": "assets/schema_input.json", - "mimetype": "text/csv", - "pattern": "^\\S+\\.csv$", - "description": "Path to comma-separated file containing information about the samples in the experiment.", - "help_text": "You will need to create a design file with information about the samples in your experiment before running the pipeline. Use this parameter to specify its location. It has to be a comma-separated file with 3 columns, and a header row.", - "fa_icon": "fas fa-file-csv" - }, - "batch": { - "type": "string", - "default": "batch-01", - "description": "Name for this batch of greetings" - }, - "outdir": { - "type": "string", - "format": "directory-path", - "description": "The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure.", - "fa_icon": "fas fa-folder-open" - } - } - }, - "institutional_config_options": { - "title": "Institutional config options", - "type": "object", - "fa_icon": "fas fa-university", - "description": "Parameters used to describe centralised config profiles. These should not be edited.", - "help_text": "The centralised nf-core configuration profiles use a handful of pipeline parameters to describe themselves. This information is then printed to the Nextflow log when you run a pipeline. You should not need to change these values when you run a pipeline.", - "properties": { - "custom_config_version": { - "type": "string", - "description": "Git commit id for Institutional configs.", - "default": "master", - "hidden": true, - "fa_icon": "fas fa-users-cog" - }, - "custom_config_base": { - "type": "string", - "description": "Base directory for Institutional configs.", - "default": "https://raw.githubusercontent.com/nf-core/configs/master", - "hidden": true, - "help_text": "If you're running offline, Nextflow will not be able to fetch the institutional config files from the internet. If you don't need them, then this is not a problem. If you do need them, you should download the files from the repo and tell Nextflow where to find them with this parameter.", - "fa_icon": "fas fa-users-cog" - }, - "config_profile_name": { - "type": "string", - "description": "Institutional config name.", - "hidden": true, - "fa_icon": "fas fa-users-cog" - }, - "config_profile_description": { - "type": "string", - "description": "Institutional config description.", - "hidden": true, - "fa_icon": "fas fa-users-cog" - }, - "config_profile_contact": { - "type": "string", - "description": "Institutional config contact information.", - "hidden": true, - "fa_icon": "fas fa-users-cog" - }, - "config_profile_url": { - "type": "string", - "description": "Institutional config URL link.", - "hidden": true, - "fa_icon": "fas fa-users-cog" - } - } - }, - "generic_options": { - "title": "Generic options", - "type": "object", - "fa_icon": "fas fa-file-import", - "description": "Less common options for the pipeline, typically set in a config file.", - "help_text": "These options are common to all nf-core pipelines and allow you to customise some of the core preferences for how the pipeline runs.\n\nTypically these options would be set in a Nextflow config file loaded for all pipeline runs, such as `~/.nextflow/config`.", - "properties": { - "version": { - "type": "boolean", - "description": "Display version and exit.", - "fa_icon": "fas fa-question-circle", - "hidden": true - }, - "publish_dir_mode": { - "type": "string", - "default": "copy", - "description": "Method used to save pipeline results to output directory.", - "help_text": "The Nextflow `publishDir` option specifies which intermediate files should be saved to the output directory. This option tells the pipeline what method should be used to move these files. See [Nextflow docs](https://www.nextflow.io/docs/latest/process.html#publishdir) for details.", - "fa_icon": "fas fa-copy", - "enum": [ - "symlink", - "rellink", - "link", - "copy", - "copyNoFollow", - "move" - ], - "hidden": true - }, - "monochrome_logs": { - "type": "boolean", - "description": "Do not use coloured log outputs.", - "fa_icon": "fas fa-palette", - "hidden": true - }, - "validate_params": { - "type": "boolean", - "description": "Boolean whether to validate parameters against the schema at runtime", - "default": true, - "fa_icon": "fas fa-check-square", - "hidden": true - }, - "pipelines_testdata_base_path": { - "type": "string", - "fa_icon": "far fa-check-circle", - "description": "Base URL or local path to location of pipeline test dataset files", - "default": "https://raw.githubusercontent.com/nf-core/test-datasets/", - "hidden": true - }, - "trace_report_suffix": { - "type": "string", - "fa_icon": "far calendar", - "description": "Suffix to add to the trace report filename. Default is the date and time in the format yyyy-MM-dd_HH-mm-ss.", - "hidden": true - } - } - } - }, - "allOf": [ - { - "$ref": "#/$defs/input_output_options" - }, - { - "$ref": "#/$defs/institutional_config_options" - }, - { - "$ref": "#/$defs/generic_options" - } - ] -} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/local/utils_nfcore_hello_pipeline/main.nf b/hello-nf-core/solutions/core-hello-part4/subworkflows/local/utils_nfcore_hello_pipeline/main.nf deleted file mode 100644 index 8882631372..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/subworkflows/local/utils_nfcore_hello_pipeline/main.nf +++ /dev/null @@ -1,125 +0,0 @@ -// -// Subworkflow with functionality specific to the core/hello pipeline -// - -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - IMPORT FUNCTIONS / MODULES / SUBWORKFLOWS -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ - -include { UTILS_NFSCHEMA_PLUGIN } from '../../nf-core/utils_nfschema_plugin' -include { paramsSummaryMap } from 'plugin/nf-schema' -include { samplesheetToList } from 'plugin/nf-schema' -include { completionSummary } from '../../nf-core/utils_nfcore_pipeline' -include { UTILS_NFCORE_PIPELINE } from '../../nf-core/utils_nfcore_pipeline' -include { UTILS_NEXTFLOW_PIPELINE } from '../../nf-core/utils_nextflow_pipeline' - -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - SUBWORKFLOW TO INITIALISE PIPELINE -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ - -workflow PIPELINE_INITIALISATION { - - take: - version // boolean: Display version and exit - validate_params // boolean: Boolean whether to validate parameters against the schema at runtime - monochrome_logs // boolean: Do not use coloured log outputs - nextflow_cli_args // array: List of positional nextflow CLI args - outdir // string: The output directory where the results will be saved - input // string: Path to input samplesheet - - main: - - ch_versions = Channel.empty() - - // - // Print version and exit if required and dump pipeline parameters to JSON file - // - UTILS_NEXTFLOW_PIPELINE ( - version, - true, - outdir, - workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1 - ) - - // - // Validate parameters and generate parameter summary to stdout - // - UTILS_NFSCHEMA_PLUGIN ( - workflow, - validate_params, - null - ) - - // - // Check config provided to the pipeline - // - UTILS_NFCORE_PIPELINE ( - nextflow_cli_args - ) - - // - // Create channel from input file provided through params.input - // - ch_samplesheet = Channel.fromList(samplesheetToList(params.input, "${projectDir}/assets/schema_input.json")) - .map { row -> - // Extract just the greeting string from each row - row[0] - } - - emit: - samplesheet = ch_samplesheet - versions = ch_versions -} - -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - SUBWORKFLOW FOR PIPELINE COMPLETION -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ - -workflow PIPELINE_COMPLETION { - - take: - outdir // path: Path to output directory where results will be published - monochrome_logs // boolean: Disable ANSI colour codes in log output - - main: - summary_params = paramsSummaryMap(workflow, parameters_schema: "nextflow_schema.json") - - // - // Completion email and summary - // - workflow.onComplete { - - completionSummary(monochrome_logs) - } - - workflow.onError { - log.error "Pipeline failed. Please refer to troubleshooting docs: https://nf-co.re/docs/usage/troubleshooting" - } -} - -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - FUNCTIONS -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ - -// -// Validate channels from input samplesheet -// -def validateInputSamplesheet(input) { - def (metas, fastqs) = input[1..2] - - // Check that multiple runs of the same sample are of the same datatype i.e. single-end / paired-end - def endedness_ok = metas.collect{ meta -> meta.single_end }.unique().size == 1 - if (!endedness_ok) { - error("Please check input samplesheet -> Multiple runs of a sample must be of the same datatype i.e. single-end or paired-end: ${metas[0].id}") - } - - return [ metas[0], fastqs ] -} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/main.nf b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/main.nf deleted file mode 100644 index d6e593e852..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/main.nf +++ /dev/null @@ -1,126 +0,0 @@ -// -// Subworkflow with functionality that may be useful for any Nextflow pipeline -// - -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - SUBWORKFLOW DEFINITION -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ - -workflow UTILS_NEXTFLOW_PIPELINE { - take: - print_version // boolean: print version - dump_parameters // boolean: dump parameters - outdir // path: base directory used to publish pipeline results - check_conda_channels // boolean: check conda channels - - main: - - // - // Print workflow version and exit on --version - // - if (print_version) { - log.info("${workflow.manifest.name} ${getWorkflowVersion()}") - System.exit(0) - } - - // - // Dump pipeline parameters to a JSON file - // - if (dump_parameters && outdir) { - dumpParametersToJSON(outdir) - } - - // - // When running with Conda, warn if channels have not been set-up appropriately - // - if (check_conda_channels) { - checkCondaChannels() - } - - emit: - dummy_emit = true -} - -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - FUNCTIONS -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ - -// -// Generate version string -// -def getWorkflowVersion() { - def version_string = "" as String - if (workflow.manifest.version) { - def prefix_v = workflow.manifest.version[0] != 'v' ? 'v' : '' - version_string += "${prefix_v}${workflow.manifest.version}" - } - - if (workflow.commitId) { - def git_shortsha = workflow.commitId.substring(0, 7) - version_string += "-g${git_shortsha}" - } - - return version_string -} - -// -// Dump pipeline parameters to a JSON file -// -def dumpParametersToJSON(outdir) { - def timestamp = new java.util.Date().format('yyyy-MM-dd_HH-mm-ss') - def filename = "params_${timestamp}.json" - def temp_pf = new File(workflow.launchDir.toString(), ".${filename}") - def jsonStr = groovy.json.JsonOutput.toJson(params) - temp_pf.text = groovy.json.JsonOutput.prettyPrint(jsonStr) - - nextflow.extension.FilesEx.copyTo(temp_pf.toPath(), "${outdir}/pipeline_info/params_${timestamp}.json") - temp_pf.delete() -} - -// -// When running with -profile conda, warn if channels have not been set-up appropriately -// -def checkCondaChannels() { - def parser = new org.yaml.snakeyaml.Yaml() - def channels = [] - try { - def config = parser.load("conda config --show channels".execute().text) - channels = config.channels - } - catch (NullPointerException e) { - log.debug(e) - log.warn("Could not verify conda channel configuration.") - return null - } - catch (IOException e) { - log.debug(e) - log.warn("Could not verify conda channel configuration.") - return null - } - - // Check that all channels are present - // This channel list is ordered by required channel priority. - def required_channels_in_order = ['conda-forge', 'bioconda'] - def channels_missing = ((required_channels_in_order as Set) - (channels as Set)) as Boolean - - // Check that they are in the right order - def channel_priority_violation = required_channels_in_order != channels.findAll { ch -> ch in required_channels_in_order } - - if (channels_missing | channel_priority_violation) { - log.warn """\ - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - There is a problem with your Conda configuration! - You will need to set-up the conda-forge and bioconda channels correctly. - Please refer to https://bioconda.github.io/ - The observed channel order is - ${channels} - but the following channel order is required: - ${required_channels_in_order} - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - """.stripIndent(true) - } -} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/meta.yml b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/meta.yml deleted file mode 100644 index e5c3a0a828..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/meta.yml +++ /dev/null @@ -1,38 +0,0 @@ -# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json -name: "UTILS_NEXTFLOW_PIPELINE" -description: Subworkflow with functionality that may be useful for any Nextflow pipeline -keywords: - - utility - - pipeline - - initialise - - version -components: [] -input: - - print_version: - type: boolean - description: | - Print the version of the pipeline and exit - - dump_parameters: - type: boolean - description: | - Dump the parameters of the pipeline to a JSON file - - output_directory: - type: directory - description: Path to output dir to write JSON file to. - pattern: "results/" - - check_conda_channel: - type: boolean - description: | - Check if the conda channel priority is correct. -output: - - dummy_emit: - type: boolean - description: | - Dummy emit to make nf-core subworkflows lint happy -authors: - - "@adamrtalbot" - - "@drpatelh" -maintainers: - - "@adamrtalbot" - - "@drpatelh" - - "@maxulysse" diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test deleted file mode 100644 index 68718e4f59..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test +++ /dev/null @@ -1,54 +0,0 @@ - -nextflow_function { - - name "Test Functions" - script "subworkflows/nf-core/utils_nextflow_pipeline/main.nf" - config "subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config" - tag 'subworkflows' - tag 'utils_nextflow_pipeline' - tag 'subworkflows/utils_nextflow_pipeline' - - test("Test Function getWorkflowVersion") { - - function "getWorkflowVersion" - - then { - assertAll( - { assert function.success }, - { assert snapshot(function.result).match() } - ) - } - } - - test("Test Function dumpParametersToJSON") { - - function "dumpParametersToJSON" - - when { - function { - """ - // define inputs of the function here. Example: - input[0] = "$outputDir" - """.stripIndent() - } - } - - then { - assertAll( - { assert function.success } - ) - } - } - - test("Test Function checkCondaChannels") { - - function "checkCondaChannels" - - then { - assertAll( - { assert function.success }, - { assert snapshot(function.result).match() } - ) - } - } -} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap deleted file mode 100644 index 846287c417..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap +++ /dev/null @@ -1,20 +0,0 @@ -{ - "Test Function getWorkflowVersion": { - "content": [ - "v9.9.9" - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" - }, - "timestamp": "2024-02-28T12:02:05.308243" - }, - "Test Function checkCondaChannels": { - "content": null, - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" - }, - "timestamp": "2024-02-28T12:02:12.425833" - } -} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test deleted file mode 100644 index 02dbf094cd..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test +++ /dev/null @@ -1,113 +0,0 @@ -nextflow_workflow { - - name "Test Workflow UTILS_NEXTFLOW_PIPELINE" - script "../main.nf" - config "subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config" - workflow "UTILS_NEXTFLOW_PIPELINE" - tag 'subworkflows' - tag 'utils_nextflow_pipeline' - tag 'subworkflows/utils_nextflow_pipeline' - - test("Should run no inputs") { - - when { - workflow { - """ - print_version = false - dump_parameters = false - outdir = null - check_conda_channels = false - - input[0] = print_version - input[1] = dump_parameters - input[2] = outdir - input[3] = check_conda_channels - """ - } - } - - then { - assertAll( - { assert workflow.success } - ) - } - } - - test("Should print version") { - - when { - workflow { - """ - print_version = true - dump_parameters = false - outdir = null - check_conda_channels = false - - input[0] = print_version - input[1] = dump_parameters - input[2] = outdir - input[3] = check_conda_channels - """ - } - } - - then { - expect { - with(workflow) { - assert success - assert "nextflow_workflow v9.9.9" in stdout - } - } - } - } - - test("Should dump params") { - - when { - workflow { - """ - print_version = false - dump_parameters = true - outdir = 'results' - check_conda_channels = false - - input[0] = false - input[1] = true - input[2] = outdir - input[3] = false - """ - } - } - - then { - assertAll( - { assert workflow.success } - ) - } - } - - test("Should not create params JSON if no output directory") { - - when { - workflow { - """ - print_version = false - dump_parameters = true - outdir = null - check_conda_channels = false - - input[0] = false - input[1] = true - input[2] = outdir - input[3] = false - """ - } - } - - then { - assertAll( - { assert workflow.success } - ) - } - } -} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config deleted file mode 100644 index a09572e5bb..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config +++ /dev/null @@ -1,9 +0,0 @@ -manifest { - name = 'nextflow_workflow' - author = """nf-core""" - homePage = 'https://127.0.0.1' - description = """Dummy pipeline""" - nextflowVersion = '!>=23.04.0' - version = '9.9.9' - doi = 'https://doi.org/10.5281/zenodo.5070524' -} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml deleted file mode 100644 index f84761125a..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml +++ /dev/null @@ -1,2 +0,0 @@ -subworkflows/utils_nextflow_pipeline: - - subworkflows/nf-core/utils_nextflow_pipeline/** diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/main.nf b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/main.nf deleted file mode 100644 index bfd258760d..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/main.nf +++ /dev/null @@ -1,419 +0,0 @@ -// -// Subworkflow with utility functions specific to the nf-core pipeline template -// - -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - SUBWORKFLOW DEFINITION -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ - -workflow UTILS_NFCORE_PIPELINE { - take: - nextflow_cli_args - - main: - valid_config = checkConfigProvided() - checkProfileProvided(nextflow_cli_args) - - emit: - valid_config -} - -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - FUNCTIONS -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ - -// -// Warn if a -profile or Nextflow config has not been provided to run the pipeline -// -def checkConfigProvided() { - def valid_config = true as Boolean - if (workflow.profile == 'standard' && workflow.configFiles.size() <= 1) { - log.warn( - "[${workflow.manifest.name}] You are attempting to run the pipeline without any custom configuration!\n\n" + "This will be dependent on your local compute environment but can be achieved via one or more of the following:\n" + " (1) Using an existing pipeline profile e.g. `-profile docker` or `-profile singularity`\n" + " (2) Using an existing nf-core/configs for your Institution e.g. `-profile crick` or `-profile uppmax`\n" + " (3) Using your own local custom config e.g. `-c /path/to/your/custom.config`\n\n" + "Please refer to the quick start section and usage docs for the pipeline.\n " - ) - valid_config = false - } - return valid_config -} - -// -// Exit pipeline if --profile contains spaces -// -def checkProfileProvided(nextflow_cli_args) { - if (workflow.profile.endsWith(',')) { - error( - "The `-profile` option cannot end with a trailing comma, please remove it and re-run the pipeline!\n" + "HINT: A common mistake is to provide multiple values separated by spaces e.g. `-profile test, docker`.\n" - ) - } - if (nextflow_cli_args[0]) { - log.warn( - "nf-core pipelines do not accept positional arguments. The positional argument `${nextflow_cli_args[0]}` has been detected.\n" + "HINT: A common mistake is to provide multiple values separated by spaces e.g. `-profile test, docker`.\n" - ) - } -} - -// -// Generate workflow version string -// -def getWorkflowVersion() { - def version_string = "" as String - if (workflow.manifest.version) { - def prefix_v = workflow.manifest.version[0] != 'v' ? 'v' : '' - version_string += "${prefix_v}${workflow.manifest.version}" - } - - if (workflow.commitId) { - def git_shortsha = workflow.commitId.substring(0, 7) - version_string += "-g${git_shortsha}" - } - - return version_string -} - -// -// Get software versions for pipeline -// -def processVersionsFromYAML(yaml_file) { - def yaml = new org.yaml.snakeyaml.Yaml() - def versions = yaml.load(yaml_file).collectEntries { k, v -> [k.tokenize(':')[-1], v] } - return yaml.dumpAsMap(versions).trim() -} - -// -// Get workflow version for pipeline -// -def workflowVersionToYAML() { - return """ - Workflow: - ${workflow.manifest.name}: ${getWorkflowVersion()} - Nextflow: ${workflow.nextflow.version} - """.stripIndent().trim() -} - -// -// Get channel of software versions used in pipeline in YAML format -// -def softwareVersionsToYAML(ch_versions) { - return ch_versions.unique().map { version -> processVersionsFromYAML(version) }.unique().mix(Channel.of(workflowVersionToYAML())) -} - -// -// Get workflow summary for MultiQC -// -def paramsSummaryMultiqc(summary_params) { - def summary_section = '' - summary_params - .keySet() - .each { group -> - def group_params = summary_params.get(group) - // This gets the parameters of that particular group - if (group_params) { - summary_section += "

${group}

\n" - summary_section += "
\n" - group_params - .keySet() - .sort() - .each { param -> - summary_section += "
${param}
${group_params.get(param) ?: 'N/A'}
\n" - } - summary_section += "
\n" - } - } - - def yaml_file_text = "id: '${workflow.manifest.name.replace('/', '-')}-summary'\n" as String - yaml_file_text += "description: ' - this information is collected when the pipeline is started.'\n" - yaml_file_text += "section_name: '${workflow.manifest.name} Workflow Summary'\n" - yaml_file_text += "section_href: 'https://github.com/${workflow.manifest.name}'\n" - yaml_file_text += "plot_type: 'html'\n" - yaml_file_text += "data: |\n" - yaml_file_text += "${summary_section}" - - return yaml_file_text -} - -// -// ANSII colours used for terminal logging -// -def logColours(monochrome_logs=true) { - def colorcodes = [:] as Map - - // Reset / Meta - colorcodes['reset'] = monochrome_logs ? '' : "\033[0m" - colorcodes['bold'] = monochrome_logs ? '' : "\033[1m" - colorcodes['dim'] = monochrome_logs ? '' : "\033[2m" - colorcodes['underlined'] = monochrome_logs ? '' : "\033[4m" - colorcodes['blink'] = monochrome_logs ? '' : "\033[5m" - colorcodes['reverse'] = monochrome_logs ? '' : "\033[7m" - colorcodes['hidden'] = monochrome_logs ? '' : "\033[8m" - - // Regular Colors - colorcodes['black'] = monochrome_logs ? '' : "\033[0;30m" - colorcodes['red'] = monochrome_logs ? '' : "\033[0;31m" - colorcodes['green'] = monochrome_logs ? '' : "\033[0;32m" - colorcodes['yellow'] = monochrome_logs ? '' : "\033[0;33m" - colorcodes['blue'] = monochrome_logs ? '' : "\033[0;34m" - colorcodes['purple'] = monochrome_logs ? '' : "\033[0;35m" - colorcodes['cyan'] = monochrome_logs ? '' : "\033[0;36m" - colorcodes['white'] = monochrome_logs ? '' : "\033[0;37m" - - // Bold - colorcodes['bblack'] = monochrome_logs ? '' : "\033[1;30m" - colorcodes['bred'] = monochrome_logs ? '' : "\033[1;31m" - colorcodes['bgreen'] = monochrome_logs ? '' : "\033[1;32m" - colorcodes['byellow'] = monochrome_logs ? '' : "\033[1;33m" - colorcodes['bblue'] = monochrome_logs ? '' : "\033[1;34m" - colorcodes['bpurple'] = monochrome_logs ? '' : "\033[1;35m" - colorcodes['bcyan'] = monochrome_logs ? '' : "\033[1;36m" - colorcodes['bwhite'] = monochrome_logs ? '' : "\033[1;37m" - - // Underline - colorcodes['ublack'] = monochrome_logs ? '' : "\033[4;30m" - colorcodes['ured'] = monochrome_logs ? '' : "\033[4;31m" - colorcodes['ugreen'] = monochrome_logs ? '' : "\033[4;32m" - colorcodes['uyellow'] = monochrome_logs ? '' : "\033[4;33m" - colorcodes['ublue'] = monochrome_logs ? '' : "\033[4;34m" - colorcodes['upurple'] = monochrome_logs ? '' : "\033[4;35m" - colorcodes['ucyan'] = monochrome_logs ? '' : "\033[4;36m" - colorcodes['uwhite'] = monochrome_logs ? '' : "\033[4;37m" - - // High Intensity - colorcodes['iblack'] = monochrome_logs ? '' : "\033[0;90m" - colorcodes['ired'] = monochrome_logs ? '' : "\033[0;91m" - colorcodes['igreen'] = monochrome_logs ? '' : "\033[0;92m" - colorcodes['iyellow'] = monochrome_logs ? '' : "\033[0;93m" - colorcodes['iblue'] = monochrome_logs ? '' : "\033[0;94m" - colorcodes['ipurple'] = monochrome_logs ? '' : "\033[0;95m" - colorcodes['icyan'] = monochrome_logs ? '' : "\033[0;96m" - colorcodes['iwhite'] = monochrome_logs ? '' : "\033[0;97m" - - // Bold High Intensity - colorcodes['biblack'] = monochrome_logs ? '' : "\033[1;90m" - colorcodes['bired'] = monochrome_logs ? '' : "\033[1;91m" - colorcodes['bigreen'] = monochrome_logs ? '' : "\033[1;92m" - colorcodes['biyellow'] = monochrome_logs ? '' : "\033[1;93m" - colorcodes['biblue'] = monochrome_logs ? '' : "\033[1;94m" - colorcodes['bipurple'] = monochrome_logs ? '' : "\033[1;95m" - colorcodes['bicyan'] = monochrome_logs ? '' : "\033[1;96m" - colorcodes['biwhite'] = monochrome_logs ? '' : "\033[1;97m" - - return colorcodes -} - -// Return a single report from an object that may be a Path or List -// -def getSingleReport(multiqc_reports) { - if (multiqc_reports instanceof Path) { - return multiqc_reports - } else if (multiqc_reports instanceof List) { - if (multiqc_reports.size() == 0) { - log.warn("[${workflow.manifest.name}] No reports found from process 'MULTIQC'") - return null - } else if (multiqc_reports.size() == 1) { - return multiqc_reports.first() - } else { - log.warn("[${workflow.manifest.name}] Found multiple reports from process 'MULTIQC', will use only one") - return multiqc_reports.first() - } - } else { - return null - } -} - -// -// Construct and send completion email -// -def completionEmail(summary_params, email, email_on_fail, plaintext_email, outdir, monochrome_logs=true, multiqc_report=null) { - - // Set up the e-mail variables - def subject = "[${workflow.manifest.name}] Successful: ${workflow.runName}" - if (!workflow.success) { - subject = "[${workflow.manifest.name}] FAILED: ${workflow.runName}" - } - - def summary = [:] - summary_params - .keySet() - .sort() - .each { group -> - summary << summary_params[group] - } - - def misc_fields = [:] - misc_fields['Date Started'] = workflow.start - misc_fields['Date Completed'] = workflow.complete - misc_fields['Pipeline script file path'] = workflow.scriptFile - misc_fields['Pipeline script hash ID'] = workflow.scriptId - if (workflow.repository) { - misc_fields['Pipeline repository Git URL'] = workflow.repository - } - if (workflow.commitId) { - misc_fields['Pipeline repository Git Commit'] = workflow.commitId - } - if (workflow.revision) { - misc_fields['Pipeline Git branch/tag'] = workflow.revision - } - misc_fields['Nextflow Version'] = workflow.nextflow.version - misc_fields['Nextflow Build'] = workflow.nextflow.build - misc_fields['Nextflow Compile Timestamp'] = workflow.nextflow.timestamp - - def email_fields = [:] - email_fields['version'] = getWorkflowVersion() - email_fields['runName'] = workflow.runName - email_fields['success'] = workflow.success - email_fields['dateComplete'] = workflow.complete - email_fields['duration'] = workflow.duration - email_fields['exitStatus'] = workflow.exitStatus - email_fields['errorMessage'] = (workflow.errorMessage ?: 'None') - email_fields['errorReport'] = (workflow.errorReport ?: 'None') - email_fields['commandLine'] = workflow.commandLine - email_fields['projectDir'] = workflow.projectDir - email_fields['summary'] = summary << misc_fields - - // On success try attach the multiqc report - def mqc_report = getSingleReport(multiqc_report) - - // Check if we are only sending emails on failure - def email_address = email - if (!email && email_on_fail && !workflow.success) { - email_address = email_on_fail - } - - // Render the TXT template - def engine = new groovy.text.GStringTemplateEngine() - def tf = new File("${workflow.projectDir}/assets/email_template.txt") - def txt_template = engine.createTemplate(tf).make(email_fields) - def email_txt = txt_template.toString() - - // Render the HTML template - def hf = new File("${workflow.projectDir}/assets/email_template.html") - def html_template = engine.createTemplate(hf).make(email_fields) - def email_html = html_template.toString() - - // Render the sendmail template - def max_multiqc_email_size = (params.containsKey('max_multiqc_email_size') ? params.max_multiqc_email_size : 0) as MemoryUnit - def smail_fields = [email: email_address, subject: subject, email_txt: email_txt, email_html: email_html, projectDir: "${workflow.projectDir}", mqcFile: mqc_report, mqcMaxSize: max_multiqc_email_size.toBytes()] - def sf = new File("${workflow.projectDir}/assets/sendmail_template.txt") - def sendmail_template = engine.createTemplate(sf).make(smail_fields) - def sendmail_html = sendmail_template.toString() - - // Send the HTML e-mail - def colors = logColours(monochrome_logs) as Map - if (email_address) { - try { - if (plaintext_email) { - new org.codehaus.groovy.GroovyException('Send plaintext e-mail, not HTML') - } - // Try to send HTML e-mail using sendmail - def sendmail_tf = new File(workflow.launchDir.toString(), ".sendmail_tmp.html") - sendmail_tf.withWriter { w -> w << sendmail_html } - ['sendmail', '-t'].execute() << sendmail_html - log.info("-${colors.purple}[${workflow.manifest.name}]${colors.green} Sent summary e-mail to ${email_address} (sendmail)-") - } - catch (Exception msg) { - log.debug(msg.toString()) - log.debug("Trying with mail instead of sendmail") - // Catch failures and try with plaintext - def mail_cmd = ['mail', '-s', subject, '--content-type=text/html', email_address] - mail_cmd.execute() << email_html - log.info("-${colors.purple}[${workflow.manifest.name}]${colors.green} Sent summary e-mail to ${email_address} (mail)-") - } - } - - // Write summary e-mail HTML to a file - def output_hf = new File(workflow.launchDir.toString(), ".pipeline_report.html") - output_hf.withWriter { w -> w << email_html } - nextflow.extension.FilesEx.copyTo(output_hf.toPath(), "${outdir}/pipeline_info/pipeline_report.html") - output_hf.delete() - - // Write summary e-mail TXT to a file - def output_tf = new File(workflow.launchDir.toString(), ".pipeline_report.txt") - output_tf.withWriter { w -> w << email_txt } - nextflow.extension.FilesEx.copyTo(output_tf.toPath(), "${outdir}/pipeline_info/pipeline_report.txt") - output_tf.delete() -} - -// -// Print pipeline summary on completion -// -def completionSummary(monochrome_logs=true) { - def colors = logColours(monochrome_logs) as Map - if (workflow.success) { - if (workflow.stats.ignoredCount == 0) { - log.info("-${colors.purple}[${workflow.manifest.name}]${colors.green} Pipeline completed successfully${colors.reset}-") - } - else { - log.info("-${colors.purple}[${workflow.manifest.name}]${colors.yellow} Pipeline completed successfully, but with errored process(es) ${colors.reset}-") - } - } - else { - log.info("-${colors.purple}[${workflow.manifest.name}]${colors.red} Pipeline completed with errors${colors.reset}-") - } -} - -// -// Construct and send a notification to a web server as JSON e.g. Microsoft Teams and Slack -// -def imNotification(summary_params, hook_url) { - def summary = [:] - summary_params - .keySet() - .sort() - .each { group -> - summary << summary_params[group] - } - - def misc_fields = [:] - misc_fields['start'] = workflow.start - misc_fields['complete'] = workflow.complete - misc_fields['scriptfile'] = workflow.scriptFile - misc_fields['scriptid'] = workflow.scriptId - if (workflow.repository) { - misc_fields['repository'] = workflow.repository - } - if (workflow.commitId) { - misc_fields['commitid'] = workflow.commitId - } - if (workflow.revision) { - misc_fields['revision'] = workflow.revision - } - misc_fields['nxf_version'] = workflow.nextflow.version - misc_fields['nxf_build'] = workflow.nextflow.build - misc_fields['nxf_timestamp'] = workflow.nextflow.timestamp - - def msg_fields = [:] - msg_fields['version'] = getWorkflowVersion() - msg_fields['runName'] = workflow.runName - msg_fields['success'] = workflow.success - msg_fields['dateComplete'] = workflow.complete - msg_fields['duration'] = workflow.duration - msg_fields['exitStatus'] = workflow.exitStatus - msg_fields['errorMessage'] = (workflow.errorMessage ?: 'None') - msg_fields['errorReport'] = (workflow.errorReport ?: 'None') - msg_fields['commandLine'] = workflow.commandLine.replaceFirst(/ +--hook_url +[^ ]+/, "") - msg_fields['projectDir'] = workflow.projectDir - msg_fields['summary'] = summary << misc_fields - - // Render the JSON template - def engine = new groovy.text.GStringTemplateEngine() - // Different JSON depending on the service provider - // Defaults to "Adaptive Cards" (https://adaptivecards.io), except Slack which has its own format - def json_path = hook_url.contains("hooks.slack.com") ? "slackreport.json" : "adaptivecard.json" - def hf = new File("${workflow.projectDir}/assets/${json_path}") - def json_template = engine.createTemplate(hf).make(msg_fields) - def json_message = json_template.toString() - - // POST - def post = new URL(hook_url).openConnection() - post.setRequestMethod("POST") - post.setDoOutput(true) - post.setRequestProperty("Content-Type", "application/json") - post.getOutputStream().write(json_message.getBytes("UTF-8")) - def postRC = post.getResponseCode() - if (!postRC.equals(200)) { - log.warn(post.getErrorStream().getText()) - } -} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/meta.yml b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/meta.yml deleted file mode 100644 index d08d24342d..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/meta.yml +++ /dev/null @@ -1,24 +0,0 @@ -# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json -name: "UTILS_NFCORE_PIPELINE" -description: Subworkflow with utility functions specific to the nf-core pipeline template -keywords: - - utility - - pipeline - - initialise - - version -components: [] -input: - - nextflow_cli_args: - type: list - description: | - Nextflow CLI positional arguments -output: - - success: - type: boolean - description: | - Dummy output to indicate success -authors: - - "@adamrtalbot" -maintainers: - - "@adamrtalbot" - - "@maxulysse" diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test deleted file mode 100644 index f117040cbd..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test +++ /dev/null @@ -1,126 +0,0 @@ - -nextflow_function { - - name "Test Functions" - script "../main.nf" - config "subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config" - tag "subworkflows" - tag "subworkflows_nfcore" - tag "utils_nfcore_pipeline" - tag "subworkflows/utils_nfcore_pipeline" - - test("Test Function checkConfigProvided") { - - function "checkConfigProvided" - - then { - assertAll( - { assert function.success }, - { assert snapshot(function.result).match() } - ) - } - } - - test("Test Function checkProfileProvided") { - - function "checkProfileProvided" - - when { - function { - """ - input[0] = [] - """ - } - } - - then { - assertAll( - { assert function.success }, - { assert snapshot(function.result).match() } - ) - } - } - - test("Test Function without logColours") { - - function "logColours" - - when { - function { - """ - input[0] = true - """ - } - } - - then { - assertAll( - { assert function.success }, - { assert snapshot(function.result).match() } - ) - } - } - - test("Test Function with logColours") { - function "logColours" - - when { - function { - """ - input[0] = false - """ - } - } - - then { - assertAll( - { assert function.success }, - { assert snapshot(function.result).match() } - ) - } - } - - test("Test Function getSingleReport with a single file") { - function "getSingleReport" - - when { - function { - """ - input[0] = file(params.modules_testdata_base_path + '/generic/tsv/test.tsv', checkIfExists: true) - """ - } - } - - then { - assertAll( - { assert function.success }, - { assert function.result.contains("test.tsv") } - ) - } - } - - test("Test Function getSingleReport with multiple files") { - function "getSingleReport" - - when { - function { - """ - input[0] = [ - file(params.modules_testdata_base_path + '/generic/tsv/test.tsv', checkIfExists: true), - file(params.modules_testdata_base_path + '/generic/tsv/network.tsv', checkIfExists: true), - file(params.modules_testdata_base_path + '/generic/tsv/expression.tsv', checkIfExists: true) - ] - """ - } - } - - then { - assertAll( - { assert function.success }, - { assert function.result.contains("test.tsv") }, - { assert !function.result.contains("network.tsv") }, - { assert !function.result.contains("expression.tsv") } - ) - } - } -} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap deleted file mode 100644 index b13b311213..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap +++ /dev/null @@ -1,136 +0,0 @@ -{ - "Test Function checkProfileProvided": { - "content": null, - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" - }, - "timestamp": "2024-02-28T12:03:03.360873" - }, - "Test Function checkConfigProvided": { - "content": [ - true - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" - }, - "timestamp": "2024-02-28T12:02:59.729647" - }, - "Test Function without logColours": { - "content": [ - { - "reset": "", - "bold": "", - "dim": "", - "underlined": "", - "blink": "", - "reverse": "", - "hidden": "", - "black": "", - "red": "", - "green": "", - "yellow": "", - "blue": "", - "purple": "", - "cyan": "", - "white": "", - "bblack": "", - "bred": "", - "bgreen": "", - "byellow": "", - "bblue": "", - "bpurple": "", - "bcyan": "", - "bwhite": "", - "ublack": "", - "ured": "", - "ugreen": "", - "uyellow": "", - "ublue": "", - "upurple": "", - "ucyan": "", - "uwhite": "", - "iblack": "", - "ired": "", - "igreen": "", - "iyellow": "", - "iblue": "", - "ipurple": "", - "icyan": "", - "iwhite": "", - "biblack": "", - "bired": "", - "bigreen": "", - "biyellow": "", - "biblue": "", - "bipurple": "", - "bicyan": "", - "biwhite": "" - } - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" - }, - "timestamp": "2024-02-28T12:03:17.969323" - }, - "Test Function with logColours": { - "content": [ - { - "reset": "\u001b[0m", - "bold": "\u001b[1m", - "dim": "\u001b[2m", - "underlined": "\u001b[4m", - "blink": "\u001b[5m", - "reverse": "\u001b[7m", - "hidden": "\u001b[8m", - "black": "\u001b[0;30m", - "red": "\u001b[0;31m", - "green": "\u001b[0;32m", - "yellow": "\u001b[0;33m", - "blue": "\u001b[0;34m", - "purple": "\u001b[0;35m", - "cyan": "\u001b[0;36m", - "white": "\u001b[0;37m", - "bblack": "\u001b[1;30m", - "bred": "\u001b[1;31m", - "bgreen": "\u001b[1;32m", - "byellow": "\u001b[1;33m", - "bblue": "\u001b[1;34m", - "bpurple": "\u001b[1;35m", - "bcyan": "\u001b[1;36m", - "bwhite": "\u001b[1;37m", - "ublack": "\u001b[4;30m", - "ured": "\u001b[4;31m", - "ugreen": "\u001b[4;32m", - "uyellow": "\u001b[4;33m", - "ublue": "\u001b[4;34m", - "upurple": "\u001b[4;35m", - "ucyan": "\u001b[4;36m", - "uwhite": "\u001b[4;37m", - "iblack": "\u001b[0;90m", - "ired": "\u001b[0;91m", - "igreen": "\u001b[0;92m", - "iyellow": "\u001b[0;93m", - "iblue": "\u001b[0;94m", - "ipurple": "\u001b[0;95m", - "icyan": "\u001b[0;96m", - "iwhite": "\u001b[0;97m", - "biblack": "\u001b[1;90m", - "bired": "\u001b[1;91m", - "bigreen": "\u001b[1;92m", - "biyellow": "\u001b[1;93m", - "biblue": "\u001b[1;94m", - "bipurple": "\u001b[1;95m", - "bicyan": "\u001b[1;96m", - "biwhite": "\u001b[1;97m" - } - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" - }, - "timestamp": "2024-02-28T12:03:21.714424" - } -} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test deleted file mode 100644 index 8940d32d1e..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test +++ /dev/null @@ -1,29 +0,0 @@ -nextflow_workflow { - - name "Test Workflow UTILS_NFCORE_PIPELINE" - script "../main.nf" - config "subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config" - workflow "UTILS_NFCORE_PIPELINE" - tag "subworkflows" - tag "subworkflows_nfcore" - tag "utils_nfcore_pipeline" - tag "subworkflows/utils_nfcore_pipeline" - - test("Should run without failures") { - - when { - workflow { - """ - input[0] = [] - """ - } - } - - then { - assertAll( - { assert workflow.success }, - { assert snapshot(workflow.out).match() } - ) - } - } -} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap deleted file mode 100644 index 84ee1e1d1e..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap +++ /dev/null @@ -1,19 +0,0 @@ -{ - "Should run without failures": { - "content": [ - { - "0": [ - true - ], - "valid_config": [ - true - ] - } - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" - }, - "timestamp": "2024-02-28T12:03:25.726491" - } -} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config deleted file mode 100644 index d0a926bf6d..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config +++ /dev/null @@ -1,9 +0,0 @@ -manifest { - name = 'nextflow_workflow' - author = """nf-core""" - homePage = 'https://127.0.0.1' - description = """Dummy pipeline""" - nextflowVersion = '!>=23.04.0' - version = '9.9.9' - doi = 'https://doi.org/10.5281/zenodo.5070524' -} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml deleted file mode 100644 index ac8523c9a2..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml +++ /dev/null @@ -1,2 +0,0 @@ -subworkflows/utils_nfcore_pipeline: - - subworkflows/nf-core/utils_nfcore_pipeline/** diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/main.nf b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/main.nf deleted file mode 100644 index 93de2a5245..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/main.nf +++ /dev/null @@ -1,45 +0,0 @@ -// -// Subworkflow that uses the nf-schema plugin to validate parameters and render the parameter summary -// - -include { paramsSummaryLog } from 'plugin/nf-schema' -include { validateParameters } from 'plugin/nf-schema' - -workflow UTILS_NFSCHEMA_PLUGIN { - - take: - input_workflow // workflow: the workflow object used by nf-schema to get metadata from the workflow - validate_params // boolean: validate the parameters - parameters_schema // string: path to the parameters JSON schema. - // this has to be the same as the schema given to `validation.parametersSchema` - // when this input is empty it will automatically use the configured schema or - // "${projectDir}/nextflow_schema.json" as default. This input should not be empty - // for meta pipelines - - main: - - // - // Print parameter summary to stdout. This will display the parameters - // that differ from the default given in the JSON schema - // - if(parameters_schema) { - log.info paramsSummaryLog(input_workflow, parameters_schema:parameters_schema) - } else { - log.info paramsSummaryLog(input_workflow) - } - - // - // Validate the parameters using nextflow_schema.json or the schema - // given via the validation.parametersSchema configuration option - // - if(validate_params) { - if(parameters_schema) { - validateParameters(parameters_schema:parameters_schema) - } else { - validateParameters() - } - } - - emit: - dummy_emit = true -} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/meta.yml b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/meta.yml deleted file mode 100644 index f7d9f02885..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/meta.yml +++ /dev/null @@ -1,35 +0,0 @@ -# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json -name: "utils_nfschema_plugin" -description: Run nf-schema to validate parameters and create a summary of changed parameters -keywords: - - validation - - JSON schema - - plugin - - parameters - - summary -components: [] -input: - - input_workflow: - type: object - description: | - The workflow object of the used pipeline. - This object contains meta data used to create the params summary log - - validate_params: - type: boolean - description: Validate the parameters and error if invalid. - - parameters_schema: - type: string - description: | - Path to the parameters JSON schema. - This has to be the same as the schema given to the `validation.parametersSchema` config - option. When this input is empty it will automatically use the configured schema or - "${projectDir}/nextflow_schema.json" as default. The schema should not be given in this way - for meta pipelines. -output: - - dummy_emit: - type: boolean - description: Dummy emit to make nf-core subworkflows lint happy -authors: - - "@nvnieuwk" -maintainers: - - "@nvnieuwk" diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test deleted file mode 100644 index 8fb3016487..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test +++ /dev/null @@ -1,117 +0,0 @@ -nextflow_workflow { - - name "Test Subworkflow UTILS_NFSCHEMA_PLUGIN" - script "../main.nf" - workflow "UTILS_NFSCHEMA_PLUGIN" - - tag "subworkflows" - tag "subworkflows_nfcore" - tag "subworkflows/utils_nfschema_plugin" - tag "plugin/nf-schema" - - config "./nextflow.config" - - test("Should run nothing") { - - when { - - params { - test_data = '' - } - - workflow { - """ - validate_params = false - input[0] = workflow - input[1] = validate_params - input[2] = "" - """ - } - } - - then { - assertAll( - { assert workflow.success } - ) - } - } - - test("Should validate params") { - - when { - - params { - test_data = '' - outdir = null - } - - workflow { - """ - validate_params = true - input[0] = workflow - input[1] = validate_params - input[2] = "" - """ - } - } - - then { - assertAll( - { assert workflow.failed }, - { assert workflow.stdout.any { it.contains('ERROR ~ Validation of pipeline parameters failed!') } } - ) - } - } - - test("Should run nothing - custom schema") { - - when { - - params { - test_data = '' - } - - workflow { - """ - validate_params = false - input[0] = workflow - input[1] = validate_params - input[2] = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" - """ - } - } - - then { - assertAll( - { assert workflow.success } - ) - } - } - - test("Should validate params - custom schema") { - - when { - - params { - test_data = '' - outdir = null - } - - workflow { - """ - validate_params = true - input[0] = workflow - input[1] = validate_params - input[2] = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" - """ - } - } - - then { - assertAll( - { assert workflow.failed }, - { assert workflow.stdout.any { it.contains('ERROR ~ Validation of pipeline parameters failed!') } } - ) - } - } -} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config deleted file mode 100644 index 478fb8a05f..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config +++ /dev/null @@ -1,8 +0,0 @@ -plugins { - id "nf-schema@2.1.0" -} - -validation { - parametersSchema = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" - monochromeLogs = true -} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json deleted file mode 100644 index 91e26fc4a7..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json +++ /dev/null @@ -1,103 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://raw.githubusercontent.com/./master/nextflow_schema.json", - "title": ". pipeline parameters", - "description": "", - "type": "object", - "$defs": { - "input_output_options": { - "title": "Input/output options", - "type": "object", - "fa_icon": "fas fa-terminal", - "description": "Define where the pipeline should find input data and save output data.", - "required": ["outdir"], - "properties": { - "validate_params": { - "type": "boolean", - "description": "Validate parameters?", - "default": true, - "hidden": true - }, - "outdir": { - "type": "string", - "format": "directory-path", - "description": "The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure.", - "fa_icon": "fas fa-folder-open" - }, - "test_data_base": { - "type": "string", - "default": "https://raw.githubusercontent.com/nf-core/test-datasets/modules", - "description": "Base for test data directory", - "hidden": true - }, - "test_data": { - "type": "string", - "description": "Fake test data param", - "hidden": true - } - } - }, - "generic_options": { - "title": "Generic options", - "type": "object", - "fa_icon": "fas fa-file-import", - "description": "Less common options for the pipeline, typically set in a config file.", - "help_text": "These options are common to all nf-core pipelines and allow you to customise some of the core preferences for how the pipeline runs.\n\nTypically these options would be set in a Nextflow config file loaded for all pipeline runs, such as `~/.nextflow/config`.", - "properties": { - "help": { - "type": "boolean", - "description": "Display help text.", - "fa_icon": "fas fa-question-circle", - "hidden": true - }, - "version": { - "type": "boolean", - "description": "Display version and exit.", - "fa_icon": "fas fa-question-circle", - "hidden": true - }, - "logo": { - "type": "boolean", - "default": true, - "description": "Display nf-core logo in console output.", - "fa_icon": "fas fa-image", - "hidden": true - }, - "singularity_pull_docker_container": { - "type": "boolean", - "description": "Pull Singularity container from Docker?", - "hidden": true - }, - "publish_dir_mode": { - "type": "string", - "default": "copy", - "description": "Method used to save pipeline results to output directory.", - "help_text": "The Nextflow `publishDir` option specifies which intermediate files should be saved to the output directory. This option tells the pipeline what method should be used to move these files. See [Nextflow docs](https://www.nextflow.io/docs/latest/process.html#publishdir) for details.", - "fa_icon": "fas fa-copy", - "enum": [ - "symlink", - "rellink", - "link", - "copy", - "copyNoFollow", - "move" - ], - "hidden": true - }, - "monochrome_logs": { - "type": "boolean", - "description": "Use monochrome_logs", - "hidden": true - } - } - } - }, - "allOf": [ - { - "$ref": "#/$defs/input_output_options" - }, - { - "$ref": "#/$defs/generic_options" - } - ] -} diff --git a/hello-nf-core/solutions/core-hello-part4/test-results/cat/test.txt b/hello-nf-core/solutions/core-hello-part4/test-results/cat/test.txt new file mode 100644 index 0000000000..c030fd55e9 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/test-results/cat/test.txt @@ -0,0 +1,3 @@ +BONJOUR +HOLà +HELLO diff --git a/hello-nf-core/solutions/core-hello-part4/test-results/cowpy/cowpy-test.txt b/hello-nf-core/solutions/core-hello-part4/test-results/cowpy/cowpy-test.txt new file mode 100644 index 0000000000..8221aa139a --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/test-results/cowpy/cowpy-test.txt @@ -0,0 +1,14 @@ + _________ +/ BONJOUR \ +| HOLà | +\ HELLO / + --------- + \ + \ + .--. + |o_o | + |:_/ | + // \ \ + (| | ) + /'\_ _/`\ + \___)=(___/ diff --git a/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/execution_report_2025-10-24_10-21-35.html b/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/execution_report_2025-10-24_10-21-35.html new file mode 100644 index 0000000000..a2f0464222 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/execution_report_2025-10-24_10-21-35.html @@ -0,0 +1,1040 @@ + + + + + + + + + + + [stoic_murdock] Nextflow Workflow Report + + + + + + + +
+
+ +

Nextflow workflow report

+

[stoic_murdock]

+ + +
+ Workflow execution completed successfully! +
+ + +
+
Run times
+
+ 24-Oct-2025 10:21:35 - 24-Oct-2025 10:21:40 + (duration: 4.8s) +
+ +
+
+
  8 succeeded  
+
  0 cached  
+
  0 ignored  
+
  0 failed (0 retries)  
+
+
+ +
Nextflow command
+
nextflow run . --outdir test-results -profile test,docker --validate_params false
+
+ +
+
CPU-Hours
+
(a few seconds)
+ +
Launch directory
+
/Users/jonathan.manning/projects/training/hello-nf-core/solutions/core-hello-part4
+ +
Work directory
+
/Users/jonathan.manning/projects/training/hello-nf-core/solutions/core-hello-part4/work
+ +
Project directory
+
/Users/jonathan.manning/projects/training/hello-nf-core/solutions/core-hello-part4
+ + +
Script name
+
main.nf
+ + + +
Script ID
+
c31b966b36e0478a8d80dfe8f16f3c88
+ + +
Workflow session
+
77b25c4a-878f-48f7-84c1-00ab8843ad22
+ + + +
Workflow profile
+
test,docker
+ + + + + +
Nextflow version
+
version 25.04.6, build 5954 (01-07-2025 11:27 UTC)
+
+
+
+ +
+

Resource Usage

+

These plots give an overview of the distribution of resource usage for each process.

+ +

CPU

+ +
+
+
+
+
+
+
+ +
+ +

Memory

+ +
+
+
+
+
+
+
+
+
+
+
+ +

Job Duration

+ +
+
+
+
+
+
+
+
+ +

I/O

+ +
+
+
+
+
+
+
+
+
+ +
+
+

Tasks

+

This table shows information about each task in the workflow. Use the search box on the right + to filter rows for specific values. Clicking headers will sort the table by that value and + scrolling side to side will reveal more columns.

+
+ + +
+
+
+
+
+ +
+ (tasks table omitted because the dataset is too big) +
+
+ +
+
+ Generated by Nextflow, version 25.04.6 +
+
+ + + + + diff --git a/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/execution_timeline_2025-10-24_10-21-35.html b/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/execution_timeline_2025-10-24_10-21-35.html new file mode 100644 index 0000000000..8ca1ee1f79 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/execution_timeline_2025-10-24_10-21-35.html @@ -0,0 +1,230 @@ + + + + + + + + + + + + + +
+

Processes execution timeline

+

+ Launch time:
+ Elapsed time:
+ Legend: job wall time / memory usage (RAM) +

+
+
+ + + + + + + diff --git a/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/execution_trace_2025-10-24_10-21-35.txt b/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/execution_trace_2025-10-24_10-21-35.txt new file mode 100644 index 0000000000..cd44d8b797 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/execution_trace_2025-10-24_10-21-35.txt @@ -0,0 +1,9 @@ +task_id hash native_id name status exit submit duration realtime %cpu peak_rss peak_vmem rchar wchar +3 17/60765a 73496 CORE_HELLO:HELLO:sayHello (3) COMPLETED 0 2025-10-24 10:21:36.784 93ms 20ms - - - - - +1 a6/e22ff7 73495 CORE_HELLO:HELLO:sayHello (1) COMPLETED 0 2025-10-24 10:21:36.781 94ms 19ms - - - - - +2 14/8cd9f9 73493 CORE_HELLO:HELLO:sayHello (2) COMPLETED 0 2025-10-24 10:21:36.774 97ms 19ms - - - - - +6 c5/4b94fb 73573 CORE_HELLO:HELLO:convertToUpper (3) COMPLETED 0 2025-10-24 10:21:36.908 110ms 30ms - - - - - +5 ce/b14e47 73575 CORE_HELLO:HELLO:convertToUpper (2) COMPLETED 0 2025-10-24 10:21:36.920 113ms 31ms - - - - - +4 1e/2658c1 73577 CORE_HELLO:HELLO:convertToUpper (1) COMPLETED 0 2025-10-24 10:21:36.923 116ms 31ms - - - - - +7 78/7a1a50 73669 CORE_HELLO:HELLO:CAT_CAT (test) COMPLETED 0 2025-10-24 10:21:37.070 1.9s 0ms 117.6% 11.3 MB 176.8 MB 80.4 KB 310 B +8 0a/c69b7f 73802 CORE_HELLO:HELLO:cowpy COMPLETED 0 2025-10-24 10:21:38.970 1.5s 348ms 115.7% 47.3 MB 353.5 MB 1.2 MB 4.5 KB diff --git a/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/hello_software_versions.yml b/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/hello_software_versions.yml new file mode 100644 index 0000000000..f2ec349f67 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/hello_software_versions.yml @@ -0,0 +1,3 @@ +Workflow: + core/hello: v1.0.0dev + Nextflow: 25.04.6 diff --git a/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/params_2025-10-24_10-21-36.json b/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/params_2025-10-24_10-21-36.json new file mode 100644 index 0000000000..4e8708ff9a --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/params_2025-10-24_10-21-36.json @@ -0,0 +1,21 @@ +{ + "input": "/Users/jonathan.manning/projects/training/hello-nf-core/solutions/core-hello-part4/assets/greetings.csv", + "outdir": "test-results", + "publish_dir_mode": "copy", + "monochrome_logs": false, + "help": false, + "help_full": false, + "show_hidden": false, + "version": false, + "pipelines_testdata_base_path": "https://raw.githubusercontent.com/nf-core/test-datasets/", + "trace_report_suffix": "2025-10-24_10-21-35", + "config_profile_name": "Test profile", + "config_profile_description": "Minimal test dataset to check pipeline function", + "custom_config_version": "master", + "validate_params": false, + "custom_config_base": "https://raw.githubusercontent.com/nf-core/configs/master", + "config_profile_contact": null, + "config_profile_url": null, + "batch": "test", + "character": "tux" +} \ No newline at end of file diff --git a/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/pipeline_dag_2025-10-24_10-21-35.html b/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/pipeline_dag_2025-10-24_10-21-35.html new file mode 100644 index 0000000000..4f540abbe3 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/pipeline_dag_2025-10-24_10-21-35.html @@ -0,0 +1,71 @@ + + + + + + +
+%%{
+  init: {
+    'theme': 'base',
+    'themeVariables': {
+      'primaryColor': '#B6ECE2',
+      'primaryTextColor': '#160F26',
+      'primaryBorderColor': '#065647',
+      'lineColor': '#545555',
+      'clusterBkg': '#BABCBD22',
+      'clusterBorder': '#DDDEDE',
+      'fontFamily': 'arial'
+    }
+  }
+}%%
+flowchart TB
+    subgraph " "
+    v2["Channel.fromList"]
+    end
+    subgraph "CORE_HELLO [CORE_HELLO]"
+    subgraph "CORE_HELLO:HELLO [HELLO]"
+    v4(["sayHello"])
+    v5(["convertToUpper"])
+    v8(["CAT_CAT"])
+    v10(["cowpy"])
+    v6(( ))
+    end
+    end
+    subgraph " "
+    v9[" "]
+    v11["cowpy_hellos"]
+    end
+    subgraph "PIPELINE_INITIALISATION [PIPELINE_INITIALISATION]"
+    v3(( ))
+    end
+    v2 --> v3
+    v3 --> v4
+    v4 --> v5
+    v5 --> v6
+    v6 --> v8
+    v8 --> v10
+    v8 --> v9
+    v10 --> v11
+
+
+ + + diff --git a/hello-nf-core/solutions/core-hello-part4/workflows/hello.nf b/hello-nf-core/solutions/core-hello-part4/workflows/hello.nf deleted file mode 100644 index 8d9b97f566..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/workflows/hello.nf +++ /dev/null @@ -1,65 +0,0 @@ -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ -include { paramsSummaryMap } from 'plugin/nf-schema' -include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' -include { sayHello } from '../modules/local/sayHello.nf' -include { convertToUpper } from '../modules/local/convertToUpper.nf' -include { cowpy } from '../modules/local/cowpy.nf' -include { CAT_CAT } from '../modules/nf-core/cat/cat/main' - -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - RUN MAIN WORKFLOW -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ - -workflow HELLO { - - take: - ch_samplesheet // channel: samplesheet read in from --input - main: - - // emit a greeting - sayHello(ch_samplesheet) - - // convert the greeting to uppercase - convertToUpper(sayHello.out) - - // collect all the greetings into one file using nf-core cat/cat module - // create metadata map with batch name as the ID - def cat_meta = [ id: params.batch ] - ch_for_cat = convertToUpper.out.collect().map { files -> tuple(cat_meta, files) } - - CAT_CAT(ch_for_cat) - - // generate ASCII art of the greetings with cowpy - cowpy(CAT_CAT.out.file_out) - - ch_versions = Channel.empty() - - // - // Collate and save software versions - // - softwareVersionsToYAML(ch_versions) - .collectFile( - storeDir: "${params.outdir}/pipeline_info", - name: 'hello_software_' + 'versions.yml', - sort: true, - newLine: true - ).set { ch_collated_versions } - - - emit: - cowpy_hellos = cowpy.out.cowpy_output - versions = ch_versions // channel: [ path(versions.yml) ] - -} - -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - THE END -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ From 9903a258ed622f85ab588d3eed110f9e837d3a72 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 21:41:44 +0000 Subject: [PATCH 057/113] Fix up part 4 solution --- .../solutions/core-hello-part4/.nf-core.yml | 106 ++ .../solutions/core-hello-part4/README.md | 75 ++ .../core-hello-part4/assets/greetings.csv | 3 + .../core-hello-part4/assets/samplesheet.csv | 3 + .../core-hello-part4/assets/schema_input.json | 33 + .../core-hello-part4/conf/base.config | 66 ++ .../core-hello-part4/conf/modules.config | 26 + .../core-hello-part4/conf/test.config | 31 + .../core-hello-part4/conf/test_full.config | 24 + .../solutions/core-hello-part4/docs/README.md | 8 + .../solutions/core-hello-part4/docs/output.md | 29 + .../solutions/core-hello-part4/docs/usage.md | 211 ++++ .../solutions/core-hello-part4/main.nf | 85 ++ .../solutions/core-hello-part4/modules.json | 36 + .../modules/local/convertToUpper.nf | 20 + .../core-hello-part4/modules/local/cowpy.nf | 21 + .../modules/local/cowpy/environment.yml | 10 + .../modules/local/cowpy/main.nf | 47 + .../modules/local/cowpy/meta.yml | 51 + .../modules/local/cowpy/tests/main.nf.test | 78 ++ .../modules/local/sayHello.nf | 20 + .../modules/nf-core/cat/cat/environment.yml | 7 + .../modules/nf-core/cat/cat/main.nf | 78 ++ .../modules/nf-core/cat/cat/meta.yml | 46 + .../nf-core/cat/cat/tests/main.nf.test | 191 +++ .../nf-core/cat/cat/tests/main.nf.test.snap | 147 +++ .../cat/tests/nextflow_unzipped_zipped.config | 6 + .../cat/tests/nextflow_zipped_unzipped.config | 8 + .../core-hello-part4/nextflow.config | 252 ++++ .../core-hello-part4/nextflow_schema.json | 163 +++ .../local/utils_nfcore_hello_pipeline/main.nf | 136 +++ .../nf-core/utils_nextflow_pipeline/main.nf | 126 ++ .../nf-core/utils_nextflow_pipeline/meta.yml | 38 + .../tests/main.function.nf.test | 54 + .../tests/main.function.nf.test.snap | 20 + .../tests/main.workflow.nf.test | 113 ++ .../tests/nextflow.config | 9 + .../nf-core/utils_nfcore_pipeline/main.nf | 419 +++++++ .../nf-core/utils_nfcore_pipeline/meta.yml | 24 + .../tests/main.function.nf.test | 126 ++ .../tests/main.function.nf.test.snap | 136 +++ .../tests/main.workflow.nf.test | 29 + .../tests/main.workflow.nf.test.snap | 19 + .../tests/nextflow.config | 9 + .../nf-core/utils_nfschema_plugin/main.nf | 74 ++ .../nf-core/utils_nfschema_plugin/meta.yml | 35 + .../utils_nfschema_plugin/tests/main.nf.test | 173 +++ .../tests/nextflow.config | 8 + .../tests/nextflow_schema.json | 103 ++ .../test-results/cat/test.txt | 3 - .../test-results/cowpy/cowpy-test.txt | 14 - .../execution_report_2025-10-24_10-21-35.html | 1040 ----------------- ...xecution_timeline_2025-10-24_10-21-35.html | 230 ---- .../execution_trace_2025-10-24_10-21-35.txt | 9 - .../pipeline_info/hello_software_versions.yml | 3 - .../params_2025-10-24_10-21-36.json | 21 - .../pipeline_dag_2025-10-24_10-21-35.html | 71 -- .../core-hello-part4/workflows/hello.nf | 67 ++ 58 files changed, 3599 insertions(+), 1391 deletions(-) create mode 100644 hello-nf-core/solutions/core-hello-part4/.nf-core.yml create mode 100644 hello-nf-core/solutions/core-hello-part4/README.md create mode 100644 hello-nf-core/solutions/core-hello-part4/assets/greetings.csv create mode 100644 hello-nf-core/solutions/core-hello-part4/assets/samplesheet.csv create mode 100644 hello-nf-core/solutions/core-hello-part4/assets/schema_input.json create mode 100644 hello-nf-core/solutions/core-hello-part4/conf/base.config create mode 100644 hello-nf-core/solutions/core-hello-part4/conf/modules.config create mode 100644 hello-nf-core/solutions/core-hello-part4/conf/test.config create mode 100644 hello-nf-core/solutions/core-hello-part4/conf/test_full.config create mode 100644 hello-nf-core/solutions/core-hello-part4/docs/README.md create mode 100644 hello-nf-core/solutions/core-hello-part4/docs/output.md create mode 100644 hello-nf-core/solutions/core-hello-part4/docs/usage.md create mode 100644 hello-nf-core/solutions/core-hello-part4/main.nf create mode 100644 hello-nf-core/solutions/core-hello-part4/modules.json create mode 100644 hello-nf-core/solutions/core-hello-part4/modules/local/convertToUpper.nf create mode 100644 hello-nf-core/solutions/core-hello-part4/modules/local/cowpy.nf create mode 100644 hello-nf-core/solutions/core-hello-part4/modules/local/cowpy/environment.yml create mode 100644 hello-nf-core/solutions/core-hello-part4/modules/local/cowpy/main.nf create mode 100644 hello-nf-core/solutions/core-hello-part4/modules/local/cowpy/meta.yml create mode 100644 hello-nf-core/solutions/core-hello-part4/modules/local/cowpy/tests/main.nf.test create mode 100644 hello-nf-core/solutions/core-hello-part4/modules/local/sayHello.nf create mode 100644 hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/environment.yml create mode 100644 hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/main.nf create mode 100644 hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/meta.yml create mode 100644 hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/main.nf.test create mode 100644 hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/main.nf.test.snap create mode 100644 hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/nextflow_unzipped_zipped.config create mode 100644 hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/nextflow_zipped_unzipped.config create mode 100644 hello-nf-core/solutions/core-hello-part4/nextflow.config create mode 100644 hello-nf-core/solutions/core-hello-part4/nextflow_schema.json create mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/local/utils_nfcore_hello_pipeline/main.nf create mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/main.nf create mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/meta.yml create mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test create mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap create mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test create mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config create mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/main.nf create mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/meta.yml create mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test create mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap create mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test create mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap create mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config create mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/main.nf create mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/meta.yml create mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test create mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config create mode 100644 hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json delete mode 100644 hello-nf-core/solutions/core-hello-part4/test-results/cat/test.txt delete mode 100644 hello-nf-core/solutions/core-hello-part4/test-results/cowpy/cowpy-test.txt delete mode 100644 hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/execution_report_2025-10-24_10-21-35.html delete mode 100644 hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/execution_timeline_2025-10-24_10-21-35.html delete mode 100644 hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/execution_trace_2025-10-24_10-21-35.txt delete mode 100644 hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/hello_software_versions.yml delete mode 100644 hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/params_2025-10-24_10-21-36.json delete mode 100644 hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/pipeline_dag_2025-10-24_10-21-35.html create mode 100644 hello-nf-core/solutions/core-hello-part4/workflows/hello.nf diff --git a/hello-nf-core/solutions/core-hello-part4/.nf-core.yml b/hello-nf-core/solutions/core-hello-part4/.nf-core.yml new file mode 100644 index 0000000000..1a638d8288 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/.nf-core.yml @@ -0,0 +1,106 @@ +repository_type: pipeline + +nf_core_version: 3.4.1 + +lint: + files_unchanged: + - .github/CONTRIBUTING.md + - .prettierignore + - .prettierignore + - .prettierignore + - CODE_OF_CONDUCT.md + - assets/nf-core-hello_logo_light.png + - docs/images/nf-core-hello_logo_light.png + - docs/images/nf-core-hello_logo_dark.png + - .github/ISSUE_TEMPLATE/bug_report.yml + - .github/CONTRIBUTING.md + - .github/PULL_REQUEST_TEMPLATE.md + - assets/email_template.txt + - docs/README.md + - .github/ISSUE_TEMPLATE/bug_report.yml + - .github/ISSUE_TEMPLATE/config.yml + - .github/ISSUE_TEMPLATE/feature_request.yml + - .github/PULL_REQUEST_TEMPLATE.md + - .github/workflows/branch.yml + - .github/workflows/linting_comment.yml + - .github/workflows/linting.yml + - .github/CONTRIBUTING.md + - .github/.dockstore.yml + - .github/CONTRIBUTING.md + - assets/sendmail_template.txt + - .prettierignore + - LICENSE + nextflow_config: + - manifest.name + - manifest.homePage + nf_test_content: false + multiqc_config: false + files_exist: + - .github/workflows/nf-test.yml + - .github/actions/get-shards/action.yml + - .github/actions/nf-test/action.yml + - nf-test.config + - tests/default.nf.test + - assets/email_template.html + - assets/sendmail_template.txt + - assets/email_template.txt + - CODE_OF_CONDUCT.md + - assets/nf-core-hello_logo_light.png + - docs/images/nf-core-hello_logo_light.png + - docs/images/nf-core-hello_logo_dark.png + - .github/ISSUE_TEMPLATE/config.yml + - .github/workflows/awstest.yml + - .github/workflows/awsfulltest.yml + - .github/ISSUE_TEMPLATE/bug_report.yml + - .github/ISSUE_TEMPLATE/feature_request.yml + - .github/PULL_REQUEST_TEMPLATE.md + - .github/CONTRIBUTING.md + - .github/.dockstore.yml + - CHANGELOG.md + - assets/multiqc_config.yml + - .github/workflows/branch.yml + - .github/workflows/nf-test.yml + - .github/actions/get-shards/action.yml + - .github/actions/nf-test/action.yml + - .github/workflows/linting_comment.yml + - .github/workflows/linting.yml + - .prettierignore + - .prettierrc.yml + - conf/igenomes.config + - conf/igenomes_ignored.config + - CITATIONS.md + - LICENSE + readme: + - nextflow_badge + - nextflow_badge + - nfcore_template_badge + +template: + org: core + name: hello + description: A basic nf-core style version of Hello Nextflow + author: pinin4fjords + version: 1.0.0dev + force: true + outdir: . + skip_features: + - github + - github_badges + - changelog + - license + - ci + - nf-test + - igenomes + - multiqc + - fastqc + - seqera_platform + - gpu + - codespaces + - vscode + - code_linters + - citations + - rocrate + - email + - adaptivecard + - slackreport + is_nfcore: false diff --git a/hello-nf-core/solutions/core-hello-part4/README.md b/hello-nf-core/solutions/core-hello-part4/README.md new file mode 100644 index 0000000000..94844f6c5e --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/README.md @@ -0,0 +1,75 @@ +# core/hello + +## Introduction + +**core/hello** is a bioinformatics pipeline that ... + + + + + + +## Usage + +> [!NOTE] +> If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/usage/installation) on how to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/usage/introduction#how-to-run-a-pipeline) with `-profile test` before running the workflow on actual data. + + + +Now, you can run the pipeline using: + + + +```bash +nextflow run core/hello \ + -profile \ + --input samplesheet.csv \ + --outdir +``` + +> [!WARNING] +> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; see [docs](https://nf-co.re/docs/usage/getting_started/configuration#custom-configuration-files). + +## Credits + +core/hello was originally written by pinin4fjords. + +We thank the following people for their extensive assistance in the development of this pipeline: + + + +## Contributions and Support + +If you would like to contribute to this pipeline, please see the [contributing guidelines](.github/CONTRIBUTING.md). + +## Citations + + + + +This pipeline uses code and infrastructure developed and maintained by the [nf-core](https://nf-co.re) community, reused here under the [MIT license](https://github.com/nf-core/tools/blob/main/LICENSE). + +> **The nf-core framework for community-curated bioinformatics pipelines.** +> +> Philip Ewels, Alexander Peltzer, Sven Fillinger, Harshil Patel, Johannes Alneberg, Andreas Wilm, Maxime Ulysse Garcia, Paolo Di Tommaso & Sven Nahnsen. +> +> _Nat Biotechnol._ 2020 Feb 13. doi: [10.1038/s41587-020-0439-x](https://dx.doi.org/10.1038/s41587-020-0439-x). diff --git a/hello-nf-core/solutions/core-hello-part4/assets/greetings.csv b/hello-nf-core/solutions/core-hello-part4/assets/greetings.csv new file mode 100644 index 0000000000..c5889e19a7 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/assets/greetings.csv @@ -0,0 +1,3 @@ +Hello +Bonjour +Holà diff --git a/hello-nf-core/solutions/core-hello-part4/assets/samplesheet.csv b/hello-nf-core/solutions/core-hello-part4/assets/samplesheet.csv new file mode 100644 index 0000000000..5f653ab7bf --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/assets/samplesheet.csv @@ -0,0 +1,3 @@ +sample,fastq_1,fastq_2 +SAMPLE_PAIRED_END,/path/to/fastq/files/AEG588A1_S1_L002_R1_001.fastq.gz,/path/to/fastq/files/AEG588A1_S1_L002_R2_001.fastq.gz +SAMPLE_SINGLE_END,/path/to/fastq/files/AEG588A4_S4_L003_R1_001.fastq.gz, diff --git a/hello-nf-core/solutions/core-hello-part4/assets/schema_input.json b/hello-nf-core/solutions/core-hello-part4/assets/schema_input.json new file mode 100644 index 0000000000..5cb7458161 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/assets/schema_input.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/core/hello/main/assets/schema_input.json", + "title": "core/hello pipeline - params.input schema", + "description": "Schema for the file provided with params.input", + "type": "array", + "items": { + "type": "object", + "properties": { + "sample": { + "type": "string", + "pattern": "^\\S+$", + "errorMessage": "Sample name must be provided and cannot contain spaces", + "meta": ["id"] + }, + "fastq_1": { + "type": "string", + "format": "file-path", + "exists": true, + "pattern": "^([\\S\\s]*\\/)?[^\\s\\/]+\\.f(ast)?q\\.gz$", + "errorMessage": "FastQ file for reads 1 must be provided, cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" + }, + "fastq_2": { + "type": "string", + "format": "file-path", + "exists": true, + "pattern": "^([\\S\\s]*\\/)?[^\\s\\/]+\\.f(ast)?q\\.gz$", + "errorMessage": "FastQ file for reads 2 cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" + } + }, + "required": ["sample", "fastq_1"] + } +} diff --git a/hello-nf-core/solutions/core-hello-part4/conf/base.config b/hello-nf-core/solutions/core-hello-part4/conf/base.config new file mode 100644 index 0000000000..e0fe40762f --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/conf/base.config @@ -0,0 +1,66 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + core/hello Nextflow base config file +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + A 'blank slate' config file, appropriate for general use on most high performance + compute environments. Assumes that all software is installed and available on + the PATH. Runs in `local` mode - all jobs will be run on the logged in environment. +---------------------------------------------------------------------------------------- +*/ + +process { + + // TODO nf-core: Check the defaults for all processes + cpus = { 1 * task.attempt } + memory = { 6.GB * task.attempt } + time = { 4.h * task.attempt } + + errorStrategy = { task.exitStatus in ((130..145) + 104 + 175) ? 'retry' : 'finish' } + maxRetries = 1 + maxErrors = '-1' + + // Process-specific resource requirements + // NOTE - Please try and reuse the labels below as much as possible. + // These labels are used and recognised by default in DSL2 files hosted on nf-core/modules. + // If possible, it would be nice to keep the same label naming convention when + // adding in your local modules too. + // TODO nf-core: Customise requirements for specific processes. + // See https://www.nextflow.io/docs/latest/config.html#config-process-selectors + withLabel:process_single { + cpus = { 1 } + memory = { 6.GB * task.attempt } + time = { 4.h * task.attempt } + } + withLabel:process_low { + cpus = { 2 * task.attempt } + memory = { 12.GB * task.attempt } + time = { 4.h * task.attempt } + } + withLabel:process_medium { + cpus = { 6 * task.attempt } + memory = { 36.GB * task.attempt } + time = { 8.h * task.attempt } + } + withLabel:process_high { + cpus = { 12 * task.attempt } + memory = { 72.GB * task.attempt } + time = { 16.h * task.attempt } + } + withLabel:process_long { + time = { 20.h * task.attempt } + } + withLabel:process_high_memory { + memory = { 200.GB * task.attempt } + } + withLabel:error_ignore { + errorStrategy = 'ignore' + } + withLabel:error_retry { + errorStrategy = 'retry' + maxRetries = 2 + } + withLabel: process_gpu { + ext.use_gpu = { workflow.profile.contains('gpu') } + accelerator = { workflow.profile.contains('gpu') ? 1 : null } + } +} diff --git a/hello-nf-core/solutions/core-hello-part4/conf/modules.config b/hello-nf-core/solutions/core-hello-part4/conf/modules.config new file mode 100644 index 0000000000..51b19b4a1e --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/conf/modules.config @@ -0,0 +1,26 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Config file for defining DSL2 per module options and publishing paths +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Available keys to override module options: + ext.args = Additional arguments appended to command in module. + ext.args2 = Second set of arguments appended to command in module (multi-tool modules). + ext.args3 = Third set of arguments appended to command in module (multi-tool modules). + ext.prefix = File name prefix for output files. +---------------------------------------------------------------------------------------- +*/ + +process { + + publishDir = [ + path: { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + + withName: 'cowpy' { + ext.args = { "-c ${params.character}" } + ext.prefix = { "cowpy-${meta.id}" } + } + +} diff --git a/hello-nf-core/solutions/core-hello-part4/conf/test.config b/hello-nf-core/solutions/core-hello-part4/conf/test.config new file mode 100644 index 0000000000..13ecf2ad4b --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/conf/test.config @@ -0,0 +1,31 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Nextflow config file for running minimal tests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Defines input files and everything required to run a fast and simple pipeline test. + + Use as follows: + nextflow run core/hello -profile test, --outdir + +---------------------------------------------------------------------------------------- +*/ + +process { + resourceLimits = [ + cpus: 2, + memory: '4.GB', + time: '1.h' + ] +} + +params { + config_profile_name = 'Test profile' + config_profile_description = 'Minimal test dataset to check pipeline function' + + // Input data + input = "${projectDir}/assets/greetings.csv" + + // Other parameters + batch = 'test' + character = 'tux' +} diff --git a/hello-nf-core/solutions/core-hello-part4/conf/test_full.config b/hello-nf-core/solutions/core-hello-part4/conf/test_full.config new file mode 100644 index 0000000000..ceeaf40cac --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/conf/test_full.config @@ -0,0 +1,24 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Nextflow config file for running full-size tests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Defines input files and everything required to run a full size pipeline test. + + Use as follows: + nextflow run core/hello -profile test_full, --outdir + +---------------------------------------------------------------------------------------- +*/ + +params { + config_profile_name = 'Full test profile' + config_profile_description = 'Full test dataset to check pipeline function' + + // Input data for full size test + // TODO nf-core: Specify the paths to your full test data ( on nf-core/test-datasets or directly in repositories, e.g. SRA) + // TODO nf-core: Give any required params for the test so that command line flags are not needed + input = params.pipelines_testdata_base_path + 'viralrecon/samplesheet/samplesheet_full_illumina_amplicon.csv' + + // Fasta references + fasta = params.pipelines_testdata_base_path + 'viralrecon/genome/NC_045512.2/GCF_009858895.2_ASM985889v3_genomic.200409.fna.gz' +} diff --git a/hello-nf-core/solutions/core-hello-part4/docs/README.md b/hello-nf-core/solutions/core-hello-part4/docs/README.md new file mode 100644 index 0000000000..593e4a39e8 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/docs/README.md @@ -0,0 +1,8 @@ +# core/hello: Documentation + +The core/hello documentation is split into the following pages: + +- [Usage](usage.md) + - An overview of how the pipeline works, how to run it and a description of all of the different command-line flags. +- [Output](output.md) + - An overview of the different results produced by the pipeline and how to interpret them. diff --git a/hello-nf-core/solutions/core-hello-part4/docs/output.md b/hello-nf-core/solutions/core-hello-part4/docs/output.md new file mode 100644 index 0000000000..7a49820c81 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/docs/output.md @@ -0,0 +1,29 @@ +# core/hello: Output + +## Introduction + +This document describes the output produced by the pipeline. + +The directories listed below will be created in the results directory after the pipeline has finished. All paths are relative to the top-level results directory. + + + +## Pipeline overview + +The pipeline is built using [Nextflow](https://www.nextflow.io/) and processes data using the following steps: + +- [Pipeline information](#pipeline-information) - Report metrics generated during the workflow execution + +### Pipeline information + +
+Output files + +- `pipeline_info/` + - Reports generated by Nextflow: `execution_report.html`, `execution_timeline.html`, `execution_trace.txt` and `pipeline_dag.dot`/`pipeline_dag.svg`. + - Reformatted samplesheet files used as input to the pipeline: `samplesheet.valid.csv`. + - Parameters used by the pipeline run: `params.json`. + +
+ +[Nextflow](https://www.nextflow.io/docs/latest/tracing.html) provides excellent functionality for generating various reports relevant to the running and execution of the pipeline. This will allow you to troubleshoot errors with the running of the pipeline, and also provide you with other information such as launch commands, run times and resource usage. diff --git a/hello-nf-core/solutions/core-hello-part4/docs/usage.md b/hello-nf-core/solutions/core-hello-part4/docs/usage.md new file mode 100644 index 0000000000..bfbc37ab42 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/docs/usage.md @@ -0,0 +1,211 @@ +# core/hello: Usage + +> _Documentation of pipeline parameters is generated automatically from the pipeline schema and can no longer be found in markdown files._ + +## Introduction + + + +## Samplesheet input + +You will need to create a samplesheet with information about the samples you would like to analyse before running the pipeline. Use this parameter to specify its location. It has to be a comma-separated file with 3 columns, and a header row as shown in the examples below. + +```bash +--input '[path to samplesheet file]' +``` + +### Multiple runs of the same sample + +The `sample` identifiers have to be the same when you have re-sequenced the same sample more than once e.g. to increase sequencing depth. The pipeline will concatenate the raw reads before performing any downstream analysis. Below is an example for the same sample sequenced across 3 lanes: + +```csv title="samplesheet.csv" +sample,fastq_1,fastq_2 +CONTROL_REP1,AEG588A1_S1_L002_R1_001.fastq.gz,AEG588A1_S1_L002_R2_001.fastq.gz +CONTROL_REP1,AEG588A1_S1_L003_R1_001.fastq.gz,AEG588A1_S1_L003_R2_001.fastq.gz +CONTROL_REP1,AEG588A1_S1_L004_R1_001.fastq.gz,AEG588A1_S1_L004_R2_001.fastq.gz +``` + +### Full samplesheet + +The pipeline will auto-detect whether a sample is single- or paired-end using the information provided in the samplesheet. The samplesheet can have as many columns as you desire, however, there is a strict requirement for the first 3 columns to match those defined in the table below. + +A final samplesheet file consisting of both single- and paired-end data may look something like the one below. This is for 6 samples, where `TREATMENT_REP3` has been sequenced twice. + +```csv title="samplesheet.csv" +sample,fastq_1,fastq_2 +CONTROL_REP1,AEG588A1_S1_L002_R1_001.fastq.gz,AEG588A1_S1_L002_R2_001.fastq.gz +CONTROL_REP2,AEG588A2_S2_L002_R1_001.fastq.gz,AEG588A2_S2_L002_R2_001.fastq.gz +CONTROL_REP3,AEG588A3_S3_L002_R1_001.fastq.gz,AEG588A3_S3_L002_R2_001.fastq.gz +TREATMENT_REP1,AEG588A4_S4_L003_R1_001.fastq.gz, +TREATMENT_REP2,AEG588A5_S5_L003_R1_001.fastq.gz, +TREATMENT_REP3,AEG588A6_S6_L003_R1_001.fastq.gz, +TREATMENT_REP3,AEG588A6_S6_L004_R1_001.fastq.gz, +``` + +| Column | Description | +| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `sample` | Custom sample name. This entry will be identical for multiple sequencing libraries/runs from the same sample. Spaces in sample names are automatically converted to underscores (`_`). | +| `fastq_1` | Full path to FastQ file for Illumina short reads 1. File has to be gzipped and have the extension ".fastq.gz" or ".fq.gz". | +| `fastq_2` | Full path to FastQ file for Illumina short reads 2. File has to be gzipped and have the extension ".fastq.gz" or ".fq.gz". | + +An [example samplesheet](../assets/samplesheet.csv) has been provided with the pipeline. + +## Running the pipeline + +The typical command for running the pipeline is as follows: + +```bash +nextflow run core/hello --input ./samplesheet.csv --outdir ./results -profile docker +``` + +This will launch the pipeline with the `docker` configuration profile. See below for more information about profiles. + +Note that the pipeline will create the following files in your working directory: + +```bash +work # Directory containing the nextflow working files + # Finished results in specified location (defined with --outdir) +.nextflow_log # Log file from Nextflow +# Other nextflow hidden files, eg. history of pipeline runs and old logs. +``` + +If you wish to repeatedly use the same parameters for multiple runs, rather than specifying each flag in the command, you can specify these in a params file. + +Pipeline settings can be provided in a `yaml` or `json` file via `-params-file `. + +> [!WARNING] +> Do not use `-c ` to specify parameters as this will result in errors. Custom config files specified with `-c` must only be used for [tuning process resource specifications](https://nf-co.re/docs/usage/configuration#tuning-workflow-resources), other infrastructural tweaks (such as output directories), or module arguments (args). + +The above pipeline run specified with a params file in yaml format: + +```bash +nextflow run core/hello -profile docker -params-file params.yaml +``` + +with: + +```yaml title="params.yaml" +input: './samplesheet.csv' +outdir: './results/' +<...> +``` + +You can also generate such `YAML`/`JSON` files via [nf-core/launch](https://nf-co.re/launch). + +### Updating the pipeline + +When you run the above command, Nextflow automatically pulls the pipeline code from GitHub and stores it as a cached version. When running the pipeline after this, it will always use the cached version if available - even if the pipeline has been updated since. To make sure that you're running the latest version of the pipeline, make sure that you regularly update the cached version of the pipeline: + +```bash +nextflow pull core/hello +``` + +### Reproducibility + +It is a good idea to specify the pipeline version when running the pipeline on your data. This ensures that a specific version of the pipeline code and software are used when you run your pipeline. If you keep using the same tag, you'll be running the same version of the pipeline, even if there have been changes to the code since. + +First, go to the [core/hello releases page](https://github.com/core/hello/releases) and find the latest pipeline version - numeric only (eg. `1.3.1`). Then specify this when running the pipeline with `-r` (one hyphen) - eg. `-r 1.3.1`. Of course, you can switch to another version by changing the number after the `-r` flag. + +This version number will be logged in reports when you run the pipeline, so that you'll know what you used when you look back in the future. + +To further assist in reproducibility, you can use share and reuse [parameter files](#running-the-pipeline) to repeat pipeline runs with the same settings without having to write out a command with every single parameter. + +> [!TIP] +> If you wish to share such profile (such as upload as supplementary material for academic publications), make sure to NOT include cluster specific paths to files, nor institutional specific profiles. + +## Core Nextflow arguments + +> [!NOTE] +> These options are part of Nextflow and use a _single_ hyphen (pipeline parameters use a double-hyphen) + +### `-profile` + +Use this parameter to choose a configuration profile. Profiles can give configuration presets for different compute environments. + +Several generic profiles are bundled with the pipeline which instruct the pipeline to use software packaged using different methods (Docker, Singularity, Podman, Shifter, Charliecloud, Apptainer, Conda) - see below. + +> [!IMPORTANT] +> We highly recommend the use of Docker or Singularity containers for full pipeline reproducibility, however when this is not possible, Conda is also supported. + +The pipeline also dynamically loads configurations from [https://github.com/nf-core/configs](https://github.com/nf-core/configs) when it runs, making multiple config profiles for various institutional clusters available at run time. For more information and to check if your system is supported, please see the [nf-core/configs documentation](https://github.com/nf-core/configs#documentation). + +Note that multiple profiles can be loaded, for example: `-profile test,docker` - the order of arguments is important! +They are loaded in sequence, so later profiles can overwrite earlier profiles. + +If `-profile` is not specified, the pipeline will run locally and expect all software to be installed and available on the `PATH`. This is _not_ recommended, since it can lead to different results on different machines dependent on the computer environment. + +- `test` + - A profile with a complete configuration for automated testing + - Includes links to test data so needs no other parameters +- `docker` + - A generic configuration profile to be used with [Docker](https://docker.com/) +- `singularity` + - A generic configuration profile to be used with [Singularity](https://sylabs.io/docs/) +- `podman` + - A generic configuration profile to be used with [Podman](https://podman.io/) +- `shifter` + - A generic configuration profile to be used with [Shifter](https://nersc.gitlab.io/development/shifter/how-to-use/) +- `charliecloud` + - A generic configuration profile to be used with [Charliecloud](https://charliecloud.io/) +- `apptainer` + - A generic configuration profile to be used with [Apptainer](https://apptainer.org/) +- `wave` + - A generic configuration profile to enable [Wave](https://seqera.io/wave/) containers. Use together with one of the above (requires Nextflow ` 24.03.0-edge` or later). +- `conda` + - A generic configuration profile to be used with [Conda](https://conda.io/docs/). Please only use Conda as a last resort i.e. when it's not possible to run the pipeline with Docker, Singularity, Podman, Shifter, Charliecloud, or Apptainer. + +### `-resume` + +Specify this when restarting a pipeline. Nextflow will use cached results from any pipeline steps where the inputs are the same, continuing from where it got to previously. For input to be considered the same, not only the names must be identical but the files' contents as well. For more info about this parameter, see [this blog post](https://www.nextflow.io/blog/2019/demystifying-nextflow-resume.html). + +You can also supply a run name to resume a specific run: `-resume [run-name]`. Use the `nextflow log` command to show previous run names. + +### `-c` + +Specify the path to a specific config file (this is a core Nextflow command). See the [nf-core website documentation](https://nf-co.re/usage/configuration) for more information. + +## Custom configuration + +### Resource requests + +Whilst the default requirements set within the pipeline will hopefully work for most people and with most input data, you may find that you want to customise the compute resources that the pipeline requests. Each step in the pipeline has a default set of requirements for number of CPUs, memory and time. For most of the pipeline steps, if the job exits with any of the error codes specified [here](https://github.com/nf-core/rnaseq/blob/4c27ef5610c87db00c3c5a3eed10b1d161abf575/conf/base.config#L18) it will automatically be resubmitted with higher resources request (2 x original, then 3 x original). If it still fails after the third attempt then the pipeline execution is stopped. + +To change the resource requests, please see the [max resources](https://nf-co.re/docs/usage/configuration#max-resources) and [tuning workflow resources](https://nf-co.re/docs/usage/configuration#tuning-workflow-resources) section of the nf-core website. + +### Custom Containers + +In some cases, you may wish to change the container or conda environment used by a pipeline steps for a particular tool. By default, nf-core pipelines use containers and software from the [biocontainers](https://biocontainers.pro/) or [bioconda](https://bioconda.github.io/) projects. However, in some cases the pipeline specified version maybe out of date. + +To use a different container from the default container or conda environment specified in a pipeline, please see the [updating tool versions](https://nf-co.re/docs/usage/configuration#updating-tool-versions) section of the nf-core website. + +### Custom Tool Arguments + +A pipeline might not always support every possible argument or option of a particular tool used in pipeline. Fortunately, nf-core pipelines provide some freedom to users to insert additional parameters that the pipeline does not include by default. + +To learn how to provide additional arguments to a particular tool of the pipeline, please see the [customising tool arguments](https://nf-co.re/docs/usage/configuration#customising-tool-arguments) section of the nf-core website. + +### nf-core/configs + +In most cases, you will only need to create a custom config as a one-off but if you and others within your organisation are likely to be running nf-core pipelines regularly and need to use the same settings regularly it may be a good idea to request that your custom config file is uploaded to the `nf-core/configs` git repository. Before you do this please can you test that the config file works with your pipeline of choice using the `-c` parameter. You can then create a pull request to the `nf-core/configs` repository with the addition of your config file, associated documentation file (see examples in [`nf-core/configs/docs`](https://github.com/nf-core/configs/tree/master/docs)), and amending [`nfcore_custom.config`](https://github.com/nf-core/configs/blob/master/nfcore_custom.config) to include your custom profile. + +See the main [Nextflow documentation](https://www.nextflow.io/docs/latest/config.html) for more information about creating your own configuration files. + +If you have any questions or issues please send us a message on [Slack](https://nf-co.re/join/slack) on the [`#configs` channel](https://nfcore.slack.com/channels/configs). + +## Running in the background + +Nextflow handles job submissions and supervises the running jobs. The Nextflow process must run until the pipeline is finished. + +The Nextflow `-bg` flag launches Nextflow in the background, detached from your terminal so that the workflow does not stop if you log out of your session. The logs are saved to a file. + +Alternatively, you can use `screen` / `tmux` or similar tool to create a detached session which you can log back into at a later time. +Some HPC setups also allow you to run nextflow within a cluster job submitted your job scheduler (from where it submits more jobs). + +## Nextflow memory requirements + +In some cases, the Nextflow Java virtual machines can start to request a large amount of memory. +We recommend adding the following line to your environment to limit this (typically in `~/.bashrc` or `~./bash_profile`): + +```bash +NXF_OPTS='-Xms1g -Xmx4g' +``` diff --git a/hello-nf-core/solutions/core-hello-part4/main.nf b/hello-nf-core/solutions/core-hello-part4/main.nf new file mode 100644 index 0000000000..eb8d91361f --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/main.nf @@ -0,0 +1,85 @@ +#!/usr/bin/env nextflow +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + core/hello +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Github : https://github.com/core/hello +---------------------------------------------------------------------------------------- +*/ + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + IMPORT FUNCTIONS / MODULES / SUBWORKFLOWS / WORKFLOWS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +include { HELLO } from './workflows/hello' +include { PIPELINE_INITIALISATION } from './subworkflows/local/utils_nfcore_hello_pipeline' +include { PIPELINE_COMPLETION } from './subworkflows/local/utils_nfcore_hello_pipeline' +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + NAMED WORKFLOWS FOR PIPELINE +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +// +// WORKFLOW: Run main analysis pipeline depending on type of input +// +workflow CORE_HELLO { + + take: + samplesheet // channel: samplesheet read in from --input + + main: + + // + // WORKFLOW: Run pipeline + // + HELLO ( + samplesheet + ) +} +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + RUN MAIN WORKFLOW +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow { + + main: + // + // SUBWORKFLOW: Run initialisation tasks + // + PIPELINE_INITIALISATION ( + params.version, + params.validate_params, + params.monochrome_logs, + args, + params.outdir, + params.input, + params.help, + params.help_full, + params.show_hidden + ) + + // + // WORKFLOW: Run main workflow + // + CORE_HELLO ( + PIPELINE_INITIALISATION.out.samplesheet + ) + // + // SUBWORKFLOW: Run completion tasks + // + PIPELINE_COMPLETION ( + params.outdir, + params.monochrome_logs, + ) +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + THE END +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ diff --git a/hello-nf-core/solutions/core-hello-part4/modules.json b/hello-nf-core/solutions/core-hello-part4/modules.json new file mode 100644 index 0000000000..3e65b38609 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/modules.json @@ -0,0 +1,36 @@ +{ + "name": "core/hello", + "homePage": "https://github.com/core/hello", + "repos": { + "https://github.com/nf-core/modules.git": { + "modules": { + "nf-core": { + "cat/cat": { + "branch": "master", + "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", + "installed_by": ["modules"] + } + } + }, + "subworkflows": { + "nf-core": { + "utils_nextflow_pipeline": { + "branch": "master", + "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "installed_by": ["subworkflows"] + }, + "utils_nfcore_pipeline": { + "branch": "master", + "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "installed_by": ["subworkflows"] + }, + "utils_nfschema_plugin": { + "branch": "master", + "git_sha": "4b406a74dc0449c0401ed87d5bfff4252fd277fd", + "installed_by": ["subworkflows"] + } + } + } + } + } +} diff --git a/hello-nf-core/solutions/core-hello-part4/modules/local/convertToUpper.nf b/hello-nf-core/solutions/core-hello-part4/modules/local/convertToUpper.nf new file mode 100644 index 0000000000..b2689e8e9c --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/modules/local/convertToUpper.nf @@ -0,0 +1,20 @@ +#!/usr/bin/env nextflow + +/* + * Use a text replacement tool to convert the greeting to uppercase + */ +process convertToUpper { + + publishDir 'results', mode: 'copy' + + input: + path input_file + + output: + path "UPPER-${input_file}" + + script: + """ + cat '$input_file' | tr '[a-z]' '[A-Z]' > 'UPPER-${input_file}' + """ +} diff --git a/hello-nf-core/solutions/core-hello-part4/modules/local/cowpy.nf b/hello-nf-core/solutions/core-hello-part4/modules/local/cowpy.nf new file mode 100644 index 0000000000..ec00520a66 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/modules/local/cowpy.nf @@ -0,0 +1,21 @@ +#!/usr/bin/env nextflow + +// Generate ASCII art with cowpy (https://github.com/jeffbuttars/cowpy) +process cowpy { + + container 'community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273' + conda 'conda-forge::cowpy==1.1.5' + + input: + tuple val(meta), path(input_file) + + output: + tuple val(meta), path("${prefix}.txt"), emit: cowpy_output + + script: + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + """ + cat $input_file | cowpy $args > ${prefix}.txt + """ +} diff --git a/hello-nf-core/solutions/core-hello-part4/modules/local/cowpy/environment.yml b/hello-nf-core/solutions/core-hello-part4/modules/local/cowpy/environment.yml new file mode 100644 index 0000000000..32bc330d8f --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/modules/local/cowpy/environment.yml @@ -0,0 +1,10 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + # TODO nf-core: List required Conda package(s). + # Software MUST be pinned to channel (i.e. "bioconda"), version (i.e. "1.10"). + # For Conda, the build (i.e. "h9402c20_2") must be EXCLUDED to support installation on different operating systems. + - "YOUR-TOOL-HERE" diff --git a/hello-nf-core/solutions/core-hello-part4/modules/local/cowpy/main.nf b/hello-nf-core/solutions/core-hello-part4/modules/local/cowpy/main.nf new file mode 100644 index 0000000000..212821d599 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/modules/local/cowpy/main.nf @@ -0,0 +1,47 @@ + + +process COWPY { + tag "$meta.id" + label 'process_single' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/YOUR-TOOL-HERE': + 'biocontainers/YOUR-TOOL-HERE' }" + + input:tuple val(meta), path(input) + + output: + tuple val(meta), path("*"), emit: output + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + cowpy: \$(cowpy --version) + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + echo $args + + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + cowpy: \$(cowpy --version) + END_VERSIONS + """ +} diff --git a/hello-nf-core/solutions/core-hello-part4/modules/local/cowpy/meta.yml b/hello-nf-core/solutions/core-hello-part4/modules/local/cowpy/meta.yml new file mode 100644 index 0000000000..616fdd9422 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/modules/local/cowpy/meta.yml @@ -0,0 +1,51 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "cowpy" +description: write your description here +keywords: + - sort + - example + - genomics +tools: + - "cowpy": + description: "" + homepage: "" + documentation: "" + tool_dev_url: "" + doi: "" + licence: null + identifier: null + +input: + - - meta: + type: map + description: Groovy Map containing sample information. e.g. `[ + id:'sample1' ]` + - input: + type: file + description: "" + pattern: "" + ontologies: + - edam: "" +output: + output: + - - meta: + type: map + description: Groovy Map containing sample information. e.g. `[ + id:'sample1' ]` + - "*": + type: file + description: "" + pattern: "" + ontologies: + - edam: "" + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: versions.yml + ontologies: + - edam: http://edamontology.org/format_3750 # YAML +authors: + - "@example" +maintainers: + - "@example" diff --git a/hello-nf-core/solutions/core-hello-part4/modules/local/cowpy/tests/main.nf.test b/hello-nf-core/solutions/core-hello-part4/modules/local/cowpy/tests/main.nf.test new file mode 100644 index 0000000000..88f75f7b6a --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/modules/local/cowpy/tests/main.nf.test @@ -0,0 +1,78 @@ +// TODO nf-core: Once you have added the required tests, please run the following command to build this file: +// nf-core modules test cowpy +nextflow_process { + + name "Test Process COWPY" + script "../main.nf" + process "COWPY" + + tag "modules" + tag "modules_" + tag "cowpy" + + // TODO nf-core: Change the test name preferably indicating the test-data and file-format used + test("sarscov2 - bam") { + + // TODO nf-core: If you are created a test for a chained module + // (the module requires running more than one process to generate the required output) + // add the 'setup' method here. + // You can find more information about how to use a 'setup' method in the docs (https://nf-co.re/docs/contributing/modules#steps-for-creating-nf-test-for-chained-modules). + + when { + process { + """ + // TODO nf-core: define inputs of the process here. Example: + + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot( + process.out, + path(process.out.versions[0]).yaml + ).match() } + //TODO nf-core: Add all required assertions to verify the test output. + // See https://nf-co.re/docs/contributing/tutorials/nf-test_assertions for more information and examples. + ) + } + + } + + // TODO nf-core: Change the test name preferably indicating the test-data and file-format used but keep the " - stub" suffix. + test("sarscov2 - bam - stub") { + + options "-stub" + + when { + process { + """ + // TODO nf-core: define inputs of the process here. Example: + + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot( + process.out, + path(process.out.versions[0]).yaml + ).match() } + ) + } + + } + +} diff --git a/hello-nf-core/solutions/core-hello-part4/modules/local/sayHello.nf b/hello-nf-core/solutions/core-hello-part4/modules/local/sayHello.nf new file mode 100644 index 0000000000..6005ad54c9 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/modules/local/sayHello.nf @@ -0,0 +1,20 @@ +#!/usr/bin/env nextflow + +/* + * Use echo to print 'Hello World!' to a file + */ +process sayHello { + + publishDir 'results', mode: 'copy' + + input: + val greeting + + output: + path "${greeting}-output.txt" + + script: + """ + echo '$greeting' > '$greeting-output.txt' + """ +} diff --git a/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/environment.yml b/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/environment.yml new file mode 100644 index 0000000000..50c2059afb --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/environment.yml @@ -0,0 +1,7 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - conda-forge::pigz=2.3.4 diff --git a/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/main.nf b/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/main.nf new file mode 100644 index 0000000000..2862c64cd9 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/main.nf @@ -0,0 +1,78 @@ +process CAT_CAT { + tag "$meta.id" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/pigz:2.3.4' : + 'biocontainers/pigz:2.3.4' }" + + input: + tuple val(meta), path(files_in) + + output: + tuple val(meta), path("${prefix}"), emit: file_out + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def args2 = task.ext.args2 ?: '' + def file_list = files_in.collect { it.toString() } + + // choose appropriate concatenation tool depending on input and output format + + // | input | output | command1 | command2 | + // |-----------|------------|----------|----------| + // | gzipped | gzipped | cat | | + // | ungzipped | ungzipped | cat | | + // | gzipped | ungzipped | zcat | | + // | ungzipped | gzipped | cat | pigz | + + // Use input file ending as default + prefix = task.ext.prefix ?: "${meta.id}${getFileSuffix(file_list[0])}" + out_zip = prefix.endsWith('.gz') + in_zip = file_list[0].endsWith('.gz') + command1 = (in_zip && !out_zip) ? 'zcat' : 'cat' + command2 = (!in_zip && out_zip) ? "| pigz -c -p $task.cpus $args2" : '' + if(file_list.contains(prefix.trim())) { + error "The name of the input file can't be the same as for the output prefix in the " + + "module CAT_CAT (currently `$prefix`). Please choose a different one." + } + """ + $command1 \\ + $args \\ + ${file_list.join(' ')} \\ + $command2 \\ + > ${prefix} + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + pigz: \$( pigz --version 2>&1 | sed 's/pigz //g' ) + END_VERSIONS + """ + + stub: + def file_list = files_in.collect { it.toString() } + prefix = task.ext.prefix ?: "${meta.id}${file_list[0].substring(file_list[0].lastIndexOf('.'))}" + if(file_list.contains(prefix.trim())) { + error "The name of the input file can't be the same as for the output prefix in the " + + "module CAT_CAT (currently `$prefix`). Please choose a different one." + } + """ + touch $prefix + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + pigz: \$( pigz --version 2>&1 | sed 's/pigz //g' ) + END_VERSIONS + """ +} + +// for .gz files also include the second to last extension if it is present. E.g., .fasta.gz +def getFileSuffix(filename) { + def match = filename =~ /^.*?((\.\w{1,5})?(\.\w{1,5}\.gz$))/ + return match ? match[0][1] : filename.substring(filename.lastIndexOf('.')) +} diff --git a/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/meta.yml b/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/meta.yml new file mode 100644 index 0000000000..2a9284d7f1 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/meta.yml @@ -0,0 +1,46 @@ +name: cat_cat +description: A module for concatenation of gzipped or uncompressed files +keywords: + - concatenate + - gzip + - cat +tools: + - cat: + description: Just concatenation + documentation: https://man7.org/linux/man-pages/man1/cat.1.html + licence: ["GPL-3.0-or-later"] + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - files_in: + type: file + description: List of compressed / uncompressed files + pattern: "*" + ontologies: [] +output: + file_out: + - - meta: + type: map + description: Groovy Map containing sample information + - ${prefix}: + type: file + description: Concatenated file. Will be gzipped if file_out ends with ".gz" + pattern: "${file_out}" + ontologies: [] + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML +authors: + - "@erikrikarddaniel" + - "@FriederikeHanssen" +maintainers: + - "@erikrikarddaniel" + - "@FriederikeHanssen" diff --git a/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/main.nf.test b/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/main.nf.test new file mode 100644 index 0000000000..9cb1617883 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/main.nf.test @@ -0,0 +1,191 @@ +nextflow_process { + + name "Test Process CAT_CAT" + script "../main.nf" + process "CAT_CAT" + tag "modules" + tag "modules_nfcore" + tag "cat" + tag "cat/cat" + + test("test_cat_name_conflict") { + when { + params { + outdir = "${outputDir}" + } + process { + """ + input[0] = + [ + [ id:'genome', single_end:true ], + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.sizes', checkIfExists: true) + ] + ] + """ + } + } + then { + assertAll( + { assert !process.success }, + { assert process.stdout.toString().contains("The name of the input file can't be the same as for the output prefix") }, + { assert snapshot(process.out.versions).match() } + ) + } + } + + test("test_cat_unzipped_unzipped") { + when { + params { + outdir = "${outputDir}" + } + process { + """ + input[0] = + [ + [ id:'test', single_end:true ], + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.sizes', checkIfExists: true) + ] + ] + """ + } + } + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + + test("test_cat_zipped_zipped") { + when { + params { + outdir = "${outputDir}" + } + process { + """ + input[0] = + [ + [ id:'test', single_end:true ], + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.gff3.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/alignment/last/contigs.genome.maf.gz', checkIfExists: true) + ] + ] + """ + } + } + then { + def lines = path(process.out.file_out.get(0).get(1)).linesGzip + assertAll( + { assert process.success }, + { assert snapshot( + lines[0..5], + lines.size(), + process.out.versions + ).match() + } + ) + } + } + + test("test_cat_zipped_unzipped") { + config './nextflow_zipped_unzipped.config' + + when { + params { + outdir = "${outputDir}" + } + process { + """ + input[0] = + [ + [ id:'test', single_end:true ], + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.gff3.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/alignment/last/contigs.genome.maf.gz', checkIfExists: true) + ] + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + + test("test_cat_unzipped_zipped") { + config './nextflow_unzipped_zipped.config' + when { + params { + outdir = "${outputDir}" + } + process { + """ + input[0] = + [ + [ id:'test', single_end:true ], + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.sizes', checkIfExists: true) + ] + ] + """ + } + } + then { + def lines = path(process.out.file_out.get(0).get(1)).linesGzip + assertAll( + { assert process.success }, + { assert snapshot( + lines[0..5], + lines.size(), + process.out.versions + ).match() + } + ) + } + } + + test("test_cat_one_file_unzipped_zipped") { + config './nextflow_unzipped_zipped.config' + when { + params { + outdir = "${outputDir}" + } + process { + """ + input[0] = + [ + [ id:'test', single_end:true ], + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + ] + """ + } + } + then { + def lines = path(process.out.file_out.get(0).get(1)).linesGzip + assertAll( + { assert process.success }, + { assert snapshot( + lines[0..5], + lines.size(), + process.out.versions + ).match() + } + ) + } + } +} diff --git a/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/main.nf.test.snap b/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/main.nf.test.snap new file mode 100644 index 0000000000..b7623ee650 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/main.nf.test.snap @@ -0,0 +1,147 @@ +{ + "test_cat_unzipped_unzipped": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fasta:md5,f44b33a0e441ad58b2d3700270e2dbe2" + ] + ], + "1": [ + "versions.yml:md5,115ed6177ebcff24eb99d503fa5ef894" + ], + "file_out": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fasta:md5,f44b33a0e441ad58b2d3700270e2dbe2" + ] + ], + "versions": [ + "versions.yml:md5,115ed6177ebcff24eb99d503fa5ef894" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2023-10-16T14:32:18.500464399" + }, + "test_cat_zipped_unzipped": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": true + }, + "cat.txt:md5,c439d3b60e7bc03e8802a451a0d9a5d9" + ] + ], + "1": [ + "versions.yml:md5,115ed6177ebcff24eb99d503fa5ef894" + ], + "file_out": [ + [ + { + "id": "test", + "single_end": true + }, + "cat.txt:md5,c439d3b60e7bc03e8802a451a0d9a5d9" + ] + ], + "versions": [ + "versions.yml:md5,115ed6177ebcff24eb99d503fa5ef894" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2023-10-16T14:32:49.642741302" + }, + "test_cat_zipped_zipped": { + "content": [ + [ + "MT192765.1\tGenbank\ttranscript\t259\t29667\t.\t+\t.\tID=unknown_transcript_1;geneID=orf1ab;gene_name=orf1ab", + "MT192765.1\tGenbank\tgene\t259\t21548\t.\t+\t.\tParent=unknown_transcript_1", + "MT192765.1\tGenbank\tCDS\t259\t13461\t.\t+\t0\tParent=unknown_transcript_1;exception=\"ribosomal slippage\";gbkey=CDS;gene=orf1ab;note=\"pp1ab;translated=by -1 ribosomal frameshift\";product=\"orf1ab polyprotein\";protein_id=QIK50426.1", + "MT192765.1\tGenbank\tCDS\t13461\t21548\t.\t+\t0\tParent=unknown_transcript_1;exception=\"ribosomal slippage\";gbkey=CDS;gene=orf1ab;note=\"pp1ab;translated=by -1 ribosomal frameshift\";product=\"orf1ab polyprotein\";protein_id=QIK50426.1", + "MT192765.1\tGenbank\tCDS\t21556\t25377\t.\t+\t0\tParent=unknown_transcript_1;gbkey=CDS;gene=S;note=\"structural protein\";product=\"surface glycoprotein\";protein_id=QIK50427.1", + "MT192765.1\tGenbank\tgene\t21556\t25377\t.\t+\t.\tParent=unknown_transcript_1" + ], + 78, + [ + "versions.yml:md5,115ed6177ebcff24eb99d503fa5ef894" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-22T11:51:46.802978" + }, + "test_cat_name_conflict": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-22T11:51:29.45394" + }, + "test_cat_one_file_unzipped_zipped": { + "content": [ + [ + ">MT192765.1 Severe acute respiratory syndrome coronavirus 2 isolate SARS-CoV-2/human/USA/PC00101P/2020, complete genome", + "GTTTATACCTTCCCAGGTAACAAACCAACCAACTTTCGATCTCTTGTAGATCTGTTCTCTAAACGAACTTTAAAATCTGT", + "GTGGCTGTCACTCGGCTGCATGCTTAGTGCACTCACGCAGTATAATTAATAACTAATTACTGTCGTTGACAGGACACGAG", + "TAACTCGTCTATCTTCTGCAGGCTGCTTACGGTTTCGTCCGTGTTGCAGCCGATCATCAGCACATCTAGGTTTTGTCCGG", + "GTGTGACCGAAAGGTAAGATGGAGAGCCTTGTCCCTGGTTTCAACGAGAAAACACACGTCCAACTCAGTTTGCCTGTTTT", + "ACAGGTTCGCGACGTGCTCGTACGTGGCTTTGGAGACTCCGTGGAGGAGGTCTTATCAGAGGCACGTCAACATCTTAAAG" + ], + 374, + [ + "versions.yml:md5,115ed6177ebcff24eb99d503fa5ef894" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-22T11:52:02.774016" + }, + "test_cat_unzipped_zipped": { + "content": [ + [ + ">MT192765.1 Severe acute respiratory syndrome coronavirus 2 isolate SARS-CoV-2/human/USA/PC00101P/2020, complete genome", + "GTTTATACCTTCCCAGGTAACAAACCAACCAACTTTCGATCTCTTGTAGATCTGTTCTCTAAACGAACTTTAAAATCTGT", + "GTGGCTGTCACTCGGCTGCATGCTTAGTGCACTCACGCAGTATAATTAATAACTAATTACTGTCGTTGACAGGACACGAG", + "TAACTCGTCTATCTTCTGCAGGCTGCTTACGGTTTCGTCCGTGTTGCAGCCGATCATCAGCACATCTAGGTTTTGTCCGG", + "GTGTGACCGAAAGGTAAGATGGAGAGCCTTGTCCCTGGTTTCAACGAGAAAACACACGTCCAACTCAGTTTGCCTGTTTT", + "ACAGGTTCGCGACGTGCTCGTACGTGGCTTTGGAGACTCCGTGGAGGAGGTCTTATCAGAGGCACGTCAACATCTTAAAG" + ], + 375, + [ + "versions.yml:md5,115ed6177ebcff24eb99d503fa5ef894" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-22T11:51:57.581523" + } +} \ No newline at end of file diff --git a/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/nextflow_unzipped_zipped.config b/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/nextflow_unzipped_zipped.config new file mode 100644 index 0000000000..ec26b0fdc6 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/nextflow_unzipped_zipped.config @@ -0,0 +1,6 @@ + +process { + withName: CAT_CAT { + ext.prefix = 'cat.txt.gz' + } +} diff --git a/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/nextflow_zipped_unzipped.config b/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/nextflow_zipped_unzipped.config new file mode 100644 index 0000000000..fbc79783d5 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/nextflow_zipped_unzipped.config @@ -0,0 +1,8 @@ + +process { + + withName: CAT_CAT { + ext.prefix = 'cat.txt' + } + +} diff --git a/hello-nf-core/solutions/core-hello-part4/nextflow.config b/hello-nf-core/solutions/core-hello-part4/nextflow.config new file mode 100644 index 0000000000..b59ba2175d --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/nextflow.config @@ -0,0 +1,252 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + core/hello Nextflow config file +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Default config options for all compute environments +---------------------------------------------------------------------------------------- +*/ + +// Global default params, used in configs +params { + + // TODO nf-core: Specify your pipeline's command line flags + // Input options + input = null + + // Boilerplate options + outdir = null + publish_dir_mode = 'copy' + monochrome_logs = false + help = false + help_full = false + show_hidden = false + version = false + pipelines_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/' + trace_report_suffix = new java.util.Date().format( 'yyyy-MM-dd_HH-mm-ss') + + // Config options + config_profile_name = null + config_profile_description = null + + custom_config_version = 'master' + custom_config_base = "https://raw.githubusercontent.com/nf-core/configs/${params.custom_config_version}" + config_profile_contact = null + config_profile_url = null + + // Schema validation default options + validate_params = true +} + +// Load base.config by default for all pipelines +includeConfig 'conf/base.config' + +profiles { + debug { + dumpHashes = true + process.beforeScript = 'echo $HOSTNAME' + cleanup = false + nextflow.enable.configProcessNamesValidation = true + } + conda { + conda.enabled = true + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + conda.channels = ['conda-forge', 'bioconda'] + apptainer.enabled = false + } + mamba { + conda.enabled = true + conda.useMamba = true + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + } + docker { + docker.enabled = true + conda.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + docker.runOptions = '-u $(id -u):$(id -g)' + } + arm64 { + process.arch = 'arm64' + // TODO https://github.com/nf-core/modules/issues/6694 + // For now if you're using arm64 you have to use wave for the sake of the maintainers + // wave profile + apptainer.ociAutoPull = true + singularity.ociAutoPull = true + wave.enabled = true + wave.freeze = true + wave.strategy = 'conda,container' + } + emulate_amd64 { + docker.runOptions = '-u $(id -u):$(id -g) --platform=linux/amd64' + } + singularity { + singularity.enabled = true + singularity.autoMounts = true + conda.enabled = false + docker.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + } + podman { + podman.enabled = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + } + shifter { + shifter.enabled = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + podman.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + } + charliecloud { + charliecloud.enabled = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + apptainer.enabled = false + } + apptainer { + apptainer.enabled = true + apptainer.autoMounts = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + } + wave { + apptainer.ociAutoPull = true + singularity.ociAutoPull = true + wave.enabled = true + wave.freeze = true + wave.strategy = 'conda,container' + } + gpu { + docker.runOptions = '-u $(id -u):$(id -g) --gpus all' + apptainer.runOptions = '--nv' + singularity.runOptions = '--nv' + } + test { includeConfig 'conf/test.config' } + test_full { includeConfig 'conf/test_full.config' } +} +// Load nf-core custom profiles from different institutions + +// If params.custom_config_base is set AND either the NXF_OFFLINE environment variable is not set or params.custom_config_base is a local path, the nfcore_custom.config file from the specified base path is included. +// Load core/hello custom profiles from different institutions. +includeConfig params.custom_config_base && (!System.getenv('NXF_OFFLINE') || !params.custom_config_base.startsWith('http')) ? "${params.custom_config_base}/nfcore_custom.config" : "/dev/null" + + +// Load core/hello custom profiles from different institutions. +// TODO nf-core: Optionally, you can add a pipeline-specific nf-core config at https://github.com/nf-core/configs +// includeConfig params.custom_config_base && (!System.getenv('NXF_OFFLINE') || !params.custom_config_base.startsWith('http')) ? "${params.custom_config_base}/pipeline/hello.config" : "/dev/null" + +// Set default registry for Apptainer, Docker, Podman, Charliecloud and Singularity independent of -profile +// Will not be used unless Apptainer / Docker / Podman / Charliecloud / Singularity are enabled +// Set to your registry if you have a mirror of containers +apptainer.registry = 'quay.io' +docker.registry = 'quay.io' +podman.registry = 'quay.io' +singularity.registry = 'quay.io' +charliecloud.registry = 'quay.io' + + + +// Export these variables to prevent local Python/R libraries from conflicting with those in the container +// The JULIA depot path has been adjusted to a fixed path `/usr/local/share/julia` that needs to be used for packages in the container. +// See https://apeltzer.github.io/post/03-julia-lang-nextflow/ for details on that. Once we have a common agreement on where to keep Julia packages, this is adjustable. + +env { + PYTHONNOUSERSITE = 1 + R_PROFILE_USER = "/.Rprofile" + R_ENVIRON_USER = "/.Renviron" + JULIA_DEPOT_PATH = "/usr/local/share/julia" +} + +// Set bash options +process.shell = [ + "bash", + "-C", // No clobber - prevent output redirection from overwriting files. + "-e", // Exit if a tool returns a non-zero status/exit code + "-u", // Treat unset variables and parameters as an error + "-o", // Returns the status of the last command to exit.. + "pipefail" // ..with a non-zero status or zero if all successfully execute +] + +// Disable process selector warnings by default. Use debug profile to enable warnings. +nextflow.enable.configProcessNamesValidation = false + +timeline { + enabled = true + file = "${params.outdir}/pipeline_info/execution_timeline_${params.trace_report_suffix}.html" +} +report { + enabled = true + file = "${params.outdir}/pipeline_info/execution_report_${params.trace_report_suffix}.html" +} +trace { + enabled = true + file = "${params.outdir}/pipeline_info/execution_trace_${params.trace_report_suffix}.txt" +} +dag { + enabled = true + file = "${params.outdir}/pipeline_info/pipeline_dag_${params.trace_report_suffix}.html" +} + +manifest { + name = 'core/hello' + contributors = [ + // TODO nf-core: Update the field with the details of the contributors to your pipeline. New with Nextflow version 24.10.0 + [ + name: 'pinin4fjords', + affiliation: '', + email: '', + github: '', + contribution: [], // List of contribution types ('author', 'maintainer' or 'contributor') + orcid: '' + ], + ] + homePage = 'https://github.com/core/hello' + description = """A basic nf-core style version of Hello Nextflow""" + mainScript = 'main.nf' + defaultBranch = 'main' + nextflowVersion = '!>=25.04.0' + version = '1.0.0dev' + doi = '' +} + +// Nextflow plugins +plugins { + id 'nf-schema@2.5.1' // Validation of pipeline parameters and creation of an input channel from a sample sheet +} + +validation { + defaultIgnoreParams = ["genomes"] + monochromeLogs = params.monochrome_logs +} + +// Load modules.config for DSL2 module specific options +includeConfig 'conf/modules.config' diff --git a/hello-nf-core/solutions/core-hello-part4/nextflow_schema.json b/hello-nf-core/solutions/core-hello-part4/nextflow_schema.json new file mode 100644 index 0000000000..fc18ba7998 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/nextflow_schema.json @@ -0,0 +1,163 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/core/hello/main/nextflow_schema.json", + "title": "core/hello pipeline parameters", + "description": "A basic nf-core style version of Hello Nextflow", + "type": "object", + "$defs": { + "input_output_options": { + "title": "Input/output options", + "type": "object", + "fa_icon": "fas fa-terminal", + "description": "Define where the pipeline should find input data and save output data.", + "required": ["input", "outdir"], + "properties": { + "input": { + "type": "string", + "format": "file-path", + "exists": true, + "schema": "assets/schema_input.json", + "mimetype": "text/csv", + "pattern": "^\\S+\\.csv$", + "description": "Path to comma-separated file containing information about the samples in the experiment.", + "help_text": "You will need to create a design file with information about the samples in your experiment before running the pipeline. Use this parameter to specify its location. It has to be a comma-separated file with 3 columns, and a header row.", + "fa_icon": "fas fa-file-csv" + }, + "outdir": { + "type": "string", + "format": "directory-path", + "description": "The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure.", + "fa_icon": "fas fa-folder-open" + } + } + }, + "institutional_config_options": { + "title": "Institutional config options", + "type": "object", + "fa_icon": "fas fa-university", + "description": "Parameters used to describe centralised config profiles. These should not be edited.", + "help_text": "The centralised nf-core configuration profiles use a handful of pipeline parameters to describe themselves. This information is then printed to the Nextflow log when you run a pipeline. You should not need to change these values when you run a pipeline.", + "properties": { + "custom_config_version": { + "type": "string", + "description": "Git commit id for Institutional configs.", + "default": "master", + "hidden": true, + "fa_icon": "fas fa-users-cog" + }, + "custom_config_base": { + "type": "string", + "description": "Base directory for Institutional configs.", + "default": "https://raw.githubusercontent.com/nf-core/configs/master", + "hidden": true, + "help_text": "If you're running offline, Nextflow will not be able to fetch the institutional config files from the internet. If you don't need them, then this is not a problem. If you do need them, you should download the files from the repo and tell Nextflow where to find them with this parameter.", + "fa_icon": "fas fa-users-cog" + }, + "config_profile_name": { + "type": "string", + "description": "Institutional config name.", + "hidden": true, + "fa_icon": "fas fa-users-cog" + }, + "config_profile_description": { + "type": "string", + "description": "Institutional config description.", + "hidden": true, + "fa_icon": "fas fa-users-cog" + }, + "config_profile_contact": { + "type": "string", + "description": "Institutional config contact information.", + "hidden": true, + "fa_icon": "fas fa-users-cog" + }, + "config_profile_url": { + "type": "string", + "description": "Institutional config URL link.", + "hidden": true, + "fa_icon": "fas fa-users-cog" + } + } + }, + "generic_options": { + "title": "Generic options", + "type": "object", + "fa_icon": "fas fa-file-import", + "description": "Less common options for the pipeline, typically set in a config file.", + "help_text": "These options are common to all nf-core pipelines and allow you to customise some of the core preferences for how the pipeline runs.\n\nTypically these options would be set in a Nextflow config file loaded for all pipeline runs, such as `~/.nextflow/config`.", + "properties": { + "version": { + "type": "boolean", + "description": "Display version and exit.", + "fa_icon": "fas fa-question-circle", + "hidden": true + }, + "publish_dir_mode": { + "type": "string", + "default": "copy", + "description": "Method used to save pipeline results to output directory.", + "help_text": "The Nextflow `publishDir` option specifies which intermediate files should be saved to the output directory. This option tells the pipeline what method should be used to move these files. See [Nextflow docs](https://www.nextflow.io/docs/latest/process.html#publishdir) for details.", + "fa_icon": "fas fa-copy", + "enum": [ + "symlink", + "rellink", + "link", + "copy", + "copyNoFollow", + "move" + ], + "hidden": true + }, + "monochrome_logs": { + "type": "boolean", + "description": "Do not use coloured log outputs.", + "fa_icon": "fas fa-palette", + "hidden": true + }, + "validate_params": { + "type": "boolean", + "description": "Boolean whether to validate parameters against the schema at runtime", + "default": true, + "fa_icon": "fas fa-check-square", + "hidden": true + }, + "pipelines_testdata_base_path": { + "type": "string", + "fa_icon": "far fa-check-circle", + "description": "Base URL or local path to location of pipeline test dataset files", + "default": "https://raw.githubusercontent.com/nf-core/test-datasets/", + "hidden": true + }, + "trace_report_suffix": { + "type": "string", + "fa_icon": "far calendar", + "description": "Suffix to add to the trace report filename. Default is the date and time in the format yyyy-MM-dd_HH-mm-ss.", + "hidden": true + }, + "help": { + "type": ["boolean", "string"], + "description": "Display the help message." + }, + "help_full": { + "type": "boolean", + "description": "Display the full detailed help message." + }, + "show_hidden": { + "type": "boolean", + "description": "Display hidden parameters in the help message (only works when --help or --help_full are provided)." + } + } + } + }, + "allOf": [ + { + "$ref": "#/$defs/input_output_options" + }, + { + "$ref": "#/$defs/institutional_config_options" + }, + { + "$ref": "#/$defs/generic_options" + } + ] +} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/local/utils_nfcore_hello_pipeline/main.nf b/hello-nf-core/solutions/core-hello-part4/subworkflows/local/utils_nfcore_hello_pipeline/main.nf new file mode 100644 index 0000000000..93c9f874cc --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/subworkflows/local/utils_nfcore_hello_pipeline/main.nf @@ -0,0 +1,136 @@ +// +// Subworkflow with functionality specific to the core/hello pipeline +// + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + IMPORT FUNCTIONS / MODULES / SUBWORKFLOWS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +include { UTILS_NFSCHEMA_PLUGIN } from '../../nf-core/utils_nfschema_plugin' +include { paramsSummaryMap } from 'plugin/nf-schema' +include { samplesheetToList } from 'plugin/nf-schema' +include { paramsHelp } from 'plugin/nf-schema' +include { completionSummary } from '../../nf-core/utils_nfcore_pipeline' +include { UTILS_NFCORE_PIPELINE } from '../../nf-core/utils_nfcore_pipeline' +include { UTILS_NEXTFLOW_PIPELINE } from '../../nf-core/utils_nextflow_pipeline' + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SUBWORKFLOW TO INITIALISE PIPELINE +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow PIPELINE_INITIALISATION { + + take: + version // boolean: Display version and exit + validate_params // boolean: Boolean whether to validate parameters against the schema at runtime + monochrome_logs // boolean: Do not use coloured log outputs + nextflow_cli_args // array: List of positional nextflow CLI args + outdir // string: The output directory where the results will be saved + input // string: Path to input samplesheet + help // boolean: Display help message and exit + help_full // boolean: Show the full help message + show_hidden // boolean: Show hidden parameters in the help message + + main: + + ch_versions = Channel.empty() + + // + // Print version and exit if required and dump pipeline parameters to JSON file + // + UTILS_NEXTFLOW_PIPELINE ( + version, + true, + outdir, + workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1 + ) + + // + // Validate parameters and generate parameter summary to stdout + // + command = "nextflow run ${workflow.manifest.name} -profile --input samplesheet.csv --outdir " + + UTILS_NFSCHEMA_PLUGIN ( + workflow, + validate_params, + null, + help, + help_full, + show_hidden, + "", + "", + command + ) + + // + // Check config provided to the pipeline + // + UTILS_NFCORE_PIPELINE ( + nextflow_cli_args + ) + + // + // Create channel from input file provided through params.input + // + + ch_samplesheet = channel.fromPath(params.input) + .splitCsv() + .map { line -> line[0] } + + emit: + samplesheet = ch_samplesheet + versions = ch_versions +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SUBWORKFLOW FOR PIPELINE COMPLETION +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow PIPELINE_COMPLETION { + + take: + outdir // path: Path to output directory where results will be published + monochrome_logs // boolean: Disable ANSI colour codes in log output + + main: + summary_params = paramsSummaryMap(workflow, parameters_schema: "nextflow_schema.json") + + // + // Completion email and summary + // + workflow.onComplete { + + completionSummary(monochrome_logs) + } + + workflow.onError { + log.error "Pipeline failed. Please refer to troubleshooting docs: https://nf-co.re/docs/usage/troubleshooting" + } +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + FUNCTIONS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +// +// Validate channels from input samplesheet +// +def validateInputSamplesheet(input) { + def (metas, fastqs) = input[1..2] + + // Check that multiple runs of the same sample are of the same datatype i.e. single-end / paired-end + def endedness_ok = metas.collect{ meta -> meta.single_end }.unique().size == 1 + if (!endedness_ok) { + error("Please check input samplesheet -> Multiple runs of a sample must be of the same datatype i.e. single-end or paired-end: ${metas[0].id}") + } + + return [ metas[0], fastqs ] +} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/main.nf b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/main.nf new file mode 100644 index 0000000000..d6e593e852 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/main.nf @@ -0,0 +1,126 @@ +// +// Subworkflow with functionality that may be useful for any Nextflow pipeline +// + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SUBWORKFLOW DEFINITION +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow UTILS_NEXTFLOW_PIPELINE { + take: + print_version // boolean: print version + dump_parameters // boolean: dump parameters + outdir // path: base directory used to publish pipeline results + check_conda_channels // boolean: check conda channels + + main: + + // + // Print workflow version and exit on --version + // + if (print_version) { + log.info("${workflow.manifest.name} ${getWorkflowVersion()}") + System.exit(0) + } + + // + // Dump pipeline parameters to a JSON file + // + if (dump_parameters && outdir) { + dumpParametersToJSON(outdir) + } + + // + // When running with Conda, warn if channels have not been set-up appropriately + // + if (check_conda_channels) { + checkCondaChannels() + } + + emit: + dummy_emit = true +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + FUNCTIONS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +// +// Generate version string +// +def getWorkflowVersion() { + def version_string = "" as String + if (workflow.manifest.version) { + def prefix_v = workflow.manifest.version[0] != 'v' ? 'v' : '' + version_string += "${prefix_v}${workflow.manifest.version}" + } + + if (workflow.commitId) { + def git_shortsha = workflow.commitId.substring(0, 7) + version_string += "-g${git_shortsha}" + } + + return version_string +} + +// +// Dump pipeline parameters to a JSON file +// +def dumpParametersToJSON(outdir) { + def timestamp = new java.util.Date().format('yyyy-MM-dd_HH-mm-ss') + def filename = "params_${timestamp}.json" + def temp_pf = new File(workflow.launchDir.toString(), ".${filename}") + def jsonStr = groovy.json.JsonOutput.toJson(params) + temp_pf.text = groovy.json.JsonOutput.prettyPrint(jsonStr) + + nextflow.extension.FilesEx.copyTo(temp_pf.toPath(), "${outdir}/pipeline_info/params_${timestamp}.json") + temp_pf.delete() +} + +// +// When running with -profile conda, warn if channels have not been set-up appropriately +// +def checkCondaChannels() { + def parser = new org.yaml.snakeyaml.Yaml() + def channels = [] + try { + def config = parser.load("conda config --show channels".execute().text) + channels = config.channels + } + catch (NullPointerException e) { + log.debug(e) + log.warn("Could not verify conda channel configuration.") + return null + } + catch (IOException e) { + log.debug(e) + log.warn("Could not verify conda channel configuration.") + return null + } + + // Check that all channels are present + // This channel list is ordered by required channel priority. + def required_channels_in_order = ['conda-forge', 'bioconda'] + def channels_missing = ((required_channels_in_order as Set) - (channels as Set)) as Boolean + + // Check that they are in the right order + def channel_priority_violation = required_channels_in_order != channels.findAll { ch -> ch in required_channels_in_order } + + if (channels_missing | channel_priority_violation) { + log.warn """\ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + There is a problem with your Conda configuration! + You will need to set-up the conda-forge and bioconda channels correctly. + Please refer to https://bioconda.github.io/ + The observed channel order is + ${channels} + but the following channel order is required: + ${required_channels_in_order} + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + """.stripIndent(true) + } +} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/meta.yml b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/meta.yml new file mode 100644 index 0000000000..e5c3a0a828 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/meta.yml @@ -0,0 +1,38 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "UTILS_NEXTFLOW_PIPELINE" +description: Subworkflow with functionality that may be useful for any Nextflow pipeline +keywords: + - utility + - pipeline + - initialise + - version +components: [] +input: + - print_version: + type: boolean + description: | + Print the version of the pipeline and exit + - dump_parameters: + type: boolean + description: | + Dump the parameters of the pipeline to a JSON file + - output_directory: + type: directory + description: Path to output dir to write JSON file to. + pattern: "results/" + - check_conda_channel: + type: boolean + description: | + Check if the conda channel priority is correct. +output: + - dummy_emit: + type: boolean + description: | + Dummy emit to make nf-core subworkflows lint happy +authors: + - "@adamrtalbot" + - "@drpatelh" +maintainers: + - "@adamrtalbot" + - "@drpatelh" + - "@maxulysse" diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test new file mode 100644 index 0000000000..68718e4f59 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test @@ -0,0 +1,54 @@ + +nextflow_function { + + name "Test Functions" + script "subworkflows/nf-core/utils_nextflow_pipeline/main.nf" + config "subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config" + tag 'subworkflows' + tag 'utils_nextflow_pipeline' + tag 'subworkflows/utils_nextflow_pipeline' + + test("Test Function getWorkflowVersion") { + + function "getWorkflowVersion" + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function dumpParametersToJSON") { + + function "dumpParametersToJSON" + + when { + function { + """ + // define inputs of the function here. Example: + input[0] = "$outputDir" + """.stripIndent() + } + } + + then { + assertAll( + { assert function.success } + ) + } + } + + test("Test Function checkCondaChannels") { + + function "checkCondaChannels" + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } +} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap new file mode 100644 index 0000000000..e3f0baf473 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap @@ -0,0 +1,20 @@ +{ + "Test Function getWorkflowVersion": { + "content": [ + "v9.9.9" + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:02:05.308243" + }, + "Test Function checkCondaChannels": { + "content": null, + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:02:12.425833" + } +} \ No newline at end of file diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test new file mode 100644 index 0000000000..02dbf094cd --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test @@ -0,0 +1,113 @@ +nextflow_workflow { + + name "Test Workflow UTILS_NEXTFLOW_PIPELINE" + script "../main.nf" + config "subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config" + workflow "UTILS_NEXTFLOW_PIPELINE" + tag 'subworkflows' + tag 'utils_nextflow_pipeline' + tag 'subworkflows/utils_nextflow_pipeline' + + test("Should run no inputs") { + + when { + workflow { + """ + print_version = false + dump_parameters = false + outdir = null + check_conda_channels = false + + input[0] = print_version + input[1] = dump_parameters + input[2] = outdir + input[3] = check_conda_channels + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } + + test("Should print version") { + + when { + workflow { + """ + print_version = true + dump_parameters = false + outdir = null + check_conda_channels = false + + input[0] = print_version + input[1] = dump_parameters + input[2] = outdir + input[3] = check_conda_channels + """ + } + } + + then { + expect { + with(workflow) { + assert success + assert "nextflow_workflow v9.9.9" in stdout + } + } + } + } + + test("Should dump params") { + + when { + workflow { + """ + print_version = false + dump_parameters = true + outdir = 'results' + check_conda_channels = false + + input[0] = false + input[1] = true + input[2] = outdir + input[3] = false + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } + + test("Should not create params JSON if no output directory") { + + when { + workflow { + """ + print_version = false + dump_parameters = true + outdir = null + check_conda_channels = false + + input[0] = false + input[1] = true + input[2] = outdir + input[3] = false + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } +} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config new file mode 100644 index 0000000000..a09572e5bb --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config @@ -0,0 +1,9 @@ +manifest { + name = 'nextflow_workflow' + author = """nf-core""" + homePage = 'https://127.0.0.1' + description = """Dummy pipeline""" + nextflowVersion = '!>=23.04.0' + version = '9.9.9' + doi = 'https://doi.org/10.5281/zenodo.5070524' +} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/main.nf b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/main.nf new file mode 100644 index 0000000000..bfd258760d --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/main.nf @@ -0,0 +1,419 @@ +// +// Subworkflow with utility functions specific to the nf-core pipeline template +// + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SUBWORKFLOW DEFINITION +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow UTILS_NFCORE_PIPELINE { + take: + nextflow_cli_args + + main: + valid_config = checkConfigProvided() + checkProfileProvided(nextflow_cli_args) + + emit: + valid_config +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + FUNCTIONS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +// +// Warn if a -profile or Nextflow config has not been provided to run the pipeline +// +def checkConfigProvided() { + def valid_config = true as Boolean + if (workflow.profile == 'standard' && workflow.configFiles.size() <= 1) { + log.warn( + "[${workflow.manifest.name}] You are attempting to run the pipeline without any custom configuration!\n\n" + "This will be dependent on your local compute environment but can be achieved via one or more of the following:\n" + " (1) Using an existing pipeline profile e.g. `-profile docker` or `-profile singularity`\n" + " (2) Using an existing nf-core/configs for your Institution e.g. `-profile crick` or `-profile uppmax`\n" + " (3) Using your own local custom config e.g. `-c /path/to/your/custom.config`\n\n" + "Please refer to the quick start section and usage docs for the pipeline.\n " + ) + valid_config = false + } + return valid_config +} + +// +// Exit pipeline if --profile contains spaces +// +def checkProfileProvided(nextflow_cli_args) { + if (workflow.profile.endsWith(',')) { + error( + "The `-profile` option cannot end with a trailing comma, please remove it and re-run the pipeline!\n" + "HINT: A common mistake is to provide multiple values separated by spaces e.g. `-profile test, docker`.\n" + ) + } + if (nextflow_cli_args[0]) { + log.warn( + "nf-core pipelines do not accept positional arguments. The positional argument `${nextflow_cli_args[0]}` has been detected.\n" + "HINT: A common mistake is to provide multiple values separated by spaces e.g. `-profile test, docker`.\n" + ) + } +} + +// +// Generate workflow version string +// +def getWorkflowVersion() { + def version_string = "" as String + if (workflow.manifest.version) { + def prefix_v = workflow.manifest.version[0] != 'v' ? 'v' : '' + version_string += "${prefix_v}${workflow.manifest.version}" + } + + if (workflow.commitId) { + def git_shortsha = workflow.commitId.substring(0, 7) + version_string += "-g${git_shortsha}" + } + + return version_string +} + +// +// Get software versions for pipeline +// +def processVersionsFromYAML(yaml_file) { + def yaml = new org.yaml.snakeyaml.Yaml() + def versions = yaml.load(yaml_file).collectEntries { k, v -> [k.tokenize(':')[-1], v] } + return yaml.dumpAsMap(versions).trim() +} + +// +// Get workflow version for pipeline +// +def workflowVersionToYAML() { + return """ + Workflow: + ${workflow.manifest.name}: ${getWorkflowVersion()} + Nextflow: ${workflow.nextflow.version} + """.stripIndent().trim() +} + +// +// Get channel of software versions used in pipeline in YAML format +// +def softwareVersionsToYAML(ch_versions) { + return ch_versions.unique().map { version -> processVersionsFromYAML(version) }.unique().mix(Channel.of(workflowVersionToYAML())) +} + +// +// Get workflow summary for MultiQC +// +def paramsSummaryMultiqc(summary_params) { + def summary_section = '' + summary_params + .keySet() + .each { group -> + def group_params = summary_params.get(group) + // This gets the parameters of that particular group + if (group_params) { + summary_section += "

${group}

\n" + summary_section += "
\n" + group_params + .keySet() + .sort() + .each { param -> + summary_section += "
${param}
${group_params.get(param) ?: 'N/A'}
\n" + } + summary_section += "
\n" + } + } + + def yaml_file_text = "id: '${workflow.manifest.name.replace('/', '-')}-summary'\n" as String + yaml_file_text += "description: ' - this information is collected when the pipeline is started.'\n" + yaml_file_text += "section_name: '${workflow.manifest.name} Workflow Summary'\n" + yaml_file_text += "section_href: 'https://github.com/${workflow.manifest.name}'\n" + yaml_file_text += "plot_type: 'html'\n" + yaml_file_text += "data: |\n" + yaml_file_text += "${summary_section}" + + return yaml_file_text +} + +// +// ANSII colours used for terminal logging +// +def logColours(monochrome_logs=true) { + def colorcodes = [:] as Map + + // Reset / Meta + colorcodes['reset'] = monochrome_logs ? '' : "\033[0m" + colorcodes['bold'] = monochrome_logs ? '' : "\033[1m" + colorcodes['dim'] = monochrome_logs ? '' : "\033[2m" + colorcodes['underlined'] = monochrome_logs ? '' : "\033[4m" + colorcodes['blink'] = monochrome_logs ? '' : "\033[5m" + colorcodes['reverse'] = monochrome_logs ? '' : "\033[7m" + colorcodes['hidden'] = monochrome_logs ? '' : "\033[8m" + + // Regular Colors + colorcodes['black'] = monochrome_logs ? '' : "\033[0;30m" + colorcodes['red'] = monochrome_logs ? '' : "\033[0;31m" + colorcodes['green'] = monochrome_logs ? '' : "\033[0;32m" + colorcodes['yellow'] = monochrome_logs ? '' : "\033[0;33m" + colorcodes['blue'] = monochrome_logs ? '' : "\033[0;34m" + colorcodes['purple'] = monochrome_logs ? '' : "\033[0;35m" + colorcodes['cyan'] = monochrome_logs ? '' : "\033[0;36m" + colorcodes['white'] = monochrome_logs ? '' : "\033[0;37m" + + // Bold + colorcodes['bblack'] = monochrome_logs ? '' : "\033[1;30m" + colorcodes['bred'] = monochrome_logs ? '' : "\033[1;31m" + colorcodes['bgreen'] = monochrome_logs ? '' : "\033[1;32m" + colorcodes['byellow'] = monochrome_logs ? '' : "\033[1;33m" + colorcodes['bblue'] = monochrome_logs ? '' : "\033[1;34m" + colorcodes['bpurple'] = monochrome_logs ? '' : "\033[1;35m" + colorcodes['bcyan'] = monochrome_logs ? '' : "\033[1;36m" + colorcodes['bwhite'] = monochrome_logs ? '' : "\033[1;37m" + + // Underline + colorcodes['ublack'] = monochrome_logs ? '' : "\033[4;30m" + colorcodes['ured'] = monochrome_logs ? '' : "\033[4;31m" + colorcodes['ugreen'] = monochrome_logs ? '' : "\033[4;32m" + colorcodes['uyellow'] = monochrome_logs ? '' : "\033[4;33m" + colorcodes['ublue'] = monochrome_logs ? '' : "\033[4;34m" + colorcodes['upurple'] = monochrome_logs ? '' : "\033[4;35m" + colorcodes['ucyan'] = monochrome_logs ? '' : "\033[4;36m" + colorcodes['uwhite'] = monochrome_logs ? '' : "\033[4;37m" + + // High Intensity + colorcodes['iblack'] = monochrome_logs ? '' : "\033[0;90m" + colorcodes['ired'] = monochrome_logs ? '' : "\033[0;91m" + colorcodes['igreen'] = monochrome_logs ? '' : "\033[0;92m" + colorcodes['iyellow'] = monochrome_logs ? '' : "\033[0;93m" + colorcodes['iblue'] = monochrome_logs ? '' : "\033[0;94m" + colorcodes['ipurple'] = monochrome_logs ? '' : "\033[0;95m" + colorcodes['icyan'] = monochrome_logs ? '' : "\033[0;96m" + colorcodes['iwhite'] = monochrome_logs ? '' : "\033[0;97m" + + // Bold High Intensity + colorcodes['biblack'] = monochrome_logs ? '' : "\033[1;90m" + colorcodes['bired'] = monochrome_logs ? '' : "\033[1;91m" + colorcodes['bigreen'] = monochrome_logs ? '' : "\033[1;92m" + colorcodes['biyellow'] = monochrome_logs ? '' : "\033[1;93m" + colorcodes['biblue'] = monochrome_logs ? '' : "\033[1;94m" + colorcodes['bipurple'] = monochrome_logs ? '' : "\033[1;95m" + colorcodes['bicyan'] = monochrome_logs ? '' : "\033[1;96m" + colorcodes['biwhite'] = monochrome_logs ? '' : "\033[1;97m" + + return colorcodes +} + +// Return a single report from an object that may be a Path or List +// +def getSingleReport(multiqc_reports) { + if (multiqc_reports instanceof Path) { + return multiqc_reports + } else if (multiqc_reports instanceof List) { + if (multiqc_reports.size() == 0) { + log.warn("[${workflow.manifest.name}] No reports found from process 'MULTIQC'") + return null + } else if (multiqc_reports.size() == 1) { + return multiqc_reports.first() + } else { + log.warn("[${workflow.manifest.name}] Found multiple reports from process 'MULTIQC', will use only one") + return multiqc_reports.first() + } + } else { + return null + } +} + +// +// Construct and send completion email +// +def completionEmail(summary_params, email, email_on_fail, plaintext_email, outdir, monochrome_logs=true, multiqc_report=null) { + + // Set up the e-mail variables + def subject = "[${workflow.manifest.name}] Successful: ${workflow.runName}" + if (!workflow.success) { + subject = "[${workflow.manifest.name}] FAILED: ${workflow.runName}" + } + + def summary = [:] + summary_params + .keySet() + .sort() + .each { group -> + summary << summary_params[group] + } + + def misc_fields = [:] + misc_fields['Date Started'] = workflow.start + misc_fields['Date Completed'] = workflow.complete + misc_fields['Pipeline script file path'] = workflow.scriptFile + misc_fields['Pipeline script hash ID'] = workflow.scriptId + if (workflow.repository) { + misc_fields['Pipeline repository Git URL'] = workflow.repository + } + if (workflow.commitId) { + misc_fields['Pipeline repository Git Commit'] = workflow.commitId + } + if (workflow.revision) { + misc_fields['Pipeline Git branch/tag'] = workflow.revision + } + misc_fields['Nextflow Version'] = workflow.nextflow.version + misc_fields['Nextflow Build'] = workflow.nextflow.build + misc_fields['Nextflow Compile Timestamp'] = workflow.nextflow.timestamp + + def email_fields = [:] + email_fields['version'] = getWorkflowVersion() + email_fields['runName'] = workflow.runName + email_fields['success'] = workflow.success + email_fields['dateComplete'] = workflow.complete + email_fields['duration'] = workflow.duration + email_fields['exitStatus'] = workflow.exitStatus + email_fields['errorMessage'] = (workflow.errorMessage ?: 'None') + email_fields['errorReport'] = (workflow.errorReport ?: 'None') + email_fields['commandLine'] = workflow.commandLine + email_fields['projectDir'] = workflow.projectDir + email_fields['summary'] = summary << misc_fields + + // On success try attach the multiqc report + def mqc_report = getSingleReport(multiqc_report) + + // Check if we are only sending emails on failure + def email_address = email + if (!email && email_on_fail && !workflow.success) { + email_address = email_on_fail + } + + // Render the TXT template + def engine = new groovy.text.GStringTemplateEngine() + def tf = new File("${workflow.projectDir}/assets/email_template.txt") + def txt_template = engine.createTemplate(tf).make(email_fields) + def email_txt = txt_template.toString() + + // Render the HTML template + def hf = new File("${workflow.projectDir}/assets/email_template.html") + def html_template = engine.createTemplate(hf).make(email_fields) + def email_html = html_template.toString() + + // Render the sendmail template + def max_multiqc_email_size = (params.containsKey('max_multiqc_email_size') ? params.max_multiqc_email_size : 0) as MemoryUnit + def smail_fields = [email: email_address, subject: subject, email_txt: email_txt, email_html: email_html, projectDir: "${workflow.projectDir}", mqcFile: mqc_report, mqcMaxSize: max_multiqc_email_size.toBytes()] + def sf = new File("${workflow.projectDir}/assets/sendmail_template.txt") + def sendmail_template = engine.createTemplate(sf).make(smail_fields) + def sendmail_html = sendmail_template.toString() + + // Send the HTML e-mail + def colors = logColours(monochrome_logs) as Map + if (email_address) { + try { + if (plaintext_email) { + new org.codehaus.groovy.GroovyException('Send plaintext e-mail, not HTML') + } + // Try to send HTML e-mail using sendmail + def sendmail_tf = new File(workflow.launchDir.toString(), ".sendmail_tmp.html") + sendmail_tf.withWriter { w -> w << sendmail_html } + ['sendmail', '-t'].execute() << sendmail_html + log.info("-${colors.purple}[${workflow.manifest.name}]${colors.green} Sent summary e-mail to ${email_address} (sendmail)-") + } + catch (Exception msg) { + log.debug(msg.toString()) + log.debug("Trying with mail instead of sendmail") + // Catch failures and try with plaintext + def mail_cmd = ['mail', '-s', subject, '--content-type=text/html', email_address] + mail_cmd.execute() << email_html + log.info("-${colors.purple}[${workflow.manifest.name}]${colors.green} Sent summary e-mail to ${email_address} (mail)-") + } + } + + // Write summary e-mail HTML to a file + def output_hf = new File(workflow.launchDir.toString(), ".pipeline_report.html") + output_hf.withWriter { w -> w << email_html } + nextflow.extension.FilesEx.copyTo(output_hf.toPath(), "${outdir}/pipeline_info/pipeline_report.html") + output_hf.delete() + + // Write summary e-mail TXT to a file + def output_tf = new File(workflow.launchDir.toString(), ".pipeline_report.txt") + output_tf.withWriter { w -> w << email_txt } + nextflow.extension.FilesEx.copyTo(output_tf.toPath(), "${outdir}/pipeline_info/pipeline_report.txt") + output_tf.delete() +} + +// +// Print pipeline summary on completion +// +def completionSummary(monochrome_logs=true) { + def colors = logColours(monochrome_logs) as Map + if (workflow.success) { + if (workflow.stats.ignoredCount == 0) { + log.info("-${colors.purple}[${workflow.manifest.name}]${colors.green} Pipeline completed successfully${colors.reset}-") + } + else { + log.info("-${colors.purple}[${workflow.manifest.name}]${colors.yellow} Pipeline completed successfully, but with errored process(es) ${colors.reset}-") + } + } + else { + log.info("-${colors.purple}[${workflow.manifest.name}]${colors.red} Pipeline completed with errors${colors.reset}-") + } +} + +// +// Construct and send a notification to a web server as JSON e.g. Microsoft Teams and Slack +// +def imNotification(summary_params, hook_url) { + def summary = [:] + summary_params + .keySet() + .sort() + .each { group -> + summary << summary_params[group] + } + + def misc_fields = [:] + misc_fields['start'] = workflow.start + misc_fields['complete'] = workflow.complete + misc_fields['scriptfile'] = workflow.scriptFile + misc_fields['scriptid'] = workflow.scriptId + if (workflow.repository) { + misc_fields['repository'] = workflow.repository + } + if (workflow.commitId) { + misc_fields['commitid'] = workflow.commitId + } + if (workflow.revision) { + misc_fields['revision'] = workflow.revision + } + misc_fields['nxf_version'] = workflow.nextflow.version + misc_fields['nxf_build'] = workflow.nextflow.build + misc_fields['nxf_timestamp'] = workflow.nextflow.timestamp + + def msg_fields = [:] + msg_fields['version'] = getWorkflowVersion() + msg_fields['runName'] = workflow.runName + msg_fields['success'] = workflow.success + msg_fields['dateComplete'] = workflow.complete + msg_fields['duration'] = workflow.duration + msg_fields['exitStatus'] = workflow.exitStatus + msg_fields['errorMessage'] = (workflow.errorMessage ?: 'None') + msg_fields['errorReport'] = (workflow.errorReport ?: 'None') + msg_fields['commandLine'] = workflow.commandLine.replaceFirst(/ +--hook_url +[^ ]+/, "") + msg_fields['projectDir'] = workflow.projectDir + msg_fields['summary'] = summary << misc_fields + + // Render the JSON template + def engine = new groovy.text.GStringTemplateEngine() + // Different JSON depending on the service provider + // Defaults to "Adaptive Cards" (https://adaptivecards.io), except Slack which has its own format + def json_path = hook_url.contains("hooks.slack.com") ? "slackreport.json" : "adaptivecard.json" + def hf = new File("${workflow.projectDir}/assets/${json_path}") + def json_template = engine.createTemplate(hf).make(msg_fields) + def json_message = json_template.toString() + + // POST + def post = new URL(hook_url).openConnection() + post.setRequestMethod("POST") + post.setDoOutput(true) + post.setRequestProperty("Content-Type", "application/json") + post.getOutputStream().write(json_message.getBytes("UTF-8")) + def postRC = post.getResponseCode() + if (!postRC.equals(200)) { + log.warn(post.getErrorStream().getText()) + } +} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/meta.yml b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/meta.yml new file mode 100644 index 0000000000..d08d24342d --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/meta.yml @@ -0,0 +1,24 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "UTILS_NFCORE_PIPELINE" +description: Subworkflow with utility functions specific to the nf-core pipeline template +keywords: + - utility + - pipeline + - initialise + - version +components: [] +input: + - nextflow_cli_args: + type: list + description: | + Nextflow CLI positional arguments +output: + - success: + type: boolean + description: | + Dummy output to indicate success +authors: + - "@adamrtalbot" +maintainers: + - "@adamrtalbot" + - "@maxulysse" diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test new file mode 100644 index 0000000000..f117040cbd --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test @@ -0,0 +1,126 @@ + +nextflow_function { + + name "Test Functions" + script "../main.nf" + config "subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config" + tag "subworkflows" + tag "subworkflows_nfcore" + tag "utils_nfcore_pipeline" + tag "subworkflows/utils_nfcore_pipeline" + + test("Test Function checkConfigProvided") { + + function "checkConfigProvided" + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function checkProfileProvided") { + + function "checkProfileProvided" + + when { + function { + """ + input[0] = [] + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function without logColours") { + + function "logColours" + + when { + function { + """ + input[0] = true + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function with logColours") { + function "logColours" + + when { + function { + """ + input[0] = false + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function getSingleReport with a single file") { + function "getSingleReport" + + when { + function { + """ + input[0] = file(params.modules_testdata_base_path + '/generic/tsv/test.tsv', checkIfExists: true) + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert function.result.contains("test.tsv") } + ) + } + } + + test("Test Function getSingleReport with multiple files") { + function "getSingleReport" + + when { + function { + """ + input[0] = [ + file(params.modules_testdata_base_path + '/generic/tsv/test.tsv', checkIfExists: true), + file(params.modules_testdata_base_path + '/generic/tsv/network.tsv', checkIfExists: true), + file(params.modules_testdata_base_path + '/generic/tsv/expression.tsv', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert function.result.contains("test.tsv") }, + { assert !function.result.contains("network.tsv") }, + { assert !function.result.contains("expression.tsv") } + ) + } + } +} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap new file mode 100644 index 0000000000..02c6701413 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap @@ -0,0 +1,136 @@ +{ + "Test Function checkProfileProvided": { + "content": null, + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:03:03.360873" + }, + "Test Function checkConfigProvided": { + "content": [ + true + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:02:59.729647" + }, + "Test Function without logColours": { + "content": [ + { + "reset": "", + "bold": "", + "dim": "", + "underlined": "", + "blink": "", + "reverse": "", + "hidden": "", + "black": "", + "red": "", + "green": "", + "yellow": "", + "blue": "", + "purple": "", + "cyan": "", + "white": "", + "bblack": "", + "bred": "", + "bgreen": "", + "byellow": "", + "bblue": "", + "bpurple": "", + "bcyan": "", + "bwhite": "", + "ublack": "", + "ured": "", + "ugreen": "", + "uyellow": "", + "ublue": "", + "upurple": "", + "ucyan": "", + "uwhite": "", + "iblack": "", + "ired": "", + "igreen": "", + "iyellow": "", + "iblue": "", + "ipurple": "", + "icyan": "", + "iwhite": "", + "biblack": "", + "bired": "", + "bigreen": "", + "biyellow": "", + "biblue": "", + "bipurple": "", + "bicyan": "", + "biwhite": "" + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:03:17.969323" + }, + "Test Function with logColours": { + "content": [ + { + "reset": "\u001b[0m", + "bold": "\u001b[1m", + "dim": "\u001b[2m", + "underlined": "\u001b[4m", + "blink": "\u001b[5m", + "reverse": "\u001b[7m", + "hidden": "\u001b[8m", + "black": "\u001b[0;30m", + "red": "\u001b[0;31m", + "green": "\u001b[0;32m", + "yellow": "\u001b[0;33m", + "blue": "\u001b[0;34m", + "purple": "\u001b[0;35m", + "cyan": "\u001b[0;36m", + "white": "\u001b[0;37m", + "bblack": "\u001b[1;30m", + "bred": "\u001b[1;31m", + "bgreen": "\u001b[1;32m", + "byellow": "\u001b[1;33m", + "bblue": "\u001b[1;34m", + "bpurple": "\u001b[1;35m", + "bcyan": "\u001b[1;36m", + "bwhite": "\u001b[1;37m", + "ublack": "\u001b[4;30m", + "ured": "\u001b[4;31m", + "ugreen": "\u001b[4;32m", + "uyellow": "\u001b[4;33m", + "ublue": "\u001b[4;34m", + "upurple": "\u001b[4;35m", + "ucyan": "\u001b[4;36m", + "uwhite": "\u001b[4;37m", + "iblack": "\u001b[0;90m", + "ired": "\u001b[0;91m", + "igreen": "\u001b[0;92m", + "iyellow": "\u001b[0;93m", + "iblue": "\u001b[0;94m", + "ipurple": "\u001b[0;95m", + "icyan": "\u001b[0;96m", + "iwhite": "\u001b[0;97m", + "biblack": "\u001b[1;90m", + "bired": "\u001b[1;91m", + "bigreen": "\u001b[1;92m", + "biyellow": "\u001b[1;93m", + "biblue": "\u001b[1;94m", + "bipurple": "\u001b[1;95m", + "bicyan": "\u001b[1;96m", + "biwhite": "\u001b[1;97m" + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:03:21.714424" + } +} \ No newline at end of file diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test new file mode 100644 index 0000000000..8940d32d1e --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test @@ -0,0 +1,29 @@ +nextflow_workflow { + + name "Test Workflow UTILS_NFCORE_PIPELINE" + script "../main.nf" + config "subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config" + workflow "UTILS_NFCORE_PIPELINE" + tag "subworkflows" + tag "subworkflows_nfcore" + tag "utils_nfcore_pipeline" + tag "subworkflows/utils_nfcore_pipeline" + + test("Should run without failures") { + + when { + workflow { + """ + input[0] = [] + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot(workflow.out).match() } + ) + } + } +} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap new file mode 100644 index 0000000000..859d1030fb --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap @@ -0,0 +1,19 @@ +{ + "Should run without failures": { + "content": [ + { + "0": [ + true + ], + "valid_config": [ + true + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:03:25.726491" + } +} \ No newline at end of file diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config new file mode 100644 index 0000000000..d0a926bf6d --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config @@ -0,0 +1,9 @@ +manifest { + name = 'nextflow_workflow' + author = """nf-core""" + homePage = 'https://127.0.0.1' + description = """Dummy pipeline""" + nextflowVersion = '!>=23.04.0' + version = '9.9.9' + doi = 'https://doi.org/10.5281/zenodo.5070524' +} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/main.nf b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/main.nf new file mode 100644 index 0000000000..ee4738c8d1 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/main.nf @@ -0,0 +1,74 @@ +// +// Subworkflow that uses the nf-schema plugin to validate parameters and render the parameter summary +// + +include { paramsSummaryLog } from 'plugin/nf-schema' +include { validateParameters } from 'plugin/nf-schema' +include { paramsHelp } from 'plugin/nf-schema' + +workflow UTILS_NFSCHEMA_PLUGIN { + + take: + input_workflow // workflow: the workflow object used by nf-schema to get metadata from the workflow + validate_params // boolean: validate the parameters + parameters_schema // string: path to the parameters JSON schema. + // this has to be the same as the schema given to `validation.parametersSchema` + // when this input is empty it will automatically use the configured schema or + // "${projectDir}/nextflow_schema.json" as default. This input should not be empty + // for meta pipelines + help // boolean: show help message + help_full // boolean: show full help message + show_hidden // boolean: show hidden parameters in help message + before_text // string: text to show before the help message and parameters summary + after_text // string: text to show after the help message and parameters summary + command // string: an example command of the pipeline + + main: + + if(help || help_full) { + help_options = [ + beforeText: before_text, + afterText: after_text, + command: command, + showHidden: show_hidden, + fullHelp: help_full, + ] + if(parameters_schema) { + help_options << [parametersSchema: parameters_schema] + } + log.info paramsHelp( + help_options, + params.help instanceof String ? params.help : "", + ) + exit 0 + } + + // + // Print parameter summary to stdout. This will display the parameters + // that differ from the default given in the JSON schema + // + + summary_options = [:] + if(parameters_schema) { + summary_options << [parametersSchema: parameters_schema] + } + log.info before_text + log.info paramsSummaryLog(summary_options, input_workflow) + log.info after_text + + // + // Validate the parameters using nextflow_schema.json or the schema + // given via the validation.parametersSchema configuration option + // + if(validate_params) { + validateOptions = [:] + if(parameters_schema) { + validateOptions << [parametersSchema: parameters_schema] + } + validateParameters(validateOptions) + } + + emit: + dummy_emit = true +} + diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/meta.yml b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/meta.yml new file mode 100644 index 0000000000..f7d9f02885 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/meta.yml @@ -0,0 +1,35 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "utils_nfschema_plugin" +description: Run nf-schema to validate parameters and create a summary of changed parameters +keywords: + - validation + - JSON schema + - plugin + - parameters + - summary +components: [] +input: + - input_workflow: + type: object + description: | + The workflow object of the used pipeline. + This object contains meta data used to create the params summary log + - validate_params: + type: boolean + description: Validate the parameters and error if invalid. + - parameters_schema: + type: string + description: | + Path to the parameters JSON schema. + This has to be the same as the schema given to the `validation.parametersSchema` config + option. When this input is empty it will automatically use the configured schema or + "${projectDir}/nextflow_schema.json" as default. The schema should not be given in this way + for meta pipelines. +output: + - dummy_emit: + type: boolean + description: Dummy emit to make nf-core subworkflows lint happy +authors: + - "@nvnieuwk" +maintainers: + - "@nvnieuwk" diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test new file mode 100644 index 0000000000..c977917aac --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test @@ -0,0 +1,173 @@ +nextflow_workflow { + + name "Test Subworkflow UTILS_NFSCHEMA_PLUGIN" + script "../main.nf" + workflow "UTILS_NFSCHEMA_PLUGIN" + + tag "subworkflows" + tag "subworkflows_nfcore" + tag "subworkflows/utils_nfschema_plugin" + tag "plugin/nf-schema" + + config "./nextflow.config" + + test("Should run nothing") { + + when { + + params { + test_data = '' + } + + workflow { + """ + validate_params = false + input[0] = workflow + input[1] = validate_params + input[2] = "" + input[3] = false + input[4] = false + input[5] = false + input[6] = "" + input[7] = "" + input[8] = "" + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } + + test("Should validate params") { + + when { + + params { + test_data = '' + outdir = null + } + + workflow { + """ + validate_params = true + input[0] = workflow + input[1] = validate_params + input[2] = "" + input[3] = false + input[4] = false + input[5] = false + input[6] = "" + input[7] = "" + input[8] = "" + """ + } + } + + then { + assertAll( + { assert workflow.failed }, + { assert workflow.stdout.any { it.contains('ERROR ~ Validation of pipeline parameters failed!') } } + ) + } + } + + test("Should run nothing - custom schema") { + + when { + + params { + test_data = '' + } + + workflow { + """ + validate_params = false + input[0] = workflow + input[1] = validate_params + input[2] = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + input[3] = false + input[4] = false + input[5] = false + input[6] = "" + input[7] = "" + input[8] = "" + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } + + test("Should validate params - custom schema") { + + when { + + params { + test_data = '' + outdir = null + } + + workflow { + """ + validate_params = true + input[0] = workflow + input[1] = validate_params + input[2] = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + input[3] = false + input[4] = false + input[5] = false + input[6] = "" + input[7] = "" + input[8] = "" + """ + } + } + + then { + assertAll( + { assert workflow.failed }, + { assert workflow.stdout.any { it.contains('ERROR ~ Validation of pipeline parameters failed!') } } + ) + } + } + + test("Should create a help message") { + + when { + + params { + test_data = '' + outdir = null + } + + workflow { + """ + validate_params = true + input[0] = workflow + input[1] = validate_params + input[2] = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + input[3] = true + input[4] = false + input[5] = false + input[6] = "Before" + input[7] = "After" + input[8] = "nextflow run test/test" + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } +} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config new file mode 100644 index 0000000000..8d8c73718a --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config @@ -0,0 +1,8 @@ +plugins { + id "nf-schema@2.5.1" +} + +validation { + parametersSchema = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + monochromeLogs = true +} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json new file mode 100644 index 0000000000..91e26fc4a7 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json @@ -0,0 +1,103 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/./master/nextflow_schema.json", + "title": ". pipeline parameters", + "description": "", + "type": "object", + "$defs": { + "input_output_options": { + "title": "Input/output options", + "type": "object", + "fa_icon": "fas fa-terminal", + "description": "Define where the pipeline should find input data and save output data.", + "required": ["outdir"], + "properties": { + "validate_params": { + "type": "boolean", + "description": "Validate parameters?", + "default": true, + "hidden": true + }, + "outdir": { + "type": "string", + "format": "directory-path", + "description": "The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure.", + "fa_icon": "fas fa-folder-open" + }, + "test_data_base": { + "type": "string", + "default": "https://raw.githubusercontent.com/nf-core/test-datasets/modules", + "description": "Base for test data directory", + "hidden": true + }, + "test_data": { + "type": "string", + "description": "Fake test data param", + "hidden": true + } + } + }, + "generic_options": { + "title": "Generic options", + "type": "object", + "fa_icon": "fas fa-file-import", + "description": "Less common options for the pipeline, typically set in a config file.", + "help_text": "These options are common to all nf-core pipelines and allow you to customise some of the core preferences for how the pipeline runs.\n\nTypically these options would be set in a Nextflow config file loaded for all pipeline runs, such as `~/.nextflow/config`.", + "properties": { + "help": { + "type": "boolean", + "description": "Display help text.", + "fa_icon": "fas fa-question-circle", + "hidden": true + }, + "version": { + "type": "boolean", + "description": "Display version and exit.", + "fa_icon": "fas fa-question-circle", + "hidden": true + }, + "logo": { + "type": "boolean", + "default": true, + "description": "Display nf-core logo in console output.", + "fa_icon": "fas fa-image", + "hidden": true + }, + "singularity_pull_docker_container": { + "type": "boolean", + "description": "Pull Singularity container from Docker?", + "hidden": true + }, + "publish_dir_mode": { + "type": "string", + "default": "copy", + "description": "Method used to save pipeline results to output directory.", + "help_text": "The Nextflow `publishDir` option specifies which intermediate files should be saved to the output directory. This option tells the pipeline what method should be used to move these files. See [Nextflow docs](https://www.nextflow.io/docs/latest/process.html#publishdir) for details.", + "fa_icon": "fas fa-copy", + "enum": [ + "symlink", + "rellink", + "link", + "copy", + "copyNoFollow", + "move" + ], + "hidden": true + }, + "monochrome_logs": { + "type": "boolean", + "description": "Use monochrome_logs", + "hidden": true + } + } + } + }, + "allOf": [ + { + "$ref": "#/$defs/input_output_options" + }, + { + "$ref": "#/$defs/generic_options" + } + ] +} diff --git a/hello-nf-core/solutions/core-hello-part4/test-results/cat/test.txt b/hello-nf-core/solutions/core-hello-part4/test-results/cat/test.txt deleted file mode 100644 index c030fd55e9..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/test-results/cat/test.txt +++ /dev/null @@ -1,3 +0,0 @@ -BONJOUR -HOLà -HELLO diff --git a/hello-nf-core/solutions/core-hello-part4/test-results/cowpy/cowpy-test.txt b/hello-nf-core/solutions/core-hello-part4/test-results/cowpy/cowpy-test.txt deleted file mode 100644 index 8221aa139a..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/test-results/cowpy/cowpy-test.txt +++ /dev/null @@ -1,14 +0,0 @@ - _________ -/ BONJOUR \ -| HOLà | -\ HELLO / - --------- - \ - \ - .--. - |o_o | - |:_/ | - // \ \ - (| | ) - /'\_ _/`\ - \___)=(___/ diff --git a/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/execution_report_2025-10-24_10-21-35.html b/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/execution_report_2025-10-24_10-21-35.html deleted file mode 100644 index a2f0464222..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/execution_report_2025-10-24_10-21-35.html +++ /dev/null @@ -1,1040 +0,0 @@ - - - - - - - - - - - [stoic_murdock] Nextflow Workflow Report - - - - - - - -
-
- -

Nextflow workflow report

-

[stoic_murdock]

- - -
- Workflow execution completed successfully! -
- - -
-
Run times
-
- 24-Oct-2025 10:21:35 - 24-Oct-2025 10:21:40 - (duration: 4.8s) -
- -
-
-
  8 succeeded  
-
  0 cached  
-
  0 ignored  
-
  0 failed (0 retries)  
-
-
- -
Nextflow command
-
nextflow run . --outdir test-results -profile test,docker --validate_params false
-
- -
-
CPU-Hours
-
(a few seconds)
- -
Launch directory
-
/Users/jonathan.manning/projects/training/hello-nf-core/solutions/core-hello-part4
- -
Work directory
-
/Users/jonathan.manning/projects/training/hello-nf-core/solutions/core-hello-part4/work
- -
Project directory
-
/Users/jonathan.manning/projects/training/hello-nf-core/solutions/core-hello-part4
- - -
Script name
-
main.nf
- - - -
Script ID
-
c31b966b36e0478a8d80dfe8f16f3c88
- - -
Workflow session
-
77b25c4a-878f-48f7-84c1-00ab8843ad22
- - - -
Workflow profile
-
test,docker
- - - - - -
Nextflow version
-
version 25.04.6, build 5954 (01-07-2025 11:27 UTC)
-
-
-
- -
-

Resource Usage

-

These plots give an overview of the distribution of resource usage for each process.

- -

CPU

- -
-
-
-
-
-
-
- -
- -

Memory

- -
-
-
-
-
-
-
-
-
-
-
- -

Job Duration

- -
-
-
-
-
-
-
-
- -

I/O

- -
-
-
-
-
-
-
-
-
- -
-
-

Tasks

-

This table shows information about each task in the workflow. Use the search box on the right - to filter rows for specific values. Clicking headers will sort the table by that value and - scrolling side to side will reveal more columns.

-
- - -
-
-
-
-
- -
- (tasks table omitted because the dataset is too big) -
-
- -
-
- Generated by Nextflow, version 25.04.6 -
-
- - - - - diff --git a/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/execution_timeline_2025-10-24_10-21-35.html b/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/execution_timeline_2025-10-24_10-21-35.html deleted file mode 100644 index 8ca1ee1f79..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/execution_timeline_2025-10-24_10-21-35.html +++ /dev/null @@ -1,230 +0,0 @@ - - - - - - - - - - - - - -
-

Processes execution timeline

-

- Launch time:
- Elapsed time:
- Legend: job wall time / memory usage (RAM) -

-
-
- - - - - - - diff --git a/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/execution_trace_2025-10-24_10-21-35.txt b/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/execution_trace_2025-10-24_10-21-35.txt deleted file mode 100644 index cd44d8b797..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/execution_trace_2025-10-24_10-21-35.txt +++ /dev/null @@ -1,9 +0,0 @@ -task_id hash native_id name status exit submit duration realtime %cpu peak_rss peak_vmem rchar wchar -3 17/60765a 73496 CORE_HELLO:HELLO:sayHello (3) COMPLETED 0 2025-10-24 10:21:36.784 93ms 20ms - - - - - -1 a6/e22ff7 73495 CORE_HELLO:HELLO:sayHello (1) COMPLETED 0 2025-10-24 10:21:36.781 94ms 19ms - - - - - -2 14/8cd9f9 73493 CORE_HELLO:HELLO:sayHello (2) COMPLETED 0 2025-10-24 10:21:36.774 97ms 19ms - - - - - -6 c5/4b94fb 73573 CORE_HELLO:HELLO:convertToUpper (3) COMPLETED 0 2025-10-24 10:21:36.908 110ms 30ms - - - - - -5 ce/b14e47 73575 CORE_HELLO:HELLO:convertToUpper (2) COMPLETED 0 2025-10-24 10:21:36.920 113ms 31ms - - - - - -4 1e/2658c1 73577 CORE_HELLO:HELLO:convertToUpper (1) COMPLETED 0 2025-10-24 10:21:36.923 116ms 31ms - - - - - -7 78/7a1a50 73669 CORE_HELLO:HELLO:CAT_CAT (test) COMPLETED 0 2025-10-24 10:21:37.070 1.9s 0ms 117.6% 11.3 MB 176.8 MB 80.4 KB 310 B -8 0a/c69b7f 73802 CORE_HELLO:HELLO:cowpy COMPLETED 0 2025-10-24 10:21:38.970 1.5s 348ms 115.7% 47.3 MB 353.5 MB 1.2 MB 4.5 KB diff --git a/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/hello_software_versions.yml b/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/hello_software_versions.yml deleted file mode 100644 index f2ec349f67..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/hello_software_versions.yml +++ /dev/null @@ -1,3 +0,0 @@ -Workflow: - core/hello: v1.0.0dev - Nextflow: 25.04.6 diff --git a/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/params_2025-10-24_10-21-36.json b/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/params_2025-10-24_10-21-36.json deleted file mode 100644 index 4e8708ff9a..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/params_2025-10-24_10-21-36.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "input": "/Users/jonathan.manning/projects/training/hello-nf-core/solutions/core-hello-part4/assets/greetings.csv", - "outdir": "test-results", - "publish_dir_mode": "copy", - "monochrome_logs": false, - "help": false, - "help_full": false, - "show_hidden": false, - "version": false, - "pipelines_testdata_base_path": "https://raw.githubusercontent.com/nf-core/test-datasets/", - "trace_report_suffix": "2025-10-24_10-21-35", - "config_profile_name": "Test profile", - "config_profile_description": "Minimal test dataset to check pipeline function", - "custom_config_version": "master", - "validate_params": false, - "custom_config_base": "https://raw.githubusercontent.com/nf-core/configs/master", - "config_profile_contact": null, - "config_profile_url": null, - "batch": "test", - "character": "tux" -} \ No newline at end of file diff --git a/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/pipeline_dag_2025-10-24_10-21-35.html b/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/pipeline_dag_2025-10-24_10-21-35.html deleted file mode 100644 index 4f540abbe3..0000000000 --- a/hello-nf-core/solutions/core-hello-part4/test-results/pipeline_info/pipeline_dag_2025-10-24_10-21-35.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - -
-%%{
-  init: {
-    'theme': 'base',
-    'themeVariables': {
-      'primaryColor': '#B6ECE2',
-      'primaryTextColor': '#160F26',
-      'primaryBorderColor': '#065647',
-      'lineColor': '#545555',
-      'clusterBkg': '#BABCBD22',
-      'clusterBorder': '#DDDEDE',
-      'fontFamily': 'arial'
-    }
-  }
-}%%
-flowchart TB
-    subgraph " "
-    v2["Channel.fromList"]
-    end
-    subgraph "CORE_HELLO [CORE_HELLO]"
-    subgraph "CORE_HELLO:HELLO [HELLO]"
-    v4(["sayHello"])
-    v5(["convertToUpper"])
-    v8(["CAT_CAT"])
-    v10(["cowpy"])
-    v6(( ))
-    end
-    end
-    subgraph " "
-    v9[" "]
-    v11["cowpy_hellos"]
-    end
-    subgraph "PIPELINE_INITIALISATION [PIPELINE_INITIALISATION]"
-    v3(( ))
-    end
-    v2 --> v3
-    v3 --> v4
-    v4 --> v5
-    v5 --> v6
-    v6 --> v8
-    v8 --> v10
-    v8 --> v9
-    v10 --> v11
-
-
- - - diff --git a/hello-nf-core/solutions/core-hello-part4/workflows/hello.nf b/hello-nf-core/solutions/core-hello-part4/workflows/hello.nf new file mode 100644 index 0000000000..1993686608 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part4/workflows/hello.nf @@ -0,0 +1,67 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ +include { paramsSummaryMap } from 'plugin/nf-schema' +include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' +include { sayHello } from '../modules/local/sayHello.nf' +include { convertToUpper } from '../modules/local/convertToUpper.nf' +include { cowpy } from '../modules/local/cowpy.nf' +include { CAT_CAT } from '../modules/nf-core/cat/cat/main' + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + RUN MAIN WORKFLOW +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow HELLO { + + take: + ch_samplesheet // channel: samplesheet read in from --input + + main: + + ch_versions = Channel.empty() + + // emit a greeting + sayHello(ch_samplesheet) + + // convert the greeting to uppercase + convertToUpper(sayHello.out) + + // create metadata map with batch name as the ID + def cat_meta = [ id: params.batch ] + // create a channel with metadata and files in tuple format + ch_for_cat = convertToUpper.out.collect().map { files -> tuple(cat_meta, files) } + + // concatenate files using the nf-core cat/cat module + CAT_CAT(ch_for_cat) + + // generate ASCII art of the greetings with cowpy + cowpy(CAT_CAT.out.file_out) + + // + // Collate and save software versions + // + softwareVersionsToYAML(ch_versions) + .collectFile( + storeDir: "${params.outdir}/pipeline_info", + name: 'hello_software_' + 'versions.yml', + sort: true, + newLine: true + ).set { ch_collated_versions } + + + emit: + cowpy_hellos = cowpy.out.cowpy_output + versions = ch_versions // channel: [ path(versions.yml) ] + +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + THE END +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ From c7175588e0129abe08697e87ad332283e79d2427 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 21:46:33 +0000 Subject: [PATCH 058/113] Try some linting fixes --- .../core-hello-part4/modules/local/cowpy/main.nf | 8 ++++---- .../modules/local/cowpy/tests/main.nf.test | 6 ++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/hello-nf-core/solutions/core-hello-part4/modules/local/cowpy/main.nf b/hello-nf-core/solutions/core-hello-part4/modules/local/cowpy/main.nf index 212821d599..1b30633471 100644 --- a/hello-nf-core/solutions/core-hello-part4/modules/local/cowpy/main.nf +++ b/hello-nf-core/solutions/core-hello-part4/modules/local/cowpy/main.nf @@ -21,9 +21,9 @@ process COWPY { script: def args = task.ext.args ?: '' def prefix = task.ext.prefix ?: "${meta.id}" - + """ - + cat <<-END_VERSIONS > versions.yml "${task.process}": @@ -34,10 +34,10 @@ process COWPY { stub: def args = task.ext.args ?: '' def prefix = task.ext.prefix ?: "${meta.id}" - + """ echo $args - + cat <<-END_VERSIONS > versions.yml "${task.process}": diff --git a/hello-nf-core/solutions/core-hello-part4/modules/local/cowpy/tests/main.nf.test b/hello-nf-core/solutions/core-hello-part4/modules/local/cowpy/tests/main.nf.test index 88f75f7b6a..bd9c6e4767 100644 --- a/hello-nf-core/solutions/core-hello-part4/modules/local/cowpy/tests/main.nf.test +++ b/hello-nf-core/solutions/core-hello-part4/modules/local/cowpy/tests/main.nf.test @@ -22,7 +22,8 @@ nextflow_process { process { """ // TODO nf-core: define inputs of the process here. Example: - + + input[0] = [ [ id:'test' ], file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), @@ -54,7 +55,8 @@ nextflow_process { process { """ // TODO nf-core: define inputs of the process here. Example: - + + input[0] = [ [ id:'test' ], file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), From 5c02a4bdb67fc72cfd1161e7bfbd9b85145d9752 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 21:53:33 +0000 Subject: [PATCH 059/113] Fix linting errors in nf-core solution files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fixed markdown code fence formatting in 04_make_module.md - Fixed end-of-file formatting in all .snap test files - Removed extra blank lines from utils_nfschema_plugin/main.nf files 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/04_make_module.md | 4 ++-- .../modules/nf-core/cat/cat/tests/main.nf.test.snap | 4 ++-- .../utils_nextflow_pipeline/tests/main.function.nf.test.snap | 2 +- .../utils_nfcore_pipeline/tests/main.function.nf.test.snap | 2 +- .../utils_nfcore_pipeline/tests/main.workflow.nf.test.snap | 2 +- .../subworkflows/nf-core/utils_nfschema_plugin/main.nf | 1 - .../modules/nf-core/cat/cat/tests/main.nf.test.snap | 4 ++-- .../utils_nextflow_pipeline/tests/main.function.nf.test.snap | 2 +- .../utils_nfcore_pipeline/tests/main.function.nf.test.snap | 2 +- .../utils_nfcore_pipeline/tests/main.workflow.nf.test.snap | 2 +- .../subworkflows/nf-core/utils_nfschema_plugin/main.nf | 1 - 11 files changed, 12 insertions(+), 14 deletions(-) diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index cdd19ceab0..b20875e4c6 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -354,7 +354,8 @@ nextflow run . --outdir core-hello-results -profile test,docker --validate_param The pipeline should run successfully. In the output, look for the cowpy process execution line which will show something like: ```console title="Output (excerpt)" -[bd/0abaf8] CORE_HELLO:HELLO:cowpy [100%] 1 of 1 ✔``` +[bd/0abaf8] CORE_HELLO:HELLO:cowpy [100%] 1 of 1 ✔ +``` Now let's verify that the `ext.args` configuration actually passed the character argument to the cowpy command. Use the task hash (the `bd/0abaf8` part) to inspect the `.command.sh` file in the work directory: @@ -400,7 +401,6 @@ cat work/bd/0abaf8*/cowpy-test.txt | | ``` - ### 1.3. Add configurable output naming with ext.prefix There's one more nf-core pattern we can apply: using `ext.prefix` for configurable output file naming. diff --git a/hello-nf-core/solutions/core-hello-part3/modules/nf-core/cat/cat/tests/main.nf.test.snap b/hello-nf-core/solutions/core-hello-part3/modules/nf-core/cat/cat/tests/main.nf.test.snap index b7623ee650..e2381ca20b 100644 --- a/hello-nf-core/solutions/core-hello-part3/modules/nf-core/cat/cat/tests/main.nf.test.snap +++ b/hello-nf-core/solutions/core-hello-part3/modules/nf-core/cat/cat/tests/main.nf.test.snap @@ -93,7 +93,7 @@ "test_cat_name_conflict": { "content": [ [ - + ] ], "meta": { @@ -144,4 +144,4 @@ }, "timestamp": "2024-07-22T11:51:57.581523" } -} \ No newline at end of file +} diff --git a/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap b/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap index e3f0baf473..846287c417 100644 --- a/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap +++ b/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap @@ -17,4 +17,4 @@ }, "timestamp": "2024-02-28T12:02:12.425833" } -} \ No newline at end of file +} diff --git a/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap b/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap index 02c6701413..b13b311213 100644 --- a/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap +++ b/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap @@ -133,4 +133,4 @@ }, "timestamp": "2024-02-28T12:03:21.714424" } -} \ No newline at end of file +} diff --git a/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap b/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap index 859d1030fb..84ee1e1d1e 100644 --- a/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap +++ b/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap @@ -16,4 +16,4 @@ }, "timestamp": "2024-02-28T12:03:25.726491" } -} \ No newline at end of file +} diff --git a/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfschema_plugin/main.nf b/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfschema_plugin/main.nf index ee4738c8d1..acb3972419 100644 --- a/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfschema_plugin/main.nf +++ b/hello-nf-core/solutions/core-hello-part3/subworkflows/nf-core/utils_nfschema_plugin/main.nf @@ -71,4 +71,3 @@ workflow UTILS_NFSCHEMA_PLUGIN { emit: dummy_emit = true } - diff --git a/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/main.nf.test.snap b/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/main.nf.test.snap index b7623ee650..e2381ca20b 100644 --- a/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/main.nf.test.snap +++ b/hello-nf-core/solutions/core-hello-part4/modules/nf-core/cat/cat/tests/main.nf.test.snap @@ -93,7 +93,7 @@ "test_cat_name_conflict": { "content": [ [ - + ] ], "meta": { @@ -144,4 +144,4 @@ }, "timestamp": "2024-07-22T11:51:57.581523" } -} \ No newline at end of file +} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap index e3f0baf473..846287c417 100644 --- a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap +++ b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap @@ -17,4 +17,4 @@ }, "timestamp": "2024-02-28T12:02:12.425833" } -} \ No newline at end of file +} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap index 02c6701413..b13b311213 100644 --- a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap +++ b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap @@ -133,4 +133,4 @@ }, "timestamp": "2024-02-28T12:03:21.714424" } -} \ No newline at end of file +} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap index 859d1030fb..84ee1e1d1e 100644 --- a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap +++ b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap @@ -16,4 +16,4 @@ }, "timestamp": "2024-02-28T12:03:25.726491" } -} \ No newline at end of file +} diff --git a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/main.nf b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/main.nf index ee4738c8d1..acb3972419 100644 --- a/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/main.nf +++ b/hello-nf-core/solutions/core-hello-part4/subworkflows/nf-core/utils_nfschema_plugin/main.nf @@ -71,4 +71,3 @@ workflow UTILS_NFSCHEMA_PLUGIN { emit: dummy_emit = true } - From 66201a74724cb5c41ceebf7e0ce020f83bd7a606 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 21:56:09 +0000 Subject: [PATCH 060/113] prettier --- .../solutions/core-hello-part3/modules.json | 64 +++++++++---------- .../solutions/core-hello-part4/modules.json | 64 +++++++++---------- 2 files changed, 64 insertions(+), 64 deletions(-) diff --git a/hello-nf-core/solutions/core-hello-part3/modules.json b/hello-nf-core/solutions/core-hello-part3/modules.json index 3e65b38609..71a7815a6b 100644 --- a/hello-nf-core/solutions/core-hello-part3/modules.json +++ b/hello-nf-core/solutions/core-hello-part3/modules.json @@ -1,36 +1,36 @@ { - "name": "core/hello", - "homePage": "https://github.com/core/hello", - "repos": { - "https://github.com/nf-core/modules.git": { - "modules": { - "nf-core": { - "cat/cat": { - "branch": "master", - "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", - "installed_by": ["modules"] - } + "name": "core/hello", + "homePage": "https://github.com/core/hello", + "repos": { + "https://github.com/nf-core/modules.git": { + "modules": { + "nf-core": { + "cat/cat": { + "branch": "master", + "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", + "installed_by": ["modules"] + } + } + }, + "subworkflows": { + "nf-core": { + "utils_nextflow_pipeline": { + "branch": "master", + "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "installed_by": ["subworkflows"] + }, + "utils_nfcore_pipeline": { + "branch": "master", + "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "installed_by": ["subworkflows"] + }, + "utils_nfschema_plugin": { + "branch": "master", + "git_sha": "4b406a74dc0449c0401ed87d5bfff4252fd277fd", + "installed_by": ["subworkflows"] + } + } + } } - }, - "subworkflows": { - "nf-core": { - "utils_nextflow_pipeline": { - "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", - "installed_by": ["subworkflows"] - }, - "utils_nfcore_pipeline": { - "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", - "installed_by": ["subworkflows"] - }, - "utils_nfschema_plugin": { - "branch": "master", - "git_sha": "4b406a74dc0449c0401ed87d5bfff4252fd277fd", - "installed_by": ["subworkflows"] - } - } - } } - } } diff --git a/hello-nf-core/solutions/core-hello-part4/modules.json b/hello-nf-core/solutions/core-hello-part4/modules.json index 3e65b38609..71a7815a6b 100644 --- a/hello-nf-core/solutions/core-hello-part4/modules.json +++ b/hello-nf-core/solutions/core-hello-part4/modules.json @@ -1,36 +1,36 @@ { - "name": "core/hello", - "homePage": "https://github.com/core/hello", - "repos": { - "https://github.com/nf-core/modules.git": { - "modules": { - "nf-core": { - "cat/cat": { - "branch": "master", - "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", - "installed_by": ["modules"] - } + "name": "core/hello", + "homePage": "https://github.com/core/hello", + "repos": { + "https://github.com/nf-core/modules.git": { + "modules": { + "nf-core": { + "cat/cat": { + "branch": "master", + "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", + "installed_by": ["modules"] + } + } + }, + "subworkflows": { + "nf-core": { + "utils_nextflow_pipeline": { + "branch": "master", + "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "installed_by": ["subworkflows"] + }, + "utils_nfcore_pipeline": { + "branch": "master", + "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "installed_by": ["subworkflows"] + }, + "utils_nfschema_plugin": { + "branch": "master", + "git_sha": "4b406a74dc0449c0401ed87d5bfff4252fd277fd", + "installed_by": ["subworkflows"] + } + } + } } - }, - "subworkflows": { - "nf-core": { - "utils_nextflow_pipeline": { - "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", - "installed_by": ["subworkflows"] - }, - "utils_nfcore_pipeline": { - "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", - "installed_by": ["subworkflows"] - }, - "utils_nfschema_plugin": { - "branch": "master", - "git_sha": "4b406a74dc0449c0401ed87d5bfff4252fd277fd", - "installed_by": ["subworkflows"] - } - } - } } - } } From 7723babbc53b438ce17db7a7c0bb762cde5c2e8f Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 22:02:57 +0000 Subject: [PATCH 061/113] Fix nested list indentation in input validation docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed sub-list indentation from 3 to 4 spaces to properly render nested bullet points under numbered list items. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/05_input_validation.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/hello_nf-core/05_input_validation.md b/docs/hello_nf-core/05_input_validation.md index 3eef5e415e..77a25004ae 100644 --- a/docs/hello_nf-core/05_input_validation.md +++ b/docs/hello_nf-core/05_input_validation.md @@ -38,17 +38,17 @@ nf-core pipelines validate two different kinds of input: 1. **Parameter validation**: Validates command-line parameters (flags like `--outdir`, `--batch`, `--input`) - - Checks parameter types, ranges, and formats - - Ensures required parameters are provided - - Validates file paths exist - - Defined in `nextflow_schema.json` + - Checks parameter types, ranges, and formats + - Ensures required parameters are provided + - Validates file paths exist + - Defined in `nextflow_schema.json` 2. **Input data validation**: Validates the contents of input files (like sample sheets or CSV files) - - Checks column structure and data types - - Validates file references within the input file - - Ensures required fields are present - - Defined in `assets/schema_input.json` + - Checks column structure and data types + - Validates file references within the input file + - Ensures required fields are present + - Defined in `assets/schema_input.json` Both types of validation happen **before** the pipeline executes any processes, ensuring fast failure with clear error messages. From 4b64c3fe710f39350f48687dd1a41f6e42245584 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Thu, 30 Oct 2025 22:08:43 +0000 Subject: [PATCH 062/113] Fix list rendering in input validation docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed from numbered list to bold headings with bullet lists to prevent all items from rendering as a single 10-item numbered list. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/05_input_validation.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/hello_nf-core/05_input_validation.md b/docs/hello_nf-core/05_input_validation.md index 77a25004ae..981947cb4b 100644 --- a/docs/hello_nf-core/05_input_validation.md +++ b/docs/hello_nf-core/05_input_validation.md @@ -36,19 +36,19 @@ The pipeline fails immediately with clear, actionable error messages. This saves nf-core pipelines validate two different kinds of input: -1. **Parameter validation**: Validates command-line parameters (flags like `--outdir`, `--batch`, `--input`) +**Parameter validation**: Validates command-line parameters (flags like `--outdir`, `--batch`, `--input`) - - Checks parameter types, ranges, and formats - - Ensures required parameters are provided - - Validates file paths exist - - Defined in `nextflow_schema.json` +- Checks parameter types, ranges, and formats +- Ensures required parameters are provided +- Validates file paths exist +- Defined in `nextflow_schema.json` -2. **Input data validation**: Validates the contents of input files (like sample sheets or CSV files) +**Input data validation**: Validates the contents of input files (like sample sheets or CSV files) - - Checks column structure and data types - - Validates file references within the input file - - Ensures required fields are present - - Defined in `assets/schema_input.json` +- Checks column structure and data types +- Validates file references within the input file +- Ensures required fields are present +- Defined in `assets/schema_input.json` Both types of validation happen **before** the pipeline executes any processes, ensuring fast failure with clear error messages. From add3cbfb430c88e56e3da3273146f58535a54bdf Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 31 Oct 2025 08:54:35 +0000 Subject: [PATCH 063/113] Remove redundant sentence from validation section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed repetitive statement about early failure that was already established in the "Why validation matters" section above. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/05_input_validation.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/hello_nf-core/05_input_validation.md b/docs/hello_nf-core/05_input_validation.md index 981947cb4b..bf38ab5b45 100644 --- a/docs/hello_nf-core/05_input_validation.md +++ b/docs/hello_nf-core/05_input_validation.md @@ -50,8 +50,6 @@ nf-core pipelines validate two different kinds of input: - Ensures required fields are present - Defined in `assets/schema_input.json` -Both types of validation happen **before** the pipeline executes any processes, ensuring fast failure with clear error messages. - !!! note This section assumes you have completed [Part 4: Make an nf-core module](./04_make_module.md) and have a working `core-hello` pipeline with nf-core-style modules. From b0f1ffc1f1a201ef20821d28f15e60df80804fae Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 31 Oct 2025 08:56:29 +0000 Subject: [PATCH 064/113] Clarify nf-schema as standalone plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Emphasized that nf-schema is a standalone Nextflow plugin that can be used in any pipeline, while also noting its heavy integration with and adoption by nf-core. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/05_input_validation.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/hello_nf-core/05_input_validation.md b/docs/hello_nf-core/05_input_validation.md index bf38ab5b45..2220c73be6 100644 --- a/docs/hello_nf-core/05_input_validation.md +++ b/docs/hello_nf-core/05_input_validation.md @@ -67,7 +67,8 @@ nf-core pipelines validate two different kinds of input: ## 1. The nf-schema plugin -The [nf-schema plugin](https://nextflow-io.github.io/nf-schema/latest/) is a Nextflow plugin that provides comprehensive validation capabilities for nf-core pipelines. +The [nf-schema plugin](https://nextflow-io.github.io/nf-schema/latest/) is a Nextflow plugin that provides comprehensive validation capabilities for any Nextflow pipeline. +While nf-schema is a standalone tool that can be used in any Nextflow workflow, it's heavily integrated into the nf-core ecosystem and is the standard validation solution for all nf-core pipelines. ### 1.1. Core functionality From 2f886f0a20875fe6bbb7724df5d445e308732931 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 31 Oct 2025 09:17:22 +0000 Subject: [PATCH 065/113] Update schema docs to use interactive builder tool MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced manual JSON editing instructions with the interactive nf-core pipelines schema build workflow. Added screenshots showing the web interface for adding parameters. Emphasized that manual editing is error-prone and the GUI tool is the recommended approach. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/05_input_validation.md | 135 ++++++++++++---------- docs/hello_nf-core/img/schema_add.png | Bin 0 -> 47961 bytes docs/hello_nf-core/img/schema_build.png | Bin 0 -> 111891 bytes 3 files changed, 71 insertions(+), 64 deletions(-) create mode 100644 docs/hello_nf-core/img/schema_add.png create mode 100644 docs/hello_nf-core/img/schema_build.png diff --git a/docs/hello_nf-core/05_input_validation.md b/docs/hello_nf-core/05_input_validation.md index 2220c73be6..eff53186bc 100644 --- a/docs/hello_nf-core/05_input_validation.md +++ b/docs/hello_nf-core/05_input_validation.md @@ -125,35 +125,38 @@ Let's start by adding parameter validation to our pipeline. This validates comma Let's look at a section of the `nextflow_schema.json` file that came with our pipeline template: ```bash -grep -A 20 '"input_output_options"' nextflow_schema.json +grep -A 25 '"input_output_options"' nextflow_schema.json ``` -The parameter schema is organized into groups. Here's the `input_output_options` group (simplified): +The parameter schema is organized into groups. Here's the `input_output_options` group: ```json title="core-hello/nextflow_schema.json (excerpt)" -"input_output_options": { - "title": "Input/output options", - "type": "object", - "description": "Define where the pipeline should find input data and save output data.", - "required": ["input", "outdir"], - "properties": { - "input": { - "type": "string", - "format": "file-path", - "exists": true, - "mimetype": "text/csv", - "pattern": "^\\S+\\.csv$", - "description": "Path to comma-separated file containing greetings.", - "help_text": "You will need to create a design file with information about the samples in your experiment before running the pipeline." + "input_output_options": { + "title": "Input/output options", + "type": "object", + "fa_icon": "fas fa-terminal", + "description": "Define where the pipeline should find input data and save output data.", + "required": ["input", "outdir"], + "properties": { + "input": { + "type": "string", + "format": "file-path", + "exists": true, + "schema": "assets/schema_input.json", + "mimetype": "text/csv", + "pattern": "^\\S+\\.csv$", + "description": "Path to comma-separated file containing information about the samples in the experiment.", + "help_text": "You will need to create a design file with information about the samples in your experiment before running the pipeline. Use this parameter to specify its location. It has to be a comma-separated file with 3 columns, and a header row.", + "fa_icon": "fas fa-file-csv" + }, + "outdir": { + "type": "string", + "format": "directory-path", + "description": "The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure.", + "fa_icon": "fas fa-folder-open" + } + } }, - "outdir": { - "type": "string", - "format": "directory-path", - "description": "The output directory where the results will be saved.", - "fa_icon": "fas fa-folder-open" - } - } -} ``` Key validation features: @@ -175,59 +178,62 @@ Notice the `batch` parameter we've been using isn't defined yet in the schema! ### 2.2. Add the batch parameter -The parameter schema can be edited manually, but nf-core provides a helpful GUI tool: +While the schema is a JSON file that can be edited manually, **manual editing is error-prone and not recommended**. +Instead, nf-core provides an interactive GUI tool that handles the JSON Schema syntax for you and validates your changes: ```bash nf-core pipelines schema build ``` -This command launches an interactive web interface where you can: +You'll see output like: -- Add new parameters -- Set validation rules -- Organize parameters into groups -- Generate help text +```console + ,--./,-. + ___ __ __ __ ___ /,-._.--\ +|\ | |__ __ / ` / \ |__) |__ } { +| \| | \__, \__/ | \ |___ \`-._,-`-, + `._,._,' -!!! warning "Schema validation errors" +nf-core/tools version 3.4.1 - https://nf-co.re - If you run `nf-core pipelines schema build` at this stage, you may see an error like: +INFO [✓] Default parameters match schema validation +INFO [✓] Pipeline schema looks valid (found 17 params) +INFO Writing schema with 17 params: 'nextflow_schema.json' +🚀 Launch web builder for customisation and editing? [y/n]: +``` - ``` - [✗] Invalid default parameters found: - input: Not in pipeline parameters. Check `nextflow.config`. - ``` +Type `y` and press Enter to launch the interactive web interface. - This happens because the template's schema includes an `input` parameter, but it's not yet defined in `nextflow.config`. You can safely ignore this for now - we're using the `--input` parameter as a command-line argument rather than setting a default in the config. +Your browser will open showing the Parameter schema builder: -For our simple case, we'll edit the JSON directly. Open `core-hello/nextflow_schema.json` and find the `"input_output_options"` section. Add the `batch` parameter: +![Schema builder interface](../img/hello_nf-core/schema_build.png) -```json title="core-hello/nextflow_schema.json (excerpt)" hl_lines="13-17" -"input_output_options": { - "title": "Input/output options", - "type": "object", - "description": "Define where the pipeline should find input data and save output data.", - "required": ["input", "outdir"], - "properties": { - "input": { - "type": "string", - "format": "file-path", - "exists": true, - "description": "Path to comma-separated file containing greetings." - }, - "batch": { - "type": "string", - "default": "batch-01", - "description": "Name for this batch of greetings" - }, - "outdir": { - "type": "string", - "format": "directory-path", - "description": "The output directory where the results will be saved." - } - } -} +To add the `batch` parameter: + +1. Click the **"Add parameter"** button at the top +2. Use the drag handle (⋮⋮) to move the new parameter up into the "Input/output options" group, below the `input` parameter +3. Fill in the parameter details: + - **ID**: `batch` + - **Description**: `Name for this batch of greetings` + - **Type**: `string` + - Check the **Required** checkbox + - Optionally, select an icon from the icon picker (e.g., `fas fa-layer-group`) + +![Adding the batch parameter](../img/hello_nf-core/schema_add.png) + +When you're done, click the **"Finished"** button at the top right. + +Back in your terminal, you'll see: + +```console +INFO Writing schema with 18 params: 'nextflow_schema.json' +⣾ Use ctrl+c to stop waiting and force exit. ``` +Press `Ctrl+C` to exit the schema builder. + +The tool has now updated your `nextflow_schema.json` file with the new `batch` parameter, handling all the JSON Schema syntax correctly. + ### 2.3. Test parameter validation Now let's test that parameter validation works correctly. @@ -259,7 +265,8 @@ The pipeline should run successfully, and the `batch` parameter is now validated ### Takeaway -You now know how to add parameters to `nextflow_schema.json` and test parameter validation. The nf-core schema build tool makes it easy to manage complex parameter schemas interactively. +You now know how to use the interactive `nf-core pipelines schema build` tool to add parameters to `nextflow_schema.json` and test parameter validation. +The web interface handles all the JSON Schema syntax for you, making it easy to manage complex parameter schemas without error-prone manual JSON editing. ### What's next? diff --git a/docs/hello_nf-core/img/schema_add.png b/docs/hello_nf-core/img/schema_add.png new file mode 100644 index 0000000000000000000000000000000000000000..5615bd86fbc7c86964e3e7d581ffd221464b5570 GIT binary patch literal 47961 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 (YmqE}nx!f@cK9?3&1^OO zVkgH}PbkAZE1OxTxw~v8P zyl%hH6tM1@AgqZy)L)9`O!*of6A$HNB3UuksOqH;u$h6V4JSNrq)WGE7E%~rvbFWB zJki^!r>sq*s?{!jEmhCrm{E2K`!Fqo0LMHkfn z?IYjt)TB0$1$mwGV#Ej52o37Y-vxtufq+ZH3@(Mf7qm&_YVH-Vj5z}t9Bl)BTQ`t8 zS0(+5`qz|FP_KtSGB#70FT-}endZIJ^Je7`LBP2eTq)5+*JYn2M9jk@wL4RqyCxFl zvlEh&4f69~FVa@I2;RrdQyz8_fYmSaT}EGO`MgcOc)Pqn*3acx)-HBO681DCWPM6Fd)P(@phq7^3KoEhm=2Gh9c)Dp{xZ4sl<5LJ75 zus|X8M8NH#aoARuAYY<8rd<+$dO1^F6D)~(kTNxlGE*HB(HF|H;IO9y3C99O(qP90 zaQrzox8|wl^NkbRLZnF&$_u_eBiwnJtBJCG0|oL{k~1JpsW)SHN1nveDQst|LN*fY z4ke#92Oy}>MF_vy4<0$sq&fDVjqqDx9s`tGlK!Gyj8(-0Azt@OpqG<;1h5FOTUHa1 zX6Ll6kXQ$dl{?(sAMbC*iUJMn)6)!?`uoAEfq$l&W3i4}rs}~oPDN@4md1FeaHD4lj?JI1s&8SgcW<&XI zzQHU3ljhT`YUJZmrBaddiRFoyqIf4fc`E?4mcaxUlbVQ!!=R#)&kMTI53eM(jl_FS zDlJ6o)t*i_F1cQcu_at512XGBcG=8INmVY=IgVb#R%fHoKJa`UP@2+T=?_MwRbiNK zXw*25GX&N!f&jgmio~jXG+(nook!%s+N*|Sxn7QcG}{1nmbSJ$FE6)n4W;DYOExj< zkM5cBcZsk~+Y3YWC3I&c>dL{r^j5V|jn&kwsx&1Ps?RCYXb_dj-~<$;tK;L}CMUTe zKipls-f(b|zyPa#s|WdZ(sC*i8^8ICSOPTcr# z2$6~46`zgJ5gr!7bSJ&?AjiY(H8C;2ZE+s8jDyXCgWA3_ZO4a;)7|2-ADjgEzZM%p z2(2cSTq))v`V0YJW0dM${paRpP9~lp(gV=8+%udiu}Q16=|#Clz-jYc{KbA4bZ5wX zomH7{hEmIGtoA>Iywb|Xla*nNlhJBMX8_B_{YR=nwR@?+o3_dw+IO(gK>09tTQ-L zU2K}`J@9;w-`pLynVHkba7glwe2RJCNZ#c@fFwl}O>12z{XY6}UsciBQAosEgHXE# z9m1@_83;7y1jmm20iFGqcx=qN!aybi7U8`8g-I`Xm#M1pPiBC{9bfOIL6r>tluNw-%VRsD3nEZx|pQ!V$BH_(-?n+fb~mq17`A|LOx!nCN2{?c#>o-j6$ z6(waqhsJ4LBMmpubSl#<4&b~jnX?_T!dMvdKxkG5!fdSK$3Q@cLzLA;UASppIiue9 zwe%%n+qX_JmK=^zvV9(wc~oZO^-&AiZ(>uPuApMfvRZouNnYWEU(H>Yxj9ty=|E~M zGdI1!wX@!)Dio6SJ9Vd+Or)F*#<=9evEZP&$ak?V4Q;tW`c2 z$29k)g-on;;yQ1v1?D;)-GwE5YV$y)r0IM8$LT_jCIMp-gpzADA+0@7{CV52tS=>T ztVCWdtY%oN&nv1pvac=;HoK86_4Z2t`A(dg#3}?+JvPna1xp+SAtcl_)Xa7j|%M%9w5&)5j`? zY!)+y#m=m_eH0CK8#8y!9C4>y#wJ>oxCcCoxp$f^gY5OhWW#gl_m-5hNemozEmPf} z>Zac7{*WJXS!|6Vix8p;!xq(GCU3ZvK0H!aWn37ylRa!UU~91}bm|f? zTaqm}Bo6JGZKD&$JGo&hYmg(j-ld^-+w6!M9We3}>BF^Yd8~0dUp zmx*JOQ}RzB%-BRhy9@5Z{+1%J4has`Bl-PYz01PhS@nyaqZY^I<7&={k_;AOP(MS2 zH!598-|qcFj%a@fV~YV?Ald@Y*Ku;Qm^1fwuf8DM#q9ohRmaFABN6{9t6oAd&p2NiDHD>&S`q4vmJSS-? zeAy5j6cklZGADI9_~Qrt7ld!`M&jP-jiOtmK|V({AmBjBrRo}h?K_^vGVjY2_|lVhYCg4dcr7=p8RwwtwiH*hThXR3pucadT~Mi%fmBT( zg+M=3q$HV+2RLlGnw8;Ghm&Xbrg6(S40jc2M@fz!Dv|%#RQj``!(hPYKehbyL8p?E z&Ck^xn3Tir+-Qq_f<8#-%$Bd6PAUjV>xmn{)&;(XF^!p{|EKfB*Z^RwQ1k~9a6dD* z_W+mw;!#_k2ar=^eKz;(rO~5_)%1%HG$z;|ulw(~-fyEhg7J~A^GQc^$8gE#rx?$O zYno2uaAeT5mp~!lqdeIu5NoOp`KO)#^z5H^W_Dn>W(jbg%RF6BWJ<6$dZXMlDLi)d zB>xTEF|&AkHQE{*jqY>*Tf6-}Jn5wZfW~#t!2SP{P~PjA??W>o4EP)UTL*xLw39ZW z%m0w51b!tJcox+|U`74A6C+Q6K{|ZBLgM`!-PMspO2sJk0N>h zJZS#)9RUsKd`PIFu74d7e?A+y0pG~-==g*1?`tsM0Bi=rdrS9U8}*;2<-T$S$NP$~)1Y~54GbGs(fedlm!7?#@iF|pk9=@%56D{+xM?cL-7BL`lnzp1g1$dDOnZp2VgV{nsME)+r!$I zlY4-VA%&r@PqMl7=Wtp~m9Y%CCnUiRJEFm`!iq*sJJK761&|AmhTrj|43vD=M?9kb`1iOZ_70V5|MWeJN2p|Ex=B`| zx+W)AjB3(=epFe+h~VizO8DJca8#>B+w=Uu=Pv-yt literal 0 HcmV?d00001 diff --git a/docs/hello_nf-core/img/schema_build.png b/docs/hello_nf-core/img/schema_build.png new file mode 100644 index 0000000000000000000000000000000000000000..ed5eb43cf0b1840eac93210abfe97efb499f585d GIT binary patch literal 111891 zcmeFYhgVb0_CAgcR0J#(r6?9ax^zQVn$mkGh!7x#4xtwj5h>C;p-2n81qdAl5$Vzr zLI@x&Kh}12g}K6zLb+=)pT*R2HSzi$XZEgk5Ucc1z0y9tp!E$Dz9jmbIvv^-w`MUR|`-x#YD^a<+_XM^rPmb{^ohhF(=P$+> zEJ`AV&2kn1Qh;Q@0CQqm)KiVB%yr4mP04QpP1me{NV*Q=g78MkjE^S8)EB#xZ4#Y6 zlWS=gjAecSg>Bogy?;O8%Ff@bVQQjXG4MR1c#NO+Qs_Y2yiWtq3gP~mobsWE4PKxF zB6Rb4(>K3YydwkiLi8^AN*^J!PPhq%*UvjP-(6$a1iqUibCwjt=&WcAT%#}VlLht% z*PNTV%U;!VVd2jHd5>udTh@z)WK7Xy5jVoE`lrjfk^vErqF1HS44Ki-ELp`ORV=qK z?BQT~YkGaM-ifJt^z7;h#^;LGCS{AA2-!;Xq}&I}mgom$sm$jz{Trr-hbUyZ7&ajY zg|=S6J?{mI3m;{#{y3+QMPB{()DDFDa`)*&I-d&xxic>+u2#gP>b$*PLQZ(b_k$w% z6~)+v?l1S9ZjZdRoPK0Wj{m~uL^p8}H+|RhBKemmo>$nv_&mAv>^k)WIocODv_srj zAO1L>Ef*OrF-LjnS&Zg2TAI!KH>&TBU!!~8p?OF5+~{+TEW}T?Me&gfQg6vW3oKIY z+)@1c$dFn;7;2%DOfB^4-qVY~E1m6NCwdr#V~ENx<^9JK7tP-}wh^b#?XyXSo_~CW zmM$+)P;pSJ>~VlDEf+PLyuCbKma=9-0l6-B(Jd&2$XiAwzvq^Nw8B*O5w%J_pXCPa z+!(zddNVa#GYfKf?vb^gMKO45(ebvR;w8)XmF-UiZX|p5zEPFVzo-)o2&#WqA9Ck= zQ|IeS-IBW~azgN@Z|twp7Dx-4S4@@mV|Q1syWb3H=bVXfqJA2{Oqmqe)4AHt(qZ;( zcjklhz0I4R(56e17w!e)XT^T0Y(V!Z_kqWk{li$*e%>FuaeRfj&G`9^Uza~8IHf&N zO@7dQefJ~vx9hJT2qYOQ+pz-gOS88mL`VOOuSmEYKmBsoFM1=`ZtQhFJd8~R8m_F)RI5g+CehWkJ{ zj5Y6JN3=0p0-N3gsPb~{NS*LAV}o(MRJLZV2Ghe5x7MB9YYN{@znilwSf{C`_d-*& zj9?{YC5=2vq8fV@DaJe8`spf> zR+@W=7DpFH>%k?;Ev@yO`wrJ7u2OyR0yhP)sgXJcm{Mw*<*R-0`D)4Qf}6Qj>hl%d zya_M$MD#TD0+*#ndx~b?=!)3X$k*WBFpPX3;arYfmfXcHGmM)5oC9Ao7A%cNJxlwk zIk+)&9jt5X237?xgFn1+7_uAj%~lx6hID{eQE$+xKlRbT{AOSy@aQx3vyig5EQA4E z2Om_H;ZV0mkF&;UNnMi42POiWCoTP|TH^du{qs)EE|i?Zv351q^KJC~5m#WK#rVHDtHdt&0`zc7ucAOJYLY z2rI-x>!@B(Uvr;W3Np2|@=5h8lX#PfnnxzyhSnyvhAqI?)$beUrelxvX^TPnCMDyg zEyh?w%2KA1PP=OR*LHi@o#FY#b367sklNcO3|Kp?Yi;IA$*SOr`uGXR(Js@<+(xEB z*OAj>DJD;jI+dCyWu05FG`LBiNm65pPk~R$kq(VolxLK8)D;cx0ceIj@HYRw@^s*dsvdiq zQ4egRc5`wX_e*Z;${y#o*8*gbd&l04oPQfw3&B{*u*kHSBV;yg4%fE=Ze}q=({Dgn zMW3)%NN#vVd;L7j>VKu|v4VzP_mH(C9mR*$1p47-ch4A$Z9t}ID^W)ytFEjG;OBgDe)5GWFWg88`Uq2D} zB>)vLH#{v-Mp^L)xd*IV#3nQ+oy5-eV*2jPiaUkZKXj&c?r_f8q~2_t>AVTMseJRh zS6P$bBtgFXn_TF<*yFgOxcs>GI3}G9P61AvgR0|4{z^s0%c&!G17ldtpF~?H)pFQB zUwl}i95Q5(kF@%6KWK%oS zWIRtbGx<~ad)KF4x6c2JTD<2a<8EIw(cnlxo>%|+={ce;wv8V#m8#DAn?K7yjj8U#?hGsbNDl9T-B>oZkiDD4us!Pd!^GN^ z;Yp|s;x91_!i~CN7u;2l_8yRdp$y7q_2#6t zI$XVXk{5A7s?|rz@3SBA$Y6(N0zYxjFZy_Tx4kz+C(7lng2GTFHhFxf>? z=^W`|BwZwDH}(S=1?l%D>5|Vn|F7B$eOVX&RX$(*yWuk(xtA|VzdDvKAdrKrt)ttQ z^+tP=C=^)N(9KXyRov3ip3lO{@imCg)86TK7cxmtaZ=G9aG?D()%u;7<#2 zQu+67{s*jonz%uv9vG@=vdTHSfLKNOp71?+AbpjUl~vNk%354oUh(hlr2nKI*t)qn ziSzS&czE!62=O_(*zgMg0090cg8YJlyrdSqu3ipq7M{Egt`Gkb@-I2^AXiHlu#+3u z(Sh~1T#MI^P&cUu4}K5y&);9q3GxL0dn5mS6mb9 z39>Vi2iud>LmER`^r?{KpZ5P#^Y0P=qo?7&djiD%PtX6T`R|^3t{@jVM|;wcZqol= znZJkq&&t0$O7j1n`G08QFE#(UOVYFSRZ0GT){Inz==n)TCQJ5G{+X`lxs`N^WI6@Z z2b%0TSX{y5$G4v;bcmEEqv{r5X1&}C%xD<8`h!U6rbpk6TU=b(m$M5Vb=`TSG}zjL z%2-{(`fc~_OnA`@IIWkKm2K#Ui0i@CN5_v2W&?ySp1=N<>>?}Kxy!O-e-{I3&ll&t zzy6=M{;qwiM3$FAdG3F@T$dxu1CU?)`ah!1#aoyChvdJDve(YVFWi1|QSM(O{?kM= zwVwR{JjUi)xt=2Z*PlBLyw2L&~R*T&J*CYO0xpc~`9C-)t zN{{lAXX%U^E^*Uih=|UyoQD|*PD&fvRA&7D>LW~%)W@sFxC6Q1pbd|)AGHu#cU;cC zlW{A>d7GWS^xdP2f!b_N3-q0seEWu^;>nGaDXf|KX`|_Fng87mtH_m4WG8mhn{Lk z-~S)G(0k6xvAt4olh3$vY=I}80a{>cm_80MY#t?~N;8#cO}(iPSh9lG)K-GSZN`Nj zn!jZDWcfd)5_t8YX2sC)*KY$WUoAB7reE%sHl)(drlZGEC_~|+h819PxN2JUP?FdsTrS4G+AY;txae>eJ#_^VxywZ$NO0A%u2@IeyQd-X=$$Oo zz8+QPZ^o!BjIZR$;1huNIl8*cLKN*yjf}@)%@Z+oUbpN9!`Ol!rTlOOoeVZSql6I3 z&>tYe6f?=pP$$w_Ynx@Dd&XVf%UTl1utu??63az|>gwa>WYG`csp+4Z`%pW7QE z-B}>|<)I?bfp6Ap7(?vAzG0I~sE-4WXmeU(xp53c$7j<-y#Ed1JCt!;o5*9lIFz;q zgXqt1KL)Skn!dQ){IgagFV2T*r^=*lCBSWk4V!`W-)*NV)as`&r?$ZI!c`QAf`D1+@t*!iI`lPJf*j#Lz6g;NM>8d6eA;86;V-!7!I z84FNq2H#WN=iySGv{;T`n}Idi<0|3spS1;3BLH8MhxXpZu)@>x=#(?-1>#;WvAm9Y zHX?q%XjaT))4pdD8B(Ky_oP4v@fp&ocNLxUL9V?FWwNC<>S#P%2syoiQV@X!_&HD5Arn7 zDD`w?CDhDoWH2h$+}~`T%J^8=G26s_Ggs_39bNg)im;}rtGsFbNn8H5#AYdLXY^9H zxq&xIB#vISCP|5AqwMJQT&Zww*4>KJwOWNnwGxFBYn1J$548>4I`ND3$Pu&5S^o^% zb&dTIa`nRr{d#vEUEL2sjb|OAj>t-QQwOfNJ77FJWp6o|xU%YeA^rIY zjK+>0x=l=oi-7MPsm8zK%5x!Y=zAL*HesjK(JoEs1_|u)nu&y|k(qf-IxKAgi+GYmQ?z11juTDy0Yc!jN{?TSL z0Wo*$ucIuQKhW4mCcqapT+GkBAsZIEwVdp+y_LaYFn#247J~kI^=@XfTN%sQq1V@} zvGr6nKuM=`E!w-WF7kEIm}d(0i@^i$Ll5`eED6fxZ(w~;0NQI6=Pv{qt^nn_FdHQL zw0QSUo-XCowF5n8#Xpspmp`;GrcMLtDi5>eMq)^&H+;H{Q(4N+lVVr+HBGhW0vtT@ z6jBC1v~hNV=```3ELw}{4E<1_*wHM{Da0)KDxe41E;#OqFCOIo%7h!iT)i_F6S`=6 z_H?f9kPHtrAg&;tmVzlY3)!EMhTEk1j(ajDkDUeUUXsMU^eJhKV?k~{%K*tXcxrX} zCkvt0?i?noCCjiaJE1J-{O*CwqOaY^gR!)B-LK7IGX1EdJsbOzY#?n`x&q%CAms>B zQ^s^)VGc~yreb_)C2e`%CWUr3tHX|id?psHAvjfgS(4Q6RNa!Rx0&sjYJf6Mj~dGx zq%-dvR5gkLd)D4xtIRFPtj1W>7Bg*xGD$|LJO>?E4PAGP2LB3Ea2^!=P^=a?xmaEc zI#R>gxy(jv3MqJc9CNs%lg{?Y<*8RIkzya|#ID0Qd0FlW5GWd#<0P(D`MwzU5li#F zMe+B#QU-qG>_e|Q0mB|V(5Yz$Nqy z9_cKvZSJ)n$e;A*3ag|v6byg?oHJSn7u$u~UHY;b-#xq2suCElU2HV}T-@=F5ZCjI zR39WIkB!8z60GYZR-8WrisP;>@ZG}bOg21rS$J$%SEQW^;&meMC1H%!#L2_s(8Nx~ zHtBmy40t6@jBM)(%&0IhMaUZ$n z3s!%5xAK>Ntv#IgX8Dt1?U}sD75Y`NuEI$Fxu6Nv!+Mi+Q`)suXe=mLJpJXZ1huQL z1Zb^CSCc={RG&1gChCZA6QsKfH3edJSL)&GHJ7-1m4WERWxi(9x%dpFvC4kNEW@j= zd|n3f6C~Hb`>JCL7?OCD=~vEaXrX`l+WRDuL44jL9Tqm+O6(eaZlRxN_0@HC5AU@w z1JD~%Q1A8;L%ZC%_m5?)R1cbJ^E`2IMed34bQqfB;e|#QQl6=s3AdZHmOz73sCCfL8Q+*KPTF+Jd+GRwMB3I3Ll@l)U zx~yT@Lp(;V`pCVpU${8!QGEv$tiWzbp8cdDU)R8I;E^C^v???#I;oNKI}<-^iXn#U!EO&a4!)=J2_EP^u3bFffSV%{dp8pW_Sf@OLEXC4W|o48%oSMZ zQbW%-)1E73*wbUxt_7&%?=zR{>&rhLoMz6IIni{jD+Sr&%fm1mg0BKgG!4u(Z9N@~ zOU~?6N^R@z8Vn??gHshEOoSR|jZLuaQSziOlp*BPYC@s0|-F{?m*IfVvE*%SFB6j56AS5BCN zOjBr&&6*Sn!~?=aanoxv#MwjEFQeSMjP+!0cwGz#IA(Q} zav#IiTf>B>AM{)Rh=;}A5|jEi360##t(fB`ZL{;nd;rGycMNQdwBFEOGM_#`eXI>J z>xVbU26-B=UKZe;~Z1lufe_DA;cEfn$M2ly8TL5aZhA4?S5^<68@&aGps5^T|ZhHd_pP zo%Wn}edMSny0Aw{*d(KLTQrZKrcKP>Ka=nUS<~p=%%xqg+eDEbcOQ`1^Jv^*Ik5a} zmN~6?vFW8b0|3(CMa@2rw}&cQr3eSP;NTwEB!^e5>LY;(ohwL~&GfbzT;dIVKvE5| zCgg(*3qXa>v4!!`Nl7upI&= z-;GJ_o$BCe%Fqo9Q4O8{@z}hu;OOzx7V}J#l~Na8=fk*+bR)-dG(&!1kXWbR;z^gB zw+wM5#=!68h-|b6>yCK2OMs^*@$ok-7S+&8|8ki(Zs(wsRy zofo_}*nHNad(k2BlQhLEavrz|L?;6+2EBzhKSEzR0+ibRP?wWL)Or&lT*{^M7 zP&!|A)%CtQ?5^Scp*zh*`UuvuHJint0mXijiqW^AUMR;WZZSzh4WHDl9Eq~{B=`;N z%uiFD-n^p_xj4kx`ztQP{ccmaTQ?{k)^ISW^_7ls@7K2hNkU|SYV{*FreJ_)8Tps8 zqn*sJSyk&&FpWsl-27$_R|`4QKT3Ygaz0cmwGnB`KXg$IQ84cPm}#)LYgO)Un4UM{ zrJOS#qo>@Q@rXW^u~nLU-nY1}Y;p==%eeBzi8Ps3nJu>zfBoQyOc_8e)jTtofffrz zqfzVQr8tFqXFW1JWrW&86nb~TY2>ooO)M_7n6JUMJ^{i$HP?DQ7T@s*3& zNk?3Spn@8d%l86%ykIE#m+VA3Te1fR1FPLC%$*HiBPyz$dw8uDOfe52*#*$9Qbyr{ z;>V0*VS+wr*3XGZD{hh1<}_Z0nP2rFBsLUboch2?i{$3sXFW$4f$F%O7v^y zqz80uy?6MVp%5hMT7W2T3u~_or zsR8tehVe3Q<39?V6Ji+HbH97}-efM6=%kr zb#JmSn9BLjL6P<53hm0}lBn%EF4wXh?H<6zBFLv@VK<;Qq46Z#1T>MMumtw26zA2u zZ;4l{P{Z|_sNE_GDVGna5{8ZQ86R{4aV$B$M6D5TFDbJR_ zNx_RHYM=0_YWt|?aeYU}R1-HtD{K|b2=^#sVi294z}_Hmff-1ho5E?-OgJuMdBaX* z;9Pp5^fxLpI&DWlQ`k+wc2WO~&mJ}UKGUr^_fN(l8sWPS(r*f=y)8)>e{T((cW&aE zgC7j4B)ln4oO8`%ZBo(U9ST-xRX_T3!ZD^A<`K7+)q)n%^nnHSK}6;)R; zEZe-!uGw6?B2;T7O_Yo(?me_0{!e#VFJP;PLx^DeR)XX6G z$1#XfCw{>R-xR&$Dkb*D{bm2o)l&SoOF9q2t4O}QWK>@aMX}cOb)=}<0MtX7vy!>b ztOf^g@cAr|G4&pSm(>x*OR>M}2x;t#81Z2eSg&TA-x7l0o{|fy2U@&HX0}URoPWR*TlyMD7D!CE_Twtuhb67hixnsK{+GBQ_u`=0!;b}{N|dp zXkfRS%gVCO>rHnV5A30LiI1^1A`|Drx{VjQr}`N|>uUSoKBx~kniLkesqmJ7UUcmd zJoXYhkIcgljo^EX3TpgG-`n5yMdP>2i3yBhJ&)y9Ah+S#oyzmFX(!KlJHA=nVx1VOSx+}0guO`bf;)*-1 z&?0CKvvF;At6_IGPSsbTxK3olR^PKg*Z4*+HiP_0nu%8)K<8PTvU~o1$5SAk7k$nI zU5p~hob@lt4ZZ)39S2(7B5lBuK1-%PUL#~apkA^#FJMbm5Vi^)upYN1)x09vH=-Xx zJvfDwtb7bKWvV-O;Zdq=j(9?AzX_o%2bQRiRN?tKp|rv#Rf-4L$0g0i%P|OSH#lBe zsSZP!-#qXb1C`o0w*pk~7`gULQ7_H`{p{NMp{<})F1jT*-pInl7FKK0nV^#P2T+&o zdqe`3dGN|wK2nOmOzW2!bbL!o6O6D1!-``SDGzl}XvOq2PP=gTG+G6V{&)@T$lthx zf>v^zc4ltkLuEkWVpTT=Q>`GxEuQAG#9&EfS$`3+uaz)~K|BWbYgPCDYNK(e5UPEO zB4Klxy9a_gt*;3F`6)92{s+lj7u?y69=1iug!*3*9L+K8OtY$ta;@Cj8U|}~jLJ{S z=<7&B#(j|a)!e&(DNy(PCGNi8$k|=yJ-0XA3Cu31sDPg2oZe^hEU(U5b^h?;2i_A?ACG5{JTkMLE_x%t>RM@u=JlfB-?B#%rlM zww+ahbw$loebeD&=4)&*M8UxJ__)jcD+SsInB`##VH&vBSnR5t+!t}mhRv{G@bk?1 zM8dX8Q%KtuHbKNNXMqoqyypJwV=O7Q)d$KB6T+P(E70~I`SsJ!#&6RrAt;b#JZo5(Fmm^A- zZvPTqe#h579q*V4-^~o!9peB^O|f1q@I_#kK+O zZ4?(xAN`eO8->Z%7^I49GuH5L!sA-2Fe(9M3w?3M$LgPQBm!w@0%}gfV;Eu_N@+$ z3i*1s&f|Gipjqfb=OQ|7wO3k$&=S&^VGA!0{~#pMzUN1Q;I->H^6KWatGC$?)M8uS z8>=wU1DurcCr_C(r!cp!qd6(za9D+ZIA7v7%!0@p{CfVh9#X?Q9@aR@srB_eUFetG zOFH&#$DEvnU5EhKwG7eTgH8?HaLc+lwliIO>sJumKV4gC^`_I@ha>p=Tbj26Nxl?s z1ij5)Zz1RPf<5K!^43#E47CfHPFVJCr$tQX?9=sMzv_z$ z{uCvxA)}#VXmLToK%*N4Bo&loUBB{|`KA6|ZVsLo>7TX2S8LYPFNwfVc5GTRsIj}v zgXWZ64%!40&Ac$TEr{jhC)JnvnWPl!qb)=4bK(e2-5gKlMeQXXGV72NA=k|>J7{HU#}VPGMv+09RVtZ z1Ra=>{ZR@T9sK#9P$Be>QU-b!ltdNa-&gIU&eP(lOrW1skwq6cl#;I&tNN585rzE+ z%AJaZkUWvJ;AFx_eu0#qid`gM8<{>K-4y(S#-r4%0xIKJzF|5PD!4Q%{U!ME)`0(+ zvDJ-gp6SGN{>tapReH5?q7uE;tJ;Mhm)&;vPF6RY9g)&3Vgtp-q<0&=T%OU;Sp^Hu zi?eA7=tw+?6?b~j6KCSyGSWYF=m;>^>DDm$S@;R%FwSYV7b>|&N|HY4so6I6gU$Hr z&Q;o7Fd21pU}!js7dicX@TIMaJeT|{C^Y+ReEP$-AMPt3?n(}+Pp)hf$bs8aMhL}H=En8WT3l*#dx+qvz8TXfIQgdICd##RM*~oGSKK88C&$=d36-T@7?U4-Qwdu7L4t=g^EXU4kV?P$2b|%2>+jS|t z1b;c+x^Y+4YHnpv)M|X=l_rMh;J`w>r4E=7x6a(C`uLe&yKZ1&=BbavnNPEQe6@yi zVQ6nQ|HOi>kL^I3O9Ms6gUHS=w=ey)fj=w#4WFM0&2MX@*H+u?HDj41dq>t7uK{d> znwxBXga@1??XLU9+D`!{eLR${uUzr8-4_xK@G6pfLgz>V0LNR24Wll*1*Jwu0V_@| zQdMYPR3{F8PK-ER(+N1c9Jda{VJSF~5qBjqu%6;Lsa`(vYk;Xyk!Lw25IX6mAysbbj7X(?9D zd-!skX2O9azIy4K8coeP89zJixBgCHqp=ApI^CFR2TT4>W~enwDXc@hi|IMJd_``J zm>3mFr-d2JUhfYBOFEd^xZSHW44yBley{2Dd(9v*vXwdXrI9_lqDIHcL}CGFzwORs z(x_O9d!l5aLZ;XoF{xsDf%R;BmOQQAGqTTEu+z;R>4dQ2C5UaKc<$}IZmlkb&fhlv`dkRoZ5{O=6>y!msps zHifcc^Kv5<{Gqw)sV+tNb(-0)*D{ay6pl#A(P4daDXe{RLe29HUq6Oz0SB{riT?N< z+?60zp$`W?I^$X@%JZWt!eI}K3ToEA3MbWzl-7TweQ#+_STCz=Vb{?wHR@NQuY)X8 z!cAKi_l7=g%V*d(bhJcO^@(w1pnd|0OSp{Ar4yWAod()Wt>>W&9p|Kn|3L+^Rut_T zv}w+=UDjpi&QFw#R`LThcYm*v2iuZOa}Z08thSQ&CikBu4!FK@=yf3fD!8z1Ms$$g zn%{9;8AA_LJ`|gn+P-{uK2I{ut%mk>7Iub^Fu1Hu$KlJPH-v)Mbmr*ysAdl^3S$yd z=2j!DKx|>IQ!|kqPI1>DwlHuHATw*#>Zgq}?kwUn_#M!TIBtdf3Fy1;YYr*9&a*!H8_oYWL+r8Mc{W6Ty)M&7 z?>|u1ziZygj^2<3mx=Gq{SRyJ3dh?a*amsF=YLbcG@-vKdXZ9q|K`ETwq3~*=4c9h zeD}{L{tr>{++|%B5|gmVQltOBWkoxkf82M)ku~fu{OCWb^++7X`sJ}Wq5qck2u7yt z+Q#(pFC6**pUU6l%>OU0N`-e@kJ2WqCg;US?A$9R$z2E&z{f3b&Ok54ZuB#Jcd4$v zhD5^pK9KpE6l~@fg*|V%bp5G&T+m7ncxsH~Rq(LVsR#et1b%#wMJaFpj@->qlPFPe zb6_dfm$%rYF_PD?<%i;#RHrDf-9;MTgN+JZNoHW^G z_z5pj`apXd*N~K#L$}sOa+`60xtO0ZXD4+4 zdzT`{89~b1B=%X2A8xA4(afsY-z>rG!MAt+p@}eAePx9Bd$e6ZnMm32Px>;$723ij zQNIoz2qvq3wV~g=))HU3I-R86$JOrpz<$>Hd*Vq|Z%f9enKYbX7bc=lV%>_pvBa6U z^S)_Fh#A{UM^=fLDoGRwn~&qK5pWhlX(X0~n(>oSXP|>UWA)ZJl!D%YZ@SN+Uj3U@I@z_qo zpZL1o@{}fT5V5LQ>s>ltyM;7Ap8hdl+QX9af{JpY9P1$Z8b#yY^(75r*vH+{z`}$? z(C?-i;n`WZ6u{JI+Y@?%F^M5t`gM61C*-ZQ|D?4z`pe5S7Hb-6pN6X{vZ#OUFtp07 zIYSdYoB{?8L=Tghc$6#2+fwX1DKyPU4Rn|lhtP4Rj>0NlDL;r`ATc~=sII@LFIme@ zG3M+g1&u`~_{OAeJ-B|UF=~kO+%s^6qF*C4sb`g;b9zpUzZ#$tKz+AXZQKpXJz4(dAgPTKmu0d zPL4kdxvoODr;k?G%1_Ne2L=l1#c~-{88LdK4G?O}+kN>jPU}aG=)oIf0F!j9aDBeY z93X&O!9u^P^>h!SrHaVQ?V6KNwmJ_h9dTahz=YBI_a!Tasz%Q~N^gUe-Tk#QG=C$K zY1d?)U*4Ti6=YMyq@SFSPdkaZpSNFY#)mj8M%PeE*GVf_pa4_7$CHQ6euafOo`a(E zHU-dQQhK9_prB6~{Gx208qYQ_6IWnAl88~}v%lk!&RuRd+$EY}ep*Xq;$^PXd;FCq z?6`OSRO`GmQNwBmaB;ynk~wxSdqHPS6`$+Fw?E&LhjR%n-VvHF$1Cs+M3`<935x*> zv6*W%cI#ptDh9Qt%Z3p?0%n-Tn#GyfANwg~>c8UX#3Bu(h!Kka;`Kh}3$*>T86`<= z7~laRk+E@-`{#zQK}5IO&JnS@nUfH6^)q(1!+C552nu`cyQFj`M3b;&yuO!al=gwQ z51Q8G>JJp1$~fL@-77w^=CF6K69SarQ4PcP}nG+e*1Vfj%n7|Sk9M^pTe0-Edx;XB)A!cz$pSV#My7n z^~f~Tz|J3xnL8kolv-y|F0FZwGjfYN-)?5^52^|gLmcbMq2SuY(lv2uN*L$7jZRAy ziJ;iTqdt)&lD>rRibRw1qPdw~?RS)#AKMPSYP9^(?hNnvAZD5qD3RE3`gHj{iCAn% zVh`gYcEbAuN9GD11IKzh5K_0MGwgkGzZo46l#kG8je2O05+;Ueb$$HlxT?hbH%(c- z`8^G(+#~ZQDAqpxdHEfKt`auam5VmYYvEg^8H?{Ir(5?8j}f8>3LbYF#g_*jIw zq7pd_(ewmQbaPqZ6-st2;-PuXK)P#nRskOk0B>PD&~lP$;)Sk=ym#7tr`EP3_LZ&# zHm^RVkvh_^G@#z;tP?5$v-va;8%07{moCj~N1A2`*-yOv>Q^?|**rN_b~p?`<$2je zA;$M!h~2);CXUZ&L3{Sx;v=CQ=c&1pGQwk1*1WdbXdK5#gwY-UsTzVF5VJEIeBtg!+s;&9=9Ia+Irs@7zAv(Z z%EZIw?RAL&Mr|K%&V^Hss}c%{o4!@|GwdH)7S#TxQ96RIsqMq%l0nJUWj@ zWoPBtKW}A(tZ|-E_sVOr9-iTLI|Ya}llUcWWnoKZ*t6Jo4~A=q9KLU8t}(qHX6W03 zMVxr8%6LKGoliL|Jpi3rCpbG|awf-EPtN3KtRDU=1JpFqHNz@ofEgU+zuk1Qh2CFgjQtl7Jykp&Xey>QHO)45zC{ zwl*JH-~?NTsd^+bqj3tG6rVRdM)gSeB@}fucvn$XxqDT6Cdit&MJN-AX!|H?G3e_z&O2uWrKX%D; zvu$76dDB?Quygu;pe;n(zjd0qy)X`53#@zt!Z>q=FKg~WFECR8;(R!>50b%nZ3oaA zav;jJ6N8qFN%@b(Ox2a}tC{2>#vOeBUEHfz)yh=tX3Y>Eb8+FUL?tFO*0 zOWLlL?ntRA-y**{_gFbdOxvn#e;88>PP^;CvJwTv>!&XUrN0XxaT>>HT)%sR^jLCP zRkT1JTwffm&z!0C$vbu5i$om4BU@`hj zoP76wdYP-a4rt`=mR1n4|vQOA2-^RE34%(f4?=PGtSWXe&x$r9S4cy7&4JMM_Ht!4UBsNF? z_r5e+s9E}~JbrQAp)+4Eh<@)dnJB5C1W6+0pjaD0c$L)^kMgY{Ek zHAkt;)71=D?Ya(qf7TIH)%XTm5~N$E^K1m6hBngNF+n@THGEn*g9&o5xgu(ryypQH z!*X^*ZH1yER2lo)FHV)^(BB5v8WP%DX(n7}ZiyLY;foW|I|Q#>{H=$J8qd%vv`rzc zUZ@riAfcwoGP5jTZ{~Ed%&#UU7u@{aL9-?SMd^eVM?)lbc2Jg7on(Ft+qK+~sEg>4@f-)Yw?J55dRdt`|t z3z1I;)F>F1?eB{^>ML81yD%me&uKe&`J%r>ptLBYJXCJ%*t|bJ78!n6d>-n_b~qBT zjyS^2l54ti!G-bTiN*Ejf`lkcm&wJfeZVUB*QUj5wcm?ec1r0%Cq;44RNCyFzzLE> zUY%5teW#IPn9l%@hceLY@LCHQz;1WP1Z*SVP; zpeG-T)n!38Nx1{DKJt+CuVB<6j99iuUUl9m&o^OKx=--#xZip_K{kjz7LF5n+y!Tz z3gg`@@eHH5iVXAPIbCRP}$p^r>4$}cRoY;)*C&BI-&gw}YXlF@D06c^Or$?{W zb6eQyzQ%$})gpIDJx~X9CI8?7h@gwKdOn~RS|)LpirT%dn;IU|O1u8&%Lexgu`NGh z7y8Z0g^he(co8#me%;Y@_H3uhmTa7A-b>nbuWW2NMrOW;8!p$8bG5}1Tq}S2XkA_E zlAhAZ_)y^lX8LzrXskP|GfPajtO4lvz>xyaUQK5*z8eTT*2NqM{~X z9udf?W|~*YH+*9rs&D!nV;PQF*L5yDES`9^d2sJIr?*OZz2X%%Q6li(zszz@b=pH> zG|McM>GHB)Ge~ zh2ZY)?(P9X2p-&Ng1cMe?(QxjxN8HAe4XsQ&)w&o@BbZljQ3ynSiNe^n(|bwSXrm#60lO@(!ssV4PsvpNmF!#clwLYH3z%H$5#Zj5XKa@@__d zT3Pn$ty>*?dP1q?TFq#Dz1%GC*nHiQM{8%6YcPB$aJ1nP5d~REi$ndh z<`1R|FQ=OGBw1~l)6z<3J)D|ZKzXML?3mlNE)j5*CXdM$7-XG}`?^=8SBL312u#t? z9!YW=r!ohG1H_jK4{3L}=-aKVn`THNljQ{1bEyw@WsjRV4IsqoB@pBQaAX)u-z-+^ zX4xLOu-}cKdEBQkgYYrI>}~+Z%Y6veCdxZ}A##*_}LS&`6D22Ar;RIIbL>Rc-?NHfN5W>`84L@U- zJ<<)Xy~La;vqGo3o*hhX)$*>dJ#&06RUd%QSAdUb|mzDXqcQ=KvG(Xzg*;Cufo8BR$XUoqg&g3O$9wxhaR4jQ!(4= z|S^(8(=&V^446I%!5(-kZO2 zy{}*(^`?;J<0*v?$l--e5e(sI+v$;87_d6cAIHyYTSsX^PMxagPw*Qqc(b~yHa+t5 zIebN?ny0z7fV6xT7F$=f#i3^sH;@t)binD;{Af3%wDBYH6|`Bjf7g7Ww_T1_XrKK@ z=ZkQPb#GoF`S$?)rbz&A2y15hT=~j1Xk*?sowq?dv=A+Tcj+!>iF`wrkMVS8Tow$$ z+*%tweUVUIr;F|GnM57RD3jf&7`}SlrjGGOUBliO-{on5Qq(8L^68y{1?{7ewdI|t zl}AApDhauxZyFv)@2{|V65>a7`KeeDQ9mB=>a1wJhcT;2mm5`Abr}0H1Wc=BAEDQ@ zvRNx7-*7h7ffLG_f7+wn-jj>wm#~SaQ1|xY ze(6*7C7{96+|*t9_PSE->1E8`=DYke+%H?@@hMk<2q`^}@=%Edql{kJr z8sjLr;%*2Jr{BV7a?LcDniLAh>}>XuNu_-wh+b0Q7<0vI!P?DEkCdUFM}_so-aiPw z$Kth@efuw4AyIvA7+@HXa-wXwH)Vh|2NL{14~H_-{T9~8qnGb0vr^Ab#;@u( ziqZPf{TF1TGJ1Q%_$h1@wuZmU-lUxJ#vihOT?jST;T&6E|4F#@sk&p@OLyLz$6$Q< z(i5ywsLJO0;#mRE2HjN}DR_;W)%h#bN(%9?DTML*Xa}duH@9dxO0Cjo22_}TJR^C? z(@FM;Bsy7sT1oBT8@!0wd%#n(*K_Dwmu#JE7_HY2XBtpZMl;=|-P3w*Rvd$}A-VDG z<-$2Tp8xSN_1yZi`1r~!4U>2s+Er`0yi?NKO#X?Zwv4%ud3CVzv9q{<#bc}U)2yuj zx&GC}f-O*-a`!--tGthaKu2=P&orc@QqB8;H5+e#E4)BBMe=l>neFE`1=g_# z{ktyIor;do+g2LQy1EUzE8sqk`0h5oZ{kaYuUU>uHzn{Po{vpXp>{~j{Dc)}0RYFf zvo8mYQDfU^$+-1TFMzlOr+-TJ1G^FO^kyDIu9z9_!AVrR2@xIj(sy~gH)$ck!~Qmu zqBdLX#uZDc(9I`G?y?3sx9hW`*}Pd!Ia-&lwMb5T*cikb=w20Sh1Dk8$FdncX5VwG zn(L32@t`X{9rRW%-FaZHo%4h|-pVcR1nOQ^tZ>Q@N$_MzQ?c@`u6Ye4C5rOastKXi z1Njq4nx3=fYr4+7?guXKQId6bpIJ?q3b2-wfJqY2ILh2RMRIJC?cHUr^ETD)6+?-A zAi1;pHMs=aNn<^xMT49-*i`AvUK5*31n%8`84t!xCxvK$UGDBkI?7tw@TId?bNy@gka(377I{wvF=N(E(= zXx`?#lOXOxZW9q@YZ}Y;KEm0O;_9f#&3cPH-<8p0g$cGXc&b@=tR_T4hro(bcE$IL zgwN;UNcmxXwNFjNxf#Zg-vZHu@5~nF8RE44J4pb`hvGW1SUR`v>o#-6@g5FgqgDoZ zY@H^wm0eEG+tD~)!)>>rW9k*IKIPKy7l>y=q?=ZkMWuqNR@|3!Ci6zdnd%ruForK9 zIs{8F8NlDtm4UuheOj*afEo2W_9qfreRE*$@n)POskB+6=sHetJ`9W4rA zTbh^Lt;I(`3Wtk(a6L@?NlK4Pb*HC}Wpn|6ZuhwVt;Zc2^=An8o&DY%VI(Y__)tKM zU4~ApZT>K8M~-_PQhs%tMY46?w2}CvtIG2WTKFgy-L0L3HLNvH=9d~jeNEM3<4Q#= zboW-%{o}lVvQK?o<(+Tb&OgU^QRLq*qP%-7K;D$!mPa&K3}OrwjVgBtj!=4tn?1zGyo!3CL&w++&9m}Ft*26YTlHhXh(XR@@qwMokW zPe>1kBa5Zu8uQM)**{F!Ihyfd1?jx$CP+feo@9dJR(4ROAz3)~Wu$?aE5N^FPfF8# zzTK-(*09mCF|4uDY1X#cK(%}&QFnR|r83KSZl-*pec?nPy;RarNREZqd>gv0#u7Da z_yfF(hMh7i4jP>H9_32>g@hA3~i%tbA<1+0I9YH!IbRxrI3K3aDMeh4e$ z8v!Qh9F(|tpQ=->RND(V=+zoG>R;8bjqdp`yr}_2s#rQ=M95$LWC=E*? z?9gcsP${dl-x|#G=mEw-=YchJYq41THrZ#ldW@ikOqKh3G!SQFW5st}%*BtijC!aEL*b$e+H{4sHk9hY zzRQJL(B9TM2DDdcmscqM@5ln?G)7>r!o*vy2 zE%=BYrPn5gdor32>jV!u>lfAE7drTtmFmcxVa;jytOKh1s?DS<1MI^ZZsL2`P(R-X z8z!T*E(r+P_~^78w9j8@Oi;EFj5`$^JsH z8PDZYD!{;DvKui!toM_bq6;nHyoF@qW%k_%ky;I*RUygDu56fXa~<@#qt}h6M>i=jzkw^!`(mIn|`2q><1uyhht}l zE)|K&9z4F@*x)y?53nK9$n4XBrhs={S<^8&0TjBYx48NP{i2^t)(b~&7lhY$qJbpP ztM0;wmSD87?t$PA7n;W9voep$>jYn~Vl|*&Y&`h}PhxkF-QmDgAr|=@6E(2GOm(?= z_oujI&_Mm^OPt-rIh@9$OWpkA=xYs6u&ZSL*}zO8AnE0RBz~GwMIJT5vP0uZioJ*4 zCJ@w*ewmx(_f;!_=+l*i`6Yg<4H1>>jXS#eJ&!AVGkx?Co6#}2hoelFvFy|(c17bYaTNcb{wsd5^ts&b#wahPa zd7Qx+{5;IwzURluoxWO`m|(lZYsYbu9j7(h$zwp>kr1F389`w{_m<&f_TRu!vU|bh@Nz zG`CEjv$IxtCkMI>9;4cTtju+0+~u+z9N?7#5jujC2GMB&P1z}w6nfPDF@Wa#_E%X; zXT)d39g{Rj2jt1lrr->zWP@220U35~w~7^axe3fv%cJ(0r`U3{Q2N*NMQzQQ54ZXH z@6tqONpm|+1!)b0%2+FDgeEpE1n)Z>JE058fBh0ytS#_<9yt@|m2@7KT%Xxv1oPOq zfDCzpH?I5`;bsQ+S~6nY+!p%j8|5;xc9bSe^=6hS70()*Eb=q#tv59%3UvLP_67Gh zeM#y(Wa8L`6UR7`6!duhF4}2@EfRm3992~Nc42Gs z{vg^c-v7N5M7Nhpk46D0r#op5g3+mY*&`^*na@?0ob;{z?1WT@jmz9PY{y0HNQfha ztINDH0euOg%d88|mJR9w*zfV1hCpefs>ELRNh)Q9AKNo%O1M5J*zwf(jhl|Li@~5y zhk$U#9i@l2Ty<1J$TW62u*Y>ekjD(rrfZfs74JiFHB9sn(;TP;sC=t&G zAMCGRW@f|o1aBaFB^?SDeY4Q1^7Z{gqj-%lYYN3fH^zad?pE3|541YTGiO@nu(rEO zVyY^nGm7$X+ZOkt%lbTDVclP?*jM{gMjGv({Kap|1GNH#xWFG1_rL+^1~Q?Qa$Dl> z&`s)Naty|69HgOnrCK13IC$ib+&Gpo?`OS2&x0-g&kOVK4mOHw2-)l!jqgT&w6fxj znsTOyIg5t~%fpz)l{2L8GU!->d~Y;)Bo%omRAFOr$~BMeq7sjmrwx?`MhVXB%CS)B zcFr&}JHC|Yeoz^Aq?c>f8gT(GFp~8ESBhK zg|(*(lpOOJ{2Sx{Y8A&3E3BwcQEz)6zL7)f%e5DPbUpZ;^@K!hDEfmW?mz3uKVVND zBCPmYJ(bh;9^C5`(+T6u%&&W@na@MWlD^T-THYig!G{~2lZh(z^S`?Rr1TprFdO5T zt}`4*H7IyX$?6w|x@ta8a^zD}yC{)rjoTcHG&}yW)l)EaXMyu4Nj*TbMUi^=?cUT{@d8s?^XRDTJ#_m;F<)_fT=34HF zCAs{!=|g(+y-^K%!ZV!X;6^4yVNyfm5VaaFyzmX-wdd5a%K87fW0>-d>gUA9%40a} zO+!z!WTv6}AM7~AZoU*cLwz*M)JqaUa;*R8>bC}px?mJP(=}cwrO_pO09SuvofvY5 zyYrYn{?oSZPpQk$TLjv!XOt>hybM^hugQd;zi1%j^K7AFF=kaN4vd-835L6;;CK9c zmWn(uK}c23*NJP{P=qLz8OpIT?;!+Dn_^NS4fWe zWD2HVsmH=>lS&v=GjfPfB&RBtIX`&(ljVgJiLN5TBpL?5X%@p+1O{&b_d4-gVl=)dr!e9xNwzKOTqffY=HW0#Cq7bHx zP=#ubD<`8~F7`$EKMf2y!XUE9S)UwgXe8_&E|_yP^e#~^kD4zmjs%DQ!!i9%3ME3Z z2zaf25mXBq6$U#qN0>AOGAt4XlyunVN=vqMJ?V03y?^XlBnu@1t9k!0Y_Iql?W>0Q z(jYQaq6kEER_b`k`uCf3OSH`4?)6rjNJxJ>m~axrK(#Ou>dfIX2Y&o57D$ z{kHrhH5vb)xiIBla?JB|X+aEMaLtYO8X*ru*h=0^6aj`d<1b0JiiZB7W!fRYU>jmD zYq$p^IWo5{t40QiW~X67IJdi~pDt|RUK|UmxR555MKEBt&SB=?4qG|d{hb@Kbu5P1oerBTRfKd2 z##bVJ>DaUpGyhM%43dL{h>`z`u0;fv8&_Y`J_8lP%JqP3?dIBGX@p=(zJIPXEk&SS zR$F{y3b`bl4TmZGx}qyeVeY-6)bC*t*_R-0+=2N=BN?Tzzk4{0k-sT?74gyW-68<5 zA~m6i10uuH?;odg-$RJTJecR$cILG%9|sVBM?y)0up%NFZ8O9!uqec((@^m|zug4^ zBE(xo1PFPtgzL997!>PDJ#>`45rj$qc((KoT{Cs(kQJFUWa_HFTat!vNtZyL&O!D) zDko~mlfytvUUuwo<{vH!wu2TbonH5vXBJ+vpe?UhbZ5#@|r&d1Y+h4#HNC+Sb$-*FwW;{=A5rBgk=R4id~E-2+M8~6fC#JH@(_A7oPntgcrJX{sMU;q2fiaRhB z1l+6MZkAR)+#z^0k)7%CPQ<)!(RwvK1+j=mSwvwW!hfI;QrxdzrfWqPprw{sD!laG zu(VQt<*=d}j{G-1iDP`1y6t0;*8hbF(~mJJ+h(4=4I^pjWQy=tk(0HQArF#Ey)73~ zrt~Hb*Lm)1(%%_jVXW7w?@Z!|Bq%}5PO^>WKuhVX1q1Y20{hmb@q>V@r>k|a3zJp% zL<*-{np_rZzNqy5FBOFqvKT@ABzFl&3udWz1_iR2#>smA`EnWDG!ct)bi<{UZCi6E z8P9tw1P<*xb58xEsb+}?jZ#a2gzc)XeEM!<^_=IqF)q|fuCr>T>PuW??e&Qit1QU1 z4^Fex=|TY_?BA_1w22Ox_h%H^z{d*)0XIco18{=0LHivfJ|9>SY48zXAlrp%ddf%e zS>wjE8}isYMqNv?j+gf~zFT`X^Hd+jWtBU~9_?k7mwa9OOwS4r0@Oo2A`R39-z(z5 zgy*myZF%=94WvLy;(e~G%*WEyKQ7U6#JEg!3_fIfdx!kGUn&Hw43NAi+!qhFIv01g z0`^vrU;L(aM^ocRtioczt1d4VIJ%y(4En8$^9CL19QLb=dL5E0a~cL3>bH*R2}FZi zGS$mUOs7k=Dl3~y7J$a7+>d*TIr~Kh{`ZMQ-ivQf@gK=~MWIBuaZPpVNK4H|l2)GK z2~9yl=ase!Ow{^6%wmRHYmVNieYNfGk0#m^5POvOzVqO;>g}5n;I&kAu3Tzz#zYG& z`LMYWka`*LbExpRGZ@Y3>=~Gv<2?{Ou}~)e36nA$>TyL7ojk*txgN^O+ddVjkz z?^2uPzEaMWoOoO#!&v=}K_}sfu|L^Zk8Q)3t(y^e?d-FCqYykwT=V4EDY`TvpT2w@n^#SU+vW1!KgQZhWXA=sz4hrox5Y; zC3&GI*Q0tbrgVO*8Vlggp68mIhR#BT-VNj;5AHB$2caf?=Mfn2#n#BQH-jdnqNo`;D9?wDLMj&F2-PLcQZ)5WMCJ zr`z!oM<^ahVorkXWifyeM zw>5CXX)iG%VJBvIP$t*Cv|JraESTOVF~jo8Kg(jR#e+#ByWQ_a_tb;MAFGro6pcVX z@@H?sRFM)H-|c>`am=2x-9TRyGP(sm4igF7LB&Nd+M^Bkk#ooPuo9#4Xv{fqDM7$j zUUqsXD0vH@9r^nLgX62`ix>ji?k{1tozS3u%BKShz>1oN zfe97BH?H1cVVu|1@~qEpDkmha@wE0MmO=wQd|G}!2!(qNm8#`cjU#X!g zjcIET=Z@E=((WN5KD-G%BGH_4qF5Hfbzo~F*uMLPpCy-C>lO1W&7t5fb)7}9Q1$@J z`LQK-1IL+nPc*v-X4+bAK`$Uz;NcGQ_IhSQdt(h$gUYo&3ijRVVDbu?lzBX~sjS^I zxD%R)v^etvaXk%1lFJv!rQ0VPc-5a=@3rfDhYQ_Byf>Q?dhxB|YF_s_64cj!aWuQT z-vf+9AB_aUlURp)ocWhl2CIXzxvm<2oL|Fh&?n>E{O+cYS z{oGqg;-ek6X_}|wogERqr9Jp)Xikjjl$r$48r}6p|SLJ5kHgV{e zDDVN5hh3(Fb|Ks5+H3vwW_9vrxYMDkcO9%NQ*;Sn1+(Ux`4!v8FNpeHeMj*MaQC5h z7t<8(+j5PbCm&9&T>yn7Rzo#r?nfsci&;J=+w~K`Jv~E{fu~<2x64uo4>pUl_7CM8 zk2w0S)gA#u89dUiB!e-5m;8tO+pSHFO_vWJR6O-?nk&(Gt`U6pe@H54p?gQK`VZ)3 z4b}a$!t3QbYdxZhjTO=A6>8tV&$~RrUnn#`@6NFpd}%v3)^hwYkwdgKX5zr^I{mT1?Q=xE%}dMlYPJ#diuX~u zU2WD-{VOSr%0qwK%}#V%fFDT8mSeVX(tAF8urf01%A5F}wU?noX>pOo(Hdw;C9{T%%@EC-HpPd$B!5LYs_>Wm!yT{ zbFetC5PwnP0T2>rC@uH9E5qw*hmfLlxTTzjiPUwnrK*YE$x5Abw#VoxhkHl|or30w z_zE(HvFkpI+cgiele(FS%LoB*vTE)Q3gu%BmtEmkWwypDu3?fYYwlI%qqZ zZ^|U!0}ToS_ut(MybcqJZvij-W_By}_5M_VV_uqFH+H6$M0X%} zG^lZy^4#loGl~QDE!$}6`&iLFXTTNfg^Cj*ZCuys+21=?>Lur|$!Ph7Ba= z<2+)F)2Z;;-iD?g1Z0?G9Ic9Z0NOw`V(0>kf_Ix@d^dG$$JLS~)eMb`tAg&sNeS7( zXuOlpULd`OSsLA7I6w0ne0BtLz~$4=2qrhH=RQU^%F&I0mo`w9PMu}7%yx85lUS&~ z9YI+~JCXnX`vUiQZzY;khqj9nyVj|Xx1($mUea1IrvwR^SC3ariys;*G#BD{mw^qL z7}CiOcf64H_w6@pZa4&&J8yJ+%IiwglX}ZQRyws6!WeoE#S{>{#;xmp2?$1T@H^1nqcTM-bE^H>?H06x&3rM4K~ zBcbo%pL#u93sT`ad1F#FCw~gHIc^PMvzIRW}Z*=3nwQY>(!Ya>q7zrfT}rNfoOqS%as*@ZC|ZJ zUJuYlKt~(+6x?7r_2KN^?-_Pai;oNQ@oE)V5J<(sv%d-5rc%&ybPREgYCq7*0wg>Q zT0raIRpDbp0wMn(2CbUjHl663pC?sMXzq`Q9UfQ?z#Vl07UM5ClJHO57MWgO*(TOk zBLD|Ni97CGqy^Eo0*&m}EkaL22Beo#toSaQyHd<`lVv-2r}AV_fsVD{aOYvE$FsXjR&! zb2Wtk?|wm&N1zW{bUp8Bn}4=X*5gqZd|P`r@CmGgxhE-+dE@i!730U@S~Soq8FT9X zcrmbo#%FtG5qsg%(BcjT_~>i3n&`e5@9DWSg3Fn5GnGMQK7a#Yp~VgyfvX3oAA7#Q zPKRloi7vniX;thtt!3Qx#1e0iXYX-s{@G-gGySMi_bZDKuiNoHB?mE=0AQ{$7K&Cv z{4$I#ef=smpOl(k&)}riL6?oO>{25GA z^&DOMpvGn~>UeTh%;7SRA1Aw`L1L4w5=W(wb^P#oU{O1h&u1bXc=|l;w$nlItwVll($RF*GKOjQ94Ynb%K|5Zr7(7TdtKSkUsid$G^-^2zokv)(M&_DtmF9d>`r+ zY2Ui#ozCo(1Cq^P@hfPNJiD$*;qYmAI^Ra`I&M8X1cBoX@r75%1S|l~e(&_s$I`iK ztpg#ZkGayijN>QPmIL>6Fp z);iT}dwOS!#z`Bfh-^i|rg>;xql?+Zc_l0r^-X(eo~{r*eqfmYL4Fy`oGSAut|ndE zQ&M>7wx9WEvnx1b_`GHNOQ1B?W$=(pKMLOL)uBYZW~=Aq3VAF`!PH*hEsgt=Hf0vI zPCY=!SCNa(*Ddpg*DTKEcKH6*&du=8bvVddA0fbtRzexd-wy^!sn1_&) zu_Ku>92iXzxL zv_)7uyRel@wR=@xj#>wjG!{r=&joOHS*hd=Pms+~>e?LO`~%i7QatF30)al~=da~9 zTQ{-&CpQDJgzWjXtBn(t!JK=c7uWG$QOCNH7BqYD1%0{w=399AGO^U)_vXY;6VZf&x>fJz+G*VnZl#XQTA zh&XSVPu|1w!I}ZksC=yd{A$;jE4`TX1mU^Ai+S7k4cpfJ)t&~%mXKBn0#%--V+=7F zFUXSY+?_lBj{89eDWk%J8(*FM8fU_M@5bnz$JtZ_9?0@h&^6!EceOuqUrt|K zo_LgPCQmBf@@KEq^=Zo)Ey49rJQbVDV!}Hn{i1hwYpnqvZT&KFwqVJa$G!*c4yOh; zTy;|*L==^38JDE)LjF=rIdwX}?crjcA=L`)09-v7EinPO=bMzR1xFe1P$vXHnuL*hJdklr3Dd`x7~tk5XXHHaYS}QUXtar(E28dVpM->I(D0Dl~48^+#+xo4vkV`IRn}ahft46yVyy1vFI7TjYn2qw0#>vxe!zb z)Z}V&2wkUHF7<`@{vhDxw! znB=tf2xqU~;Y@~Fe#JZJY;F1>4905lAJYKZUrZg0-5ommMSeSbncUiJDAukIXY9ZH zR=!MH4D3RtxcJ$>=ilX>W0kl#<`Hl{`F^gF0K7HYx{s}XxOC<6{IRCCt_CLX2RKTt zQ|9Ue>S0}KzKcL}fV^TdoY^+)LX2iG^f-|8ph!6%NrZJdww`=4CMQ6%&hd5MH$0WT zfSbh4J`v_<(3!xdQ%(~S*YmGmQiA*7v*f9b)d9^umVPb($sP;ZPJ`82njky9f1p$Q zpxx#25zy)~fyOl+hX-O_375TbmC+lbdFG(&l@pI28w~V={iar&83}lZ(W9Nr!S9KO zm*1z+w&_U$yrT{Lc@$aFExyq$b6Mr~bJq}rgX^$?*J`M!Kk z+2fPz6fxvg%lPCo^`S4G1CD1_zDxE`2P}=3=Bie&%W25o`BO{ElKTj|n9W!P(dkt! zv5C}GVcAmfrErMX+v!g;T72l5t_Ranuoi>gqqhS-rgPb;Pvmbuc)wix6`HhvcmMpr zk+SP{#8tvG?sL{AvnF^}v5Px-80=d0@!PD{-hBr~3?BP+BZW+YAp_J#JKO5Dle|eR zqYlaYE?DeQ17Y}LlXYLO))aFt6U|~}3NM!2LS^`hfjDCUw8O}?SrV7j?Cdz+u#;+- zZL_>-UBQ%HOxxQpPGfQ%U;DxlF)81&V+s~s2$~IkUr)(5*ppvu>6)|)bU4>t;&zzT z6g%qs^ulK4iDmp6ceGZX^`pCslL@i&v+0|ri_N!DYyRIF9-X>MI{_2A$_t1lec1}g z)#grVn-XS~pA_zlR}PTtc6$M{HvzyW4BoFA97L(+Mc86FhIxzhz-lt)vX2CxnA^@D zeLk!C1P8UMWx5J_qnmzuGYZqMtiXEHc1>trAjS{urnM(Xq9;H^>DDt>KB3n7_C;i1 z>ZWOQw8N>U;Or<@kJoM+N@}?Q5Hy$L$~%iVP1*Qs7Hz*wB!xT`(b|X9RrP9jG^bKB zGLMtrsEc%}&dH?DbWy5Rrc5k*PZ0-7Ak6)`G1_Xex9ih`&Rm0~q*oAp(CoZyjHjrX zq?Imf>7W=N)AaoT=wj~87!*+!|1Unid!KV~c$m<-SgpFS^gP0=OyekwM%gP0{j%3P zgQshutqB|vNFEP}bqdu-3%-d5??^J&BP-DsBr^>>{a<(|*xz$DG}HU_KA#Zrx+z5x zEHlfc)!8b$eB5w%bG=&ICZe{(=c+pY`bbLVV{>R%?`AS72C|!+j7v>2nyRU-6N(P`IhmAKcVS;m z>Ip^@amh0b>TgOw%QZgYTDajGNLp?r7>$)G<$kOt+LKQz+o7O;q(o#f=z@ZMN3QSl zRNaf3hLBzR{v`kE_U8EjEI+_Kx@q#BFz%GM-lkf;3rW54`ExinvVPGQuUAvZwCSQD zNM2^itl}nO@Ww2?(SL;#Pr%y8%2td=qC>Fx*Woc-^(Y*qpS4MygHyQ&+q^fky6|ZV zDK?*1XN6qed2GX6%A{RaSG`(Lw1d}cB^)(vI#_tPK9xvivrsB|gskC+`pZ8OL+&d& zetl^pKv_$=U$&|Al9+Qrs}yekWR#|I1bT`K19BDy!{tuKi)~WNB!lf2#TGoBG%$5~ z6`fBlm^QqG(SZVvl^b8%u6Jduilknjok6PJcm*H8B;F3=-X*bYm~*3 z3~h`h!Z2YyM5{*FF)3di%y?CHccG2R9?Y02qMSYgYsVT1E13*{;#SH+NtSwM1Uibe zm_3%`gN#I~?!t;)fzqy;gT+Ly0`TY$%1d7Nw8)rcPG#EAg{0e_A7&e0Ue#;os?C&7 zi%45Z_I!;|MY&q&`HC@H+WA?G;f?i0+U3qfAv;#mQ&k4lg4L`z0j8W>U|nXqv)i)~ z52eWs*<`$>WWW*L*blquFZ9x7+V{E3qP>8}D0~w6gV6}aszf+Q3SrSZu#QTvw?`BQ zPCD21?lRvHj@u~c3vGKk0LQDtZt{Swur0@+>CO}C#I48mttG&Cp)xi6T(`MTl8Sk| zwCB&>IFpK!r&yGq_vms`pFyYI$im6Oa%q|(fyU&c8*;R5()rI69p|exDw(uMJJuJ3 z#2pe_s$>e`Fc-qx?N?z1v2{I+i?YF#a%s4S=~A65=a$VLXN-D?u_Zk$lkt?67J=7y z;VyS}uSC2<`d%%myoMX}5mgYlfd zJb@j~$q6C{WE=cWSBuGZ3~BszD-;;!(I!Cxcp(>gOl*8+LDEzGas*3XY!V&-f`0f$ zZ$%ps$>-CqMZ3h*0Sl&}=L2s0Xw7f9-M0td3vx+9=Aqw_{wQgUUP0n4m_(5U2xe-y zKVP4G+AoN4!H;OA?ZjD}M*1SyOKrx%%hKc5Xrg(4X;!QYWF6)=T|9p~EHwV;<&qFy znf@Tj(r!z=_*MS$gDT7XTTvpiyKQFkK0bO=XCDHl#KR);W#HhlgbUk(K zH-x#nK42Su+fxdM?PS8c&7%rr5t&rB*%m0gBI0xRkqAV>&7%s#faMFnY_@8uk_lyK zsv*J7mus|WyJvZypac9ZI5 zp6DBuUPG781`a16UWz0BUW1NkNKvR1YKY295Z#_5k{N0Hrw`lpH0AZlU)!xVEir00 zZS;U&EgLgqcXHUKh*TXY54^D=A?(Dp>M4*rR93iMHT-;rorbm&@I04-&4%LBTJZ#Q zywYF3v7sI*IkmrV(pmU+_k5Gpxyvb@X}{i@PYBzUq{lYuDEmh?`l}f)KC76jg_1C5> zvvSF=!}L+i8MB9N$s?1qgW*ZiwJhV2%ZU;JICKHa?Rv%Ojk_T$9LG`aIDEsRO+qk* znE>{OFGS9Ah0Vvzn9;NL7ksazF>A7+!Ys1CaDJ_&B%9}6i1*{V@9x6IJu5j=Q$wL2 zg(bb=nCWDKuXd?YE~7ZFzE3K(p>Gh5>VWLO>#TQ>63tJR>kkpw*eBcDV!#=>l0o%; zRhgQeDn|MUuSF}ThaHHo?yfw=*22}+T)$Cq0;eC@EwoP_te~H3mpvlM!V#m9n}r=TkMHS`R5vBa!z^jZKj)I(^mb~ z+X}rYZ3|yX2V!bYH;8=ol>*|(ZH~MMH>wEVN-oSPQ2C5c`X}$?QrtX{_ zr4Z%2U}#7-20|I+2)Ht%8zI!5LZv}fp`9$dP=konM#G3~6)`W!Zlf7^rGh!H(@^QnQRY?Um6CtrRmp$Hrs-q79fhqmOg>DM)? ze}O*6^QD#vs;FW|hmll0Cr2$*b9|@uGvFmxK9hGQ%4VUhBr%#pKt78f5>g{t+Wy1w z>vVBF5>QFFVyq}ST8m0HYBEK91!gDZRx{?)4dp8d=%0bdhRE$Gbyl#HXC0Tnur~-O zuMICjHDjEu8Z8vexksL(P-qtNfL(^bXln3wN*O+C_!!2TwJlr3h1nz5_EP$}7Ds<1 zKF_k<+t&E5Q0wEDa2yh5!7eIj=5#KP_9OoA>RGpiij92)wqianr{~fiJ@hl{MnY7E z1yTMQ^kmvF`g^w_p`LZG$2o!v=S+SGOyS}GS1$nld}nO!=WQhICc_`vpQd#1-kH?j zo>LZanv4enG1}GIET1}Fur=Z=BF`k`I2QtEyNY9w$rTV(4Ba_H7QI?3$$2)9jwrb)ClIC@`rAvnz?j-9cdKBkbOwc{dga zI|?_Jz+?KFcpR=e3g;XHi4RJ#Mc;{jksJ{yg6kWD6I{9OBYdLqC`~fH_Ng=)KAkdY zB<`!6anPLqsdEPQ`z@nNiG4zOy_T`VpB+kW>POFpr*QanW+ZD6*2n(0_wPrdGk(aI zx>pU~Qdl>6BCDkWlLRvwWAnGE5|J7wzPw)lW}b{jbdFqxWG2z<(k~EghE1W03wJdX zPg&(L)-`O`7R~G??FbKF2~>2zKkvgC)*(JM>F%)H;%5p~LEuV?v3a9Q;j@4Sh@F zWYDNllY=cL7bM#Xy*Jo%_mol@buo6svK|b&I7?zwE4fBETPfsH^J}aLHkZ{w1;oyamIYMNl8qh!5!E0!-Z52oLl06&aq5XfFk(zWM49C@`zQ9^o3iG zEGr!}U2Jfr=D~HBa#VCw5z`OedV0!5v5m*!($_V|>OXfE@ldVKmrhd6@&I4?Eas@$ z+w&jUn zjkND*sByhNf;;lo{lIj4qP%F!c=&89TF%I4gXdWpkes9&*k!$X^9i4Xn&~7s6h+zS zPDIEfA$62kz`KOmQ*|CKNA=ZL`%X*6IOn4P7>VvoQItTRoUz$puKsWrF+IWBD3++6bjfU3akr@t11A z>=gHUsraKwMN2#J3u^zYzw6fl21UhU<&w(YZiYd*4)^m-qck>)tne6;$RU%DF?qNw zK{&bmT%z*6QDjZ!NBJ}RHh!+bKjdu$5BXi2Ds5c2-9M%ZntWB}lhkJW=tw$Wpe9ov zKPv6siy#^hW@ic_@us)y_r>rN7-j1GmV36)0xS~0#C!uqwIp`Cv6&#YPi&*^X3!F0 z>f~)H$eeS-qxowIDYBG>K@#)-l@IQtMP1v3h$2#V;CG7sD{a(eiZg4eQ)+)mC&dXNk@`<4iOn>$EWwv zGqLo_g;#LMoqZm*L}qcG5|1x)Dl_$>eTHWlZpF(m2VQC`{=?IFSA5Hb9(!^O15HKE zHQac&uK+*?r7A8U)z0Y$l(|L$5Y2tTeqH8w;&N^6r-=sFRonUVvynd3jh5|(nXiYd zC;$370_I%Rcg%KbWSzw}+!u)zK_a&%KQ7g zxXW4j@4dfwpYP56xj9!oMvv|#wQ5z>eCBUHrR$Gdu($cPwR>WZhLUBjr!~jnnT3m` zx(*M&z~o7e%aP8+y-cA_@;|2bGbgM_($%P9C+cVbQz};?}$KN?iYvnLW(iS$% zb(Ae{P3+o?z#y}S@#1{wS@%xfi0cFCRtGCo#3w4#==ymOD8C{30oW0LCNau}a z^F8LL`YkaS&*#uQ+E+2?rnYHgn9-U>t24^iDSnLbHyB`a9FtBWc(nJW7O~4}q)cI+ zmRrWM$1MBKvdgDme=l7p`|(<=(|zyp9^pXB@ybDQ@t1V~f2k$gXnhslAu1EHdt7P^ zk(SH-Tc&A+A>Z2eO5DsD;%}odpQ;>S__>KE0bI&7hm<#GG^C$|gxh{yPQ_9O(&(KV zzTw*1-Pf)%$-Q#=Jf`)wl;bq}%2!&}bOCRC?bnOZvBffRWE`BJ7oFb{#-bhJnaSRr z43Ai4o_27{9;$8NVB^ zd8bF+gG z<>(Mh3c%gZsG5=6Ix-9Ogqj3BvE$P%ziq~#4B<0EKCIcYwB$;%d5>Y~Ns}>gT;w1~tj# zY*x#g$#-oOA+EddKemfTC43f#TBiANYsw|4UE(;8aLko?TZ~evsxQZ0Hh4X*?vPxC zF&n*#z)PrMkR{$5NuDHX>wKZxuHcX-nUB}1!TBiKD5h2$Q=MYRI^13&)%;nn@w7;x zb8ov9>1gS00Iz9gnDvtYSJ8V+m*j$gFVxhisPMoCHo#NEbw5_SCiY>$E?jQ}-d+Lv z#t`>+lqBYZuQ**@FDrf3!CHH7fS*4JBC^wCx>{OXY-ps7FS?w?$K3Y;a4aPRlwO^dx zdPiKSh@Ik^*^(F78m)zUlYH$dett%aCR>m@Q8``zAnhA0U!&6NXC04lXRd^<&V{9j zeyNQOelKL~@$-iG(j_GPt1!MfpWi@i)4$v9k?u>|$Kdf}DjsQ^8|)5vB{|>hU_^g2 zI5=L>u1sw^t~;iw8;EA_&d^!nX!@(E{9YrDX&O7iyl?b+`HWRXST0mLs!79p*2p-F zxe;G9;$P)NFp&ri;+pwcTW#f2-Q0Jjs#Z{>s-+%jH%{ z8!BZgob2IM-zIEr1aS@|zNWlj$x4y9_l`xjUfd5$pw(j~mJ+~cb3_pu)q-_~0n@98TD%TwM&KTWeF0Mu*Xh;BD>W5Y; z7xaZeJ$$pPZ{khqR2<*^?>p`Adj?k`==K_%5Pj01An8$Fyslw89(9NHnhrLJxobCq zANHm_%F_9x2x5j+n?~)zeZJvcH>wK7j0<*=79EY`zb^RS*Zr@LM`P5_n?A88EE{BS zP0k=3xJ@cBKf`7ZGobyK$(5S^*JGX>p}g<#7GkJszY)csvt!{=$W z<`an)tLep%GzuZdpl|w+RXg_HYtn7i;zuQiCsRpOugjzSP^`c!52qm!+K;^Owou4d zPf6N)I9l_*-i8BdyTgp0}^wIcCOqaPw znta!GdtUgQj3<~LLs8r5=^NYB1B4-O_w(TIN=t#qD}C@ucr>zx@8Mcm|7UVhd0{nE7q9|nznq#@q{h6-JRP`?{T^= z@~rAb_uQzUjzAup1BI@u32o-j&%r#4RP34Tzw(ffSC8|!ZTOWgGpptv^O<;H`XAfr zRxer&hkKvDupJc9%is>iqFZZ_(?2Pa&z7Dpn^|ce@v0KnKY2jo5jOjnys6lB9~E(R z*AtbY*Y6A^&@&Fw*fMObCY6LAb{xs{a6a!F7fJ9cP1g&I?)0??AonUoqeVIqjXiqd{A)ta%m z9RAMo)x6Y2{9u#q@^J=EWfHegX0o}y{I$3vl=677r`9k17(x~yxO1^Lm!;`jZ*q_16Y4~#+wA^rsu|wq_QPE3iyGW zU3T&J^gd&Jtyo935AS;vY`v3oYTd<9rqHv z-I+9%zk!66F(Pti@w?;z$&|bcn(?JIK~niz zb%p6Mw}er^WSkBFoP3VFMLg7(zjSNg7fLMvFfzoudUi_`j!1)|h^zKbA# zlr+K&RzF#+Ysql<#b{S0BQX{%{Lmuo;yfuFauFAfkESPgo)jZ|Q9sZ{WnD3Ghj&@m zE>?WSQ$c(iDEcwQeV>QP3(WvN#7pv*jaapr0?-M)JAZS^jiaF``|*CS9J z9Ewv-uzpU)8v9BvQ;x%Iq{xwg5!foAO11ta&Q^!MzzeBm-~LB?Yt zVK$PP+je)U+J>B6a&M32Fcj2WXFexU`X!H2VqsA^zn5`3396WtCzAvPJ!SCNDQo4} zinWtU*nzwy1vOeODR!^J62J$!wn3cLYw3s{TOlq> z(5BZGQl`+cD5r3u=T3t@3>MT_^=~LU5oOl?;aLBkES<~h#q@2-x6ZqS9(f8Ild)G; zGd9#K`TH@Kf{RsNi;SVyw)^EXwq&ru zKfLKdI;YuTV2vhcWiDEy=(uh&9oD}12Jbe@XS8Z|--uUw*?Te77zBeiegv{;L({J0 zcC1(;+EZ{g^Vx&HK6Qjqp7303riiJLdJkSp7}}MgonZ@7?z57oJXC)QUe7?>g?+k$ zF^-)Fw&TI5_P*9MJ0B{{$d{e;ymWn_TMM4yiMSLcvpk*FyKO(&nd+tcMndLuSfuD3 z56y*`{2`~%@rcQoB@WHwzo!ws7?Y;^YLV;c;z12YVfm`MQ>=0W+XBL0T>l<0y zC63p~^Am6;-Vr78*e!dTtob`JP{AoUAscJmE*_C}*XDnk-6TVc*hJ+SIXw3f;5e$( z-V_`axVeKdlArm-sy^YHXJa{G3>}U`H+==O_xLY(qXjBa@1Jeed=2$r->PxvWdKveX+%L>?Cd*@K<8(nQWZ57`Mnr{7-k-fhB`$*oJ=R+l zbBrScR4K~ybrv9U%n@Mv%g<=Eb=0F$J(&Ie#;`LeXgWhaqe1D$akE5gvZ(H<1CEr# zlnKxb;yi2l-bu~S8HZp_RY>FM9fry1zo7hZLB_rH^AeqaQS+;__4Ds9Q7nIj8(bxA~7995(ZaBoohjS;Jna#_lptoNGL{}@T+ zv`_)rzCEfK(dQk#u#xN6qPU&?zJ5+r{@XBVRIYu`p8hlKc%dwhXYs_sRHc;$pZjr6 zI`49`ONK`pYxP4nBCMCHy*RN{2Gjdk%d7m|u|V z?wli?-Gi2(FMCuobDJC{aW<;03a3knu#iP|ljKg0|6E{cjrqPyz@79pC*bJD5e z)N$jnF~hDMEWKC~LCYYM1-fs#3!-9hhg!vY2%gtFG}9&OQ7VNB!x7?l*KIN(8$tbR zy)JvoM~gp7L4$<&bP|{#NP%>G$(`-|uhQph-BDtoay4ZerQ8ol11Z}s;S>zf1JGj#+lyM+WZK7rj)R zp6A4TPculQT#ii)VX7HXcUwBsG4&SH@s72e4*>a91}oId;*)tGCwYG%5AKv>u(d~h zX}2^we{qEqyjyRjfu^v?x$3!D2C6gJCoJnqovzYI;kI3r0#wz4)Ga=mbZUQZCv_9i zV28;q`KcUVpEA&y*iqwewlm&fTn6bOLl;mz{7+84t1E?dGD7Q0&bZ^Kl-DQ1M?86B zIu-5hhs8FD9A*k-fVucm`roN?{&CIyCN!#d!zFkY_!WA&fsHAGPvyugRB`WUbQ%U-u|{E zV9S$-`{WhqdAXxt_M_EevbfyN@bs4*+AG5LoqZg-Y2rU>bcMdN3U#z1c0bi^RCYV@#uJOJsm{Nx%1T+Cyxq{yI* z$+k6qbGmsFw2L=1=>SXbhdV%679(E0-V#-KP|fl21>TMcSg~($gUx~x?$JI_Fss<& z3R7_kG!n0@7ouJg_TFLj88TL@DUder9so_XNh*euaJ&L}-Z4LzuX=j>J9Rm7M1L?8 z>p+v#DbgYmbUl~ivR`X{iM1vk-X@yBpqQI0hFtnuDU7pB94KE55h~LS6+EXnXC40f zwf|>2=xtNJRO!Sjie&^xk>ceilq5z`$U}pUbfKU>4TEtLB?&uXdGXSAOsO!38J9;u zzD?_6ughBkwPe@Y=6cig@8MA+=6i`@l+&kVJho#^C+pLpI<4+(+AY3Q0F>lowk$0Jo+(pD^N7%n=?0(+^)o!7Hn;cSeQ`vmNC0!5z3g7FpdR|d zeU~vgryn&YyAf~?YNrFR)=G7BwlDb!zZ00-I&ub^oVMTja9Q)}wmOH^KyJ}FY(4NN z$<>+@6*EkEPI{u(+*UWO%ZN+Dll*FF57)x5Q%$ARezJDGs_z8=dvdwUXiENp2$ouJ zcAy!;Zp}@t*VvPDfN{0l|8xHuY^Mv$s)V(is@QqKcP$tp<^VddPnJ_EITYk{I6u;y=0Ig%|`K&luJ z=s-#fzbmIcBN7onJtxVTr(A+hH%t5=%V2vcGKk3=vQ)mM5Cxlt;yhy+k=qL}wIUoki8IZ|!V#HnvYQKgmJCTb_!A*3CTx-jW%bWI z(U=?JSdqPSl9(EYq31{*-|I{(=ND6aC8>^QS0jcVCV_&XJaTTn!n368s^d-JZ)(Jt z6!(QFR;dyA@3a?XPXVqUpqgv;sG=Nb_cFv=(ia9bznv^b<}Nt=E>*;IKH&H*pUv+R z`k1_F8d*ao=F*ng&gQp8s>|n==T@$!u4OiyF8*HhSHN!o0CRD$WTWtjedTwDJ6kNq zOqP!3p*Onjk&;!%cb~?w)4ZF1^T6(x59GWG-ZLEn%EC7@p=hKxOxI=usmf;SSL4}_ z{i#2eT9DtI0l+j`gfnQlv>?8FrJXpvlGn)AOgxoNE`zV%sa*5BQJwP~R$azT`d6cL zi~{Ko!jyty$1j=jxe>q69iNLUdoPWxO1l^8D&pfLQ~!*xJYMW&jFbqFTv}(6N2c-+Zw=tMn@Kc_}r<-9=J!eFEW;AM^S)}A|JbFQw)cdCYz0Z(lrz6$PruK^l0UkHa8Wx4CF!1>8ou0g4!bA#d7ZgdSNsa)3098DVQ zJfA@ghV*_AT7vmXN?_7$&InJev69l8s?VOHD|^$uj0a!8#E>(ZmB1cir(JC(*OElP z5baH8@1_sa#kkm1P&X(*&Wys0vPQvwnE&CpWvrGGw`ao^kcMp$Op) zGEI%T`C`~RV755`b0;=k_wk#58Ox0db6e7MJNq$tXBLe$BvNnbCnx;a6hN8&Qfce@ zmNddnf)RhBHbeJ`BUMF;UPZwOmta&W zh}~;pIWeMs%>>P4)6d-I;C<}!(yjT3#^?7K5Kc%a7w12DU4)L-6*Ecf@P(^sjK?B` zMBI=m6nWwuc!&fICZePyZH%472vV_V6rm@ztnkNv*%S+goJg?!fs0mVo<;ZqKyKr3 zgJvA9o-rcll9C3~KnMecAgRFRHd`x}CVsGlP7eM!)EYRF1_+}dMzTl2XD#9Og)))f zpd;y@g?S>H<89AbEdd$)En)(gFDHYrb^8G(uHadRme3v~)1_P^SQp-jM&pL~G8qv_` z$><*OHhG4vEm(&2vVDK7G{VT1?OBm@LUJ0bosRnF00Lz?Lu>>SBakJXVZB0v>6Utx zR{l$5K5|J`$ZX3n(VtepcH6!5D0v;DDgRr_VxoXeuk1{y%}W_5SD1Zqpp(w-LTVUX zsoHxPK8{H+M?+1|Q6v%Gzt$yrI2RK-g698IcSAV7}^~u37U;%;25+9Z&oZ ziWd}o1qYZ5)t~32%oAFK4$_zEmf+O;vCz@kO?ThVU1 z{=VE1J2{ZcF%o}r%nT{Kbm3%J)0P@o&Hb{29Okl{3c8(VYG^SYQ#&3N}b z>V{)xtO(SnGisvm`KJ7x3`M?p#F5KG!7Z-e7N#D-21c?iJU4RoBxO`4Mqp=%Rup@SpQGia__|1u*z5_eI}#h$bOlwwCys8?`>4v` zO&%M7e;m**QAw8z$glV{ufw==#Irkq_R#N*PRJLH6!{8%U zD1$_RZVJKNkk1>f@C`=kK;(;A?$RWXW458iwFye)AA}dZ+w5a|F2A5LdC3IlZ2{c$ zO%oxr{&$)8WPwpfzlX^lE(0^-%FdB)<9GW1dpw+J&7wo(!w<`UR zdiOK1U5(r7G4VOsdmwl1tUPy~))fVWN%t0##_j6c?v0127;>q7WT1+JwRLbtL)VA+ zX(w8r8tfL+l2x5JVH+b*NCr3RsN2#WiF4}YUGX|ejFK`Lk89mT013~dPNCk55Uuii z()GQwh$6v-s@nG`uU!Vz_I8Sf_niE$*9Vd~-5v$B8+`?n-sou0Jr+w-v^L=+xA&uk zR&N|mK@q-u?NEcWI&r!gpr1%xy#!HuiQKlm*vL>N(Y*h-F0?>|6i_O>zK}2ZGaa?r zo8L=}qSxJ-l&(6~@*E|1QV-LCkT~P;kW>7Gtt6R6KY1YOZN6M_$aAV(>xAvmjQk-# zXYCeaBd~kG`PJu0ucJ}nH$KeH4H|gL_L3f&QF|DWWf>syfwuhHJ<@VJKy@08OZNLI^ znwQVV_;H9U@8kFMQKNZV`Nwnm-|w5SrhTla&v=0z>a>YvIhCV)z))a(cZmoj(D(hB z8z~;FjN(_%`sDo2|MbyQ7_^Edi3t?)kRE9YW_Sy#(2N^6(~R!sdmE6z!7CN)2NuWJ z@^DAtMT#(o&!4IPSRG>eG37#k1iCkP(TPw#<{3=P!y4P+ioorelhqOcs^6O_FDEt) zs{GvL#^Zdp#caZ$2^3TcU~;Iv9>%QAXsuW?*d%|uR?YB5H-aUgnL8Ds($nAly+_US z`6h>2KIB_GAU#zbx-(5Ti?;@YWY-K0l)XsCVTNCg!jB*}8A!aG4fBRLr)kJxlID=T z3Psn8yY93RaN_#}A*Bt|ZS#zpwppmA0Lm*a1m%x^hq)aVrsz$GCQBAOuTDoN2WP;4 zk4H6o+XL&yr=Kd!Xb2=-tKxUn^h^ytBb3@$ZSB2&+zYt3z++-%&8?LzKX2RxLFEm%g@>hj*g*Q`XO}ojX{_^FIRgTaJ|6f*b0Kw3Nz$xBfxp# znD%irSV6;9ezGVmHgTpk`gSrhwW0>r1ed%UN#lqq9Hh74_u9J)q35&hj@-KN zPP3YK3cV_$)w|mQU|{2$jL4{`-YaCzI)|X_B3K)jwc#3#Aq9pUCK_l2U=cKU*?Y5_ z9~+oJj@f(tqqtlBK5!vWG_nLd=s@7~mENm|2F}mjU=Uf1EdF5h48h!GO3<4A$(|=p zJ=L?)Ppb0tvZ51tw91sLW@tz)aOLC&jhAc424xwYsUz8F=8G9Lyq8MRb1Q~|#yyct zaIecUKU45|2j$s&%%=r_w4E6RJug?e%gUy2ZLY4%2MHc_merus zy^Ofl)AqeAJxz}$82_X7t@mVWek*FWXkoOj!gNBl=11sN>7SJ#Pi;RP7?c@MjmaXq zSUD^Krk8Sc8(`up60r2^TONG))1!0MgP+7(`7o1=ONzTjzbv&xSEdhWxJTH~dh8cr z+g-a7hinVF@BX62nd&fN?SkWH}4c>TVsPP10@>o56jT z(smbs?5NFa{L=vZ-pO>NxJfd2SRPuyj<&+UYJFPtE;*qv;Almzam@tI)ubHoZ=Q&N8EJv>Z;IMD=EkgkS-W; zZ>is&az%H1*XdQrnr^THqQi&>_)o<+_lr4LR;_kPNer0AoZdk>!a*~OaQc|%q;xek zFHt#zw_mgL_{C9Nyi~#1I=)%KY8##?0IYP>O#n-fVEN#+8kEIpnWc$$ROOG#EOm2* zlUMgX5NAHWXa%3vs@BZgUko=Cw>rrgH>pd+dl-05?jJ|E7BA_y_>deAM>BtBLMC~_ zuPx>>zY~u>r?KhbL6H2?jv8;V(rkIb$Q5mDMjp8ijm%z2`UQRLfBStHiSUZmY-iH$ z8DKOrE9+-HFnAUw;Bl(yc^j(AGd5u|s7Cd?H%+xzq4ABVt)LYuFZ-|(hg1S17nkLP zoFEx-B@6O-@jVUSL$!E^IyLd}36@LIXp0M-HGgneUfj@3(+ot>&T_o0{94sV{_1US04B!B6}_EZz-eIUEDV} zt$SXBopiw=jnWBuNUR`6a;iZBI{;cOAoFklFk}aD?-)Hm2)d6b+wcgC(lMc?1T+wK1t{kJi@K+LF zYmH*iUgkWef=KlD4NSyhf4@v2c{lgYs;gLNt3%dl>u!;qN*b~6;@Zk9yA2a=FNZyt za?;n1saeyI-onu)=4!V$s1K+Oq5(gmUhzOPZ{81^q*b{rZ{?H84O=Wm+Fk7ZF6wGU z1w>f)ix>f5FqXAaucFW2vkAPtNX(aCYGg7_c%|<$v)T;x@4ZapDEbbIpai$0sX$lt zpXUPQgPvtk&RcHqJgZM#=0|}I9fT3H@5aLcHnqxympJhR zYA-~=fA(9|1;m*cygluNnDyJ6^C~8TyF%4wqX_1B=9?Sc)9PKH@%GB@xXQw7OQ+PTl zr5?NXfH;9uB{fu)Om7GQ7kX|%mL$;C~vw@QQ zviyd*r%)0P4j==xb^Z7WCB9Mjk3g#}n6hws|FBgt;7GQGRb<=SxO3pvX_2YyfpjcH zmOW^T$=Sn^ZG6(#WRi5#K3%`WyH><HWxo&O0bulI{KKq(()pRJExwk#~AkwbtebwHs%$kVD zAg>NCzRAT3NxV%~+8QO3eE`(Pee~tti&BnlmeW z|LOROYXXR9JctHh&KMtDgFF7~#goE$axHKR&D}`fK*XZeOl5I!1d*yW zcbIg-D*_*`&#XvDNs&y`iJk(6h}Jgg+4i~xyni9${(&25Ru1F&R6z2KbpIQ}`-80t z=pqJL_%>h5?*0$O`d_?U95I|>I-SG1)bu~?{|_@q{8dr=XMLI6CKt+vmORwsh8W z%}M|8dN%PaXQ>ttUf&#g}TpXAT5sX&k zvG6< zy&@IZ5Z=x6uzpdo;^@8O=i(O>^UBMu9`}rR@w@*A6g%DUEv%0KVkPtez|)bG?{lm! z=MRRzJ;oJWxEZ;#e94&C+ML@vu>-tIo$f}it^OI;dpG@L0K}Bx_ zQOut2rv;_(mdOW{7tIknw8=QNRvJ~~fN$C!Z4&G?j(-Ad;;-~%92uO>JKW2kS$uA} z0GE`vShbz6VlptULjYBNg8D$@1hxK9P1(M;X%aCc#cP{=7kog_pLDd?(pxsMj&@bM z?Jd7V#%77B+u}MLC+%zb8_0Y9 zixO(DjF#dDD4?opfZkCBFori~M*?a&N;rFC-)pxY*I@0%p3jApSDR+?HK+n)8gxD^ z0;p3V0t!~?%btBg=7o}NiFV$n{i-v@5kQ~YsU>8d_Gt5b4)R#939pUKL5UF}Np z*e!i7-S5TcceR#DWu*~iZ?l*rWYqsg&Sf>9cuB=80z|8Y5kxF1cIx@afH7M%V3;WV zMx{szcOeK3j}1XEg4BB=)pRsn6ezz|J2|d38C2(Uz325&t9{jNhQ?wYrBiPq3y?nr zneQD-+)DQj-bz%k}G;=JOE+_@esSoFBQ;HNa6x?7-g(vGhN&{-8B?2=j zqgr?Q+KI1~KgeXftCYbiHgdo8_Bd04w72cXOcq;=<@`M7DR<(r*3|EYGX;!e3II+{ z>A}VpKMbVF1x?ioRcNBni7pWjdA8+Q%?8?^zxyA?H*f(=hkHWU&c)Uof!F zEH(QA=2~_|Sj5FcY3$>e-}nh>w?@*XftnPMM_Ggt3un9@m5K9)6`(eSk`#Oljb2zTPx8pPFsH5;e5j!^R~U#zQpB_aywD zFWWMiGnyHJnpCxdSu#$K(?q3)AI6dn0AEPl7$a>K6OwL)3L8ZO^_cec^a4jnu793US)u|_Y8y4+9;WT+n; z7-Njm17%W98HWh;CFF2pE*B z%A1Y_|79w0*TXsP=iEnmOri9O{>|7no+m!tIE@oo8fVD8Ea0~q5dRw5eKLoeN6TSN+#WoD!kQ$@=E*u;1@i#!eUkNbzvfz-N z=IZ`R8Q5kBHS9mtx)?%5F%=c1zZs(N*AjL+QoK#bJ|kqo{K5w5j}+)bD;5ilE^!~9 z1N%&FG4GKJJFNGhO-q0?9BKdeklW!D@I|qRWXMWc{U?~qin|<}L46ORG>BB2$fB=T z)vHLAr)Q0MzaBF=m8(kBjT+D|oA8R6qsc5%dCKF~ndC$8PlQWn^#yJfPjxYF)q~XU zL!#h)JR0ss3r&jo@FOUs_m1RBMMXnEH3XAn=keiAUHRd~VA9e8iN+8y8~NGT(9<55 zmHC3=LhD+-FOSE#sOlDz)_Li=LsyUdRIM8o-zfLALGo%PS^VCNSZgHzs`L4dGDsrD zHanDYe>^`lxAWzH&Ex-hqW;@xaM^Mklr?^kcC{Kc5>i1=PLuxlBu4cLsm;M;b?!k4 z?7WW`$pT`uOEn-e*AFfs$;`SR(%4Pa##du4(aPVD?Jnx)(s$bvCezEB8gqu*#@l)= z)ip_VtC)`!zSVdQ+8c{Jt6rre=(@kC&Q)u6j#cr|`)))~Z@Vx;%kvbF*J2Pq{Qi|J zPU{8!RP_Q-8s2{BLeX95b}FpZyjZ8@#HdvLj9HsChDZpv#Cv5(J^@d`B6S@3PnLwC zP2ug|N&~=dh}LapK>fLRse;}Ah?_+Ky~}Up2*}`@{XgctU>?L_1=8owNzaes8R(Bcw7q6-vmYSz+yz zYiV8NddRPQW-9+05MvLXSu-vW=_@Z651-usP#6H`LBCxS{sE%84MOS+{Jotc(mYD& zx;#KY-YpU32=Fsf#f@&UUEGB<%I!qj z?Yvp>$;UAbRV82ka6q=qSgrwUp8{mM`eT1i^I~|U)AhuozQ^WR zR4E1!W@$G~biuGRkhPEV`quNRnxPYfw%-?DyQ90Pk$7+qiVXv87C#KS3)7Cmjvq&6 zs`Tq;SH++KAQ+we5sWuCQanJ9TQw0-D__ zG-J}7cq|rszL!gO2B9sP;vJnB^pf{hH`aguLZlz5c#VYxPq%L+%WC}S%1fgMtT(*3 z0+~C4{xi7!SGkb;GBz+>0aTiFSBRgy&AjVzi42v1vs5XbvA$`W$@m%aSAoAR4}dGr z`7_b+Y*fb7)O3~A@Bg0c?2dTD%?}3*@c)U;rgpoW0ly!LrS*sSzfG2ZYOVUdeo@TW z>eJLJP58?)1US#}OPp%guKSl()Fx9{j=XBG!|B09X`!MdesHl_o=L;g1|NF$i)nH5GU>CiQ=Z;kScNyLP zdoo$srK6TRo3ukH_5aI-2Z+2B7fIccnrBsE{!3_v=wBQI#EpuO=LN|B|Be55`~83X z#J>_s@6!X_r4`%#_?Sk}`@B&j^%Ky!I?lpG>dckjn4a&9Zg=&o{!f4H;0XO9m7e|M z)g26U-Of7n)FzR_@Tf?4{FSJ$TItmv9ppbxSmd+FN*KrRTa9l;9A={uA!dHId-hL{ zg6TYdG^^JFxUa@O06#s3eh}f5ebhN*R($)PKYuhu)N68k`Uz1WxWCyxth0mq@_8N* zi-+MR0S?wO9;X|*ox$izL#f|0bmb-qiM zGGHdH6_T4cTshC>8U1`Fty*^#ngc+W2k-B&<*CO1l70>BACmai?1irzyiWcPpR1cs zn8(lP8GuVO2oqVHeT-sBPLs;{?}ujiu_HO+hZBs_<>>bPwAC>hg|I`sp64$8Kq?zT z7Q{Yoz8)q5RY)c8S);$H4etQ#ZFk4w==7!~XMfu)Tt77BJT_X-$pG~QxmZ@d1*x~g z8IA7Ue(LWifvFF0q$%3hpkhG{Dm=QnY56lZ)95pJo*(1rLR^>Lr0?Ioz8iJgzJ5}? zT@VfM+uvfoeVBUFIsTl6(!Sa0t|4P5+2I>o4-hltX90=!WjFNuZJ3|=d>skk^~6|Q z5`D#@+bVV4NN3<42Xzy;0=9HH%=0|TJ%l{@mU=1(SWzE(0p_Mjj|XWSVDBu2dk@&6 zK#Qt}qyB%@m-=rb!RbgE$U<~vfWo7qLMTfS5RQ2WI%Vq#N==!zQO99Og*@OPeYS{Im5>1hKb_(c765L4uP2lT*)x7>s%ChenxWTrJ9Y$h_3E4`9u=7^R?MCKZtT@*f4Xo4H9y^W zx|E*Pd~iVkTI|0f7@xoerOa(8SX8W6Vb~hj^Tyi2`IvreFPl< z>{(%0*ha#Rw0atCQQ&SoU-CUf$a!bXFiVcYs`W_-*!Sq`8iW{%_oB#YH6U0|(_|B= z_7Y&Qm%W!$X~zMhA@k$ds+|~N1&`B`b64@b<@}cI`NRHhSt5&HeQ|2r>+_$~g&O+> zPLA7s9xi6oN|Zzyb-q=gQ?VLQGkeQGk(vOemF@Y(JN%8 zrt>@{GJ-sHBy&TtC{-fVD|ct~eNTc`3jlxrsb+brfmE76ji&Z%lRK8FgUPL16$TM>wS^r0iiK6Us04C$*oyHC|hV3oOq`{A!IJAevdc3AORf4@i_GXO8E!&$WMmlFh z1vv;lOY9|ORHX-cOb{?74Q0Ns-Cjlm3rv^Kh_ETx;j<`InfigG5h41`Q?eg0r1h^T z+ssG7g5WwJ$i2wiPw;!XNtt)sDOf9%Pe@)iEy5i-Yp@Yk4h)GhA2~?FXZAd9LEkkw z8x_h&KzV^X;QMAlw$A+o_gygqU>%$wlk#F>E`Grb;(5WyQD3|}jK29sx7pb_t!k!H zSJKHnZk1dX93}VI2=y&L_1mvRPh>Y?oNsn1H);UN3l>+nAqTB z6K`_fTN{8C9kk+tYfSq;m<%9ujP(F6gf-&zcCWeuRxS@0O061Kwtt>6xU<0xrwdB5 znT=nQ{f;1vdGswHHf8M}>-b~9m-c;L0w`cA*5W9dj3A6W9!jfKTlCk*RKhzkAJ0=w zENMU&X7tSwer$EK&8Beiz&`acbf6V7{`}It-4~bB@t1kC??aEOvF7@(C|M-*jbh#+ zq&kL^gDv(;n6OCYLW2fonJ?9_gZ&{l)-6}fSLOzXnS z6NizvxEIpnNXYa_4%mF6STTdUaGq*952IrDyZJb7nQk$qd^TS&?o0Ra1baZftn(Zu zy=`)}DTHgZys#{st~+m`2fIIRG~S19E^f~hs=ZNlALx)yj&+~`RFB1{oRAl3j^6})L3qqg3Ys^_6545$U-&RWhVf`L-!gu zyc>#FA&})dZ{ax{XWs-|mENz4$Dgns$CE0B>NH|wLBLL+Io~d$wHdip$o6i46 z?{w)3uzg47I(cTSQ}gSo#5QX_cYvn|WAMH892S*OCvMprJ-M?MQ2ASn4?uPrVZgV0 zfWX=;J9BTQO7iaNFdz2gu#ymtPe;y~+8=2Wa|^}8Je}Pn+^D44^>7G75^(3Mx165n zTBOCs6Jj0O98Ag&CxGS=GV6G(ktzc9ss^dsquH{`Y2VGqs)bzk7K49R=ykyc6xHsd z))@Q$xR$qs@;#>%kYWR|m<>u$@Vl5=Tz+OLnmeq8JkDEv8eyzzHZ~$vYsR!OvWds2 zFsjIgJZG^_e5aef){T{1#A!1hM8ENKVjk(<=8voOG~Up|@haWa%~WZ^?ldrcONro; zX_egGO`|?Ze0Ui6f7pA=sJNbHZ!|ar5AGyDaCe6gB)Gc=cXtL0fe_qXg1fs13-0bV zgWCXub0^RLyyraUeeS3G`L4CstYLcZrgnFAmvmMA5}Bo)cls~4q8}4JDrrnV+?-16 zkEE!9k~T^|b1(NB11ogMi-^(@Nq8&rmog1;3Z|hcRK=YquiLa|Uq>zlO^QSnb5zWe z88jjc!*@EAtrXAC8%58vRxNoKU{p7lI_NG74xxKHm-msnZim%N0?UdTIbn;~Ba_m{ zZ6^+>NJ;{pA^4Q9un475^ZjZmz>jnJCy1?-Ui`qZrOd#R5rGkT3P<--4hxKYY2e37%JK zHPQaq1Hw_6Q)a|zP||6-N)(QhD|%IfHohLVJ6}#|`JoW}W0`mXuxiZH!R%D+--v23 zMN7o#RlTaabj9CDwHQ1`&&8$%z4-g!Q1gLao)VxbnumD)404F zD{Q8u%(m$ZIm@?DAXeicuW_vR|44XCF6_&$78fa7!%#>DjYW7RfSZTbTk*Lm5fAZ6 zOhnr58_N_|Ewnyf$f08toQBmjw(`7iRWx}VT(Do6tw@^{lFwOvigg@PT!(simgbT)L=da>^_ZTUDwXY)X}Z0K+ilh~sn_`gkhOGGkShs~0IMF4LdL zEv)@RJ;At;IgCjnx<(5r9<8diig~Q8`QwgF?qZ>ox;?^c#X{L^DDa_v9tGeoE;ruJ zEEQW^F2vnTh8@I?6B(K`n!<9pr98Bm){2P7XsFIUb`%^S+j05bG<~tA|MOo3zCyLR zyhfL@Ahu>U3;RRfH=mo~gl=$ilFESd<4LS?1`5OSvQBXHKwi0>Ttymiq{6_oh{HTM zF`^!ObDXKPvnOFOom=$6G&K(29vlB}(j__xPv&1dkYcI@Zrt+Ao`aqrH6HvB6_91l0?}*2MgS{ry+>10-*pJmyTxDz zxQyC8B#@?o3K`Zb*J($(Xte6W7uhv%(FL>z zC?lfzl0KIpm80L~ro-GJCMn|lt6#lH-rDB3KLNhE7*r~~2(`0p9wL8%Knr+V_kE9Y z+dIGx<=<%iAQ^E1D`8N`znvI1|7#@AL zWYsCPCGqhJ8Uv?PYbyLgUPH%kIU)4>1H&LrB>xX>JU;9Uc_xF~5M4sKWG4TUZH)9$AcKBgiXR08G76^diy>ikwx#!}&invwdWo8G=KA$d;1)3>A+nvg23 zW**K)M-@GCP~QF?kEqpvNu01atYalg^~javr!J%CPMaq~CY+*Wv}k?Qzqa5-CYA9h z7q87+EE~Q5V-`b&;cB{;y^g_jF1dL>uvyp_uWzkgOR34$f6S$)=FOAlqoBKEcB)>p_2_Nsb6RQX!7vVEM-A=qY&C`d=vZwWy<|9%=h=6H-42XRojkg%!=}`J>;dcc1h&h3* z6oYPnu<(a$FyMJjFfpxkwum^C=$=OiNSI*M60o&_?yRe&S$2iN-wGbKFa&dv70|2H z6_6I+RAga9n@M&}wMNvy=GS*P?;_s2&p8UDaw{ceutHO(R*I0^VlfUU1kZ(ZIxQ5m z<+|IML`ziR4&5y#x8AeWvJ~JkYQ8-il3H(*PK60p$lyUp0>;8WnAel{ZBgv?N1u+< zzCd;0#B7QS^0X>Lc&taV)d_lPqX16%+zyX#pb&;(PKybyY#il=uDhpMw+L|kq_ATL z!Nk$JUT%S30r|VtCVTV=T8Q2`hwr&EPhO;WYeSKIciy(sRWGI0F6BL||ooO8AHcditf3 z@=*G5@b}l!jdy&nY8pq|X=cJdltt)VyAQ9zi#-G-H;wGaaKGDDw;Pf}G5Yw=*Lrei z#5Jan(7gCdxT;mp$y+wN?{?n!1IQXW?P&(oiuJby7Bub_G8{EEnYmx5ug)INk&x)O zZ2-{>W`q@kZ*=+HUk&Tr(iHOu@l~w5E`q^v{_2+*7(@yxicpLwR3hp>f9}0JMN161 zs=Kc0h;We!+@_8N40(gS?-j_hnT{8$6D$O_&hQ@<#eMOHO;08q+mi`G76C-zd*Wm=mp0N}KUhF+zv&fH^pbB4EO$!l!ha`rtVGIWN2 z=w3c6>;Mqg=rF(1f}j_=41_6%(eLARSRzzUKV^r|&X~Fhrbm)Lhpm-YnJlznGk1dd z67}`S%egG6yGL0%sY}>a{n$C8AC2nx6s%vqj%l}w3FF`q8e+#Jnw z>YT49;F~PpKh+(#8Qwz`kZHOv7X^WJmgYYv@`Wo{&Qww!*5vKNfiP6ZnhXvyqiv=~pJc)Mn>UweN&%D>VULLp&E z3W?9lC0AH5h_9P|&~HU{2i@0&Yyud~Vz2LZS4YO1*UCby*FRPCO>h|dywh)D?YjE_ za&XOp!se>J5DGK!1+M5J**SEcVz_g@XghT7f4t#I|5)gz45OBIA~;6WO-bEn_rmcB zDS)Np;J3U1LI|;v(6SNpgMSEI;{v!hD=+8R*pRa*cFlRnzIS%9T91|1U5Nc|y*0=O zvpU#Gwq6Smz7kWaGkM5`+t6x7kWmu$36!8H3(4-!rJVWwbHe`6BROQ1S_~F?A^a5c zYgi3JW!4WLiXrt~yT+n8b`T*kFYPHN`#MS3N%^NB=CyQIOQb{Cpj=A_N{aYgKFpGlP zPgo^~F`|Pb&=`bBnniNRLqC7bFF}L5jv0PP9*&6Xf?e7L=rd8_0+-A$DTGinuI}&X zot2^UWg1EA0;209Poby4Wj>);x4)DUxPe1qu5d!db3V$ znu{3uSTdPSXQx3z4#;bFB}`3s^#^5p+zt2V9?;&XMUxChXaQ zf1|a&b^&_BD{ExTH9d~6G=935y}`ARVKlHM$;OS_v)7o*lQHyIF0_#pVM%p6zO}a? z$%NuQvt*Ho=pthHEoBrmy_O(!r*# z!~VnI^(nqgooc3CuPt+J_~bzw#8a&yN0V&$slbY{o$M6nmP}rTO-KbFS-)Vq$V{`R zxuB_)kgxYCKpw9&NnoqadiJ~9a`r6f#JIp_j=)!`el7z(KF%j4&Noc*9tx2jw<+~p zw(!b2PxEAvZ$+8?Mu@6nD~+-W9+zHwhaK+?WUk30w%0ZS!JliQpr^9M5*BDlyXjK7?PBk5?G8+kK4wy9b{}i% zqp?3VCY!@m!tMKQw$;&IZ8^ue8yy$F<#ekjF@22Q&qfJY?L^+4cPP_5qp#eKw^FQb zUrCU)f}>aW?|AI@;^kO)W=VPM*XRiJ=N7MFGVhJWb`SDN4#X^TpIym!c~D~Cpu~#x z(qK)`oOIaJrFtFiVuCB#ln7aATSO%+%1E{)Mb`Um=F!|WJPe0gJLLrhex@+Z7)w}# z%QA;d#Dyyy+!RroaX$~MS|@+D^nvh5m2A!^Ua=W`KXJ7ki-$W=(1j=QJ4EPbCZ=7oe(RMCkkwA-q*2JCSW=9_K0om8@o0ffg+Q0 zFc`CdB}lzWU-$lmA0RrZNrM{Tq?B3WYQMdKnz8`J^Z7n@+Ia6@sD{49fWCjNiH;0R zbTJ=Et9Fv;e7cew_pr>$?=-tqC91&p86)}rJCVqlF}qO>^z{1|>T=0~l#KARzra57 z^SIQzmLH~7AK_Wr9a@jd%H7@^vu1td*mzpry^H!#lI*)o(ob_I`IT{N*?8-)Fa^;n zu$jayy}x}%6|X6B#Ewktaq}nZuqxS-(`Z-c<9fS1wHs(+#U0k3aqZ80qEb(yR!EgW z8$7F?&bltPw+P!Ga9Nf3vz!dmn~E>b3wj|!ctmJ3=IUEX)4#mIvGio4T2)-j#({VN zieF@8xSY99xZaOZ4I1~A5>J(8Aez;~p-;GG&SGUgva_Kmg*v$;>hfou-ST7^9G&_{ z(QGH8r!O-31F>2tAk(?7os&mj69epcZ5QU!gru7Sw3Mi|x1amjoe8Yc+7i0?tzrkK z?ik9->vN7f@@^2%%PezzKklh&xKl1E79#WK8hM;A7m{mjoIg66SzX|0`R+W(6 zXwO+N$PTCqV5&#YSnLf!5g>F*i8fA2G6KB3|9t;^+rE7@zi1RDOn_8Y=7JtHkF~Jw z6uc$%eC8{b?c3CSJ!6%}Ly12VGxvs9^9g7<`}o{nAIu18C5Op*0>&-7sl8;_;0`#2 zkfe?C=7XMpk@<#eg)LX5p^esYE+x2A-KbhU=~G~5ZeKxS7!pY4BcQ=qs5ts1mem{Y zUG;6Pdid#PFs&Qx=n5{71qbypjI51G+)%4 z-dT-CY6bhNQ)p{oOW)MD!zHPwXXwjHuZQgM0*oz}ehMd}E%&LEy;-Sx=_$^l-oko{lfMJbYm+_+#jr_ zJeAFFyY4DI(>gtU?LP|xH1hda4cg|;I6{K8<%r;XJl6p2t;H_;qodU;6aG)b#pIs2 zLfj{aYZuc}f(Q{@z=C8I+jhO0K}Tp%u!P6g8Z^~z@lLG$)sGBDEL;KYqJ~H@IBLha z>!7Fpp5hcSeutHlCAWP+DI^kdp9^u%E1Z);Ni17w zi`=G-=Ysx=2HpeaL=!CUw{zE=7lj`jTGc}!r?cIG5Co2uau{x{;~vf<+KRTCKho_J z#X0^}*=YIuuFt~B9`wDbBj0#t zfAoaVFfpqbPB=)^8!5ec$r;(7_~Q!StR98&b86l|!K9(Umm^x_33GTP*e$OaPo7hF z3)wBQ>uUKZz_DI|PIc*3s@9G!VXyEXPRE+M+g0C#k;>@vYA62BWvNKuW zsyyi~WBoYoQd_x#+gb{B%S;=fm4kFGV|MSw>V7o@3%^uQma+A>YO*b;4Ax1;N>eCh zI0~tO?j&rWL1Ee`0;gZOI9tX9k}Sp`gFXYuTU-a@yg~-=D=qspqrRQv9&Z>H*;#|m z(Ci)M8|hXPD%#DN(YKJqseO@w%>p#(w#S&F&t~yoiAtSde;(emRHqRjb=bxCQBlXw zm1|9sX0r=-pgOjIhmP8omEj+3+(1ygZ<p%*3`4=nCBlfP$mDeY@=rUC90@Y+68zsjWCbv925D#5p`O&Xe$aD~xYC#ET{U zuP=)&$UNm^>fodeLH#xqLSbTTXEIXg4c?n(j; zq1af&RdWMwqN7FOuMSBaD~jB2*_0aa#6AH}TLl%2swy_M6R?0P%~V)( zf^`(~WAkU<^4g5eHgRYgtn{I*(bsqZ6AwZD7|DsfxHq>DuKp?G7TGk!`MX=z`(|1+VkxZQsnugbeC9Zz{F; zgk7IC7(xJdQzqhD)WusutANIZ)^5#sH-3H@ov?lU#Ub!`h59_*QmP?L=%6gK`=&IP zZL@8Yu!CstT^ABLPqym1;^Bu$VQ}PiJ4D2J2qm>p{LS>4AOg#IxK2<|QRG|wYIY?) zDp(pKd?Wa*l@{x-Qi|hly3m6THW^U7K7g|?@N&_}|Li`Dzo)wMHi92fsZnO}US%`H z@%TD=Qskbkr7f@>E!8O6{H}&HOYJ+<&*t13Mzb1`^JJ$B0i7(nMk0l99*{mD)mUiF zAKc>pHPR`AeoIhR6cT0qB))es<^vwbnSxAJOum%- zJuq#tT(1U;VO!&ZUyV^deVbEj^QiseAFv~dKmgQp=v?>9USY;+ypCmP4!`KoO|iJ* zbUB9v4>i6W6ZZpUdG4p%R&lV(xlLsIz65lEJl~g~EfjQsHXfl$XcfgFb4p3b2P21b$fD;!9{|7HU z66}rxRrff*?SYiBcr)a{M!gV6|11H5TNf>UQCW22M%KoJPWc?}U&0cKi^c-9nNGpW zC2#`aSZKW+;5?sfwGmrEr)HK$ z*wP|UR+%O*-7kWh&d(EzJ-W~!5cW6+=O?>UKTAo6&S%9|XgaptbfBOy(qTe6?ZRl+ z&eiC+iLR|NY1;+a;K#O}pUZ9ctP2-FNNKmUnSp}ad)iJsK z_YW<7Z%H=W&tU(v*4H#kD2$jtSP1(B1!k5KlojB>R=F7kEG0))M%XPo8Y)0IC) z0qAX`8&1zRy*nRl_;3pdjQp0Lii|^F?h+=0hdAqD7zOleFEMIH4@|JGmS3Ld1$R)q z?YBmI!`HlGOt4B6R(Xoni8!u>gXCW4OR-OUulW>^3||t9A4M7AjJ)Z!N$JX*7z6gU z!xRea8muAfud!0B-PF{nS{L8wNGxsjIQ{OBiSAa8UBY~@mt@wJ`#o*4`bceO0X z6cOxOq}ql$AEMyi6%t}&hRin;_h62GHZT6%hEc?V?sac|zqgCYedT-V#xmtmLp0fG zIhk#7*uU!7sqsjR$CbMx?``=9%X_y`D`WtYL9wH*K26WatQHVLM|IT~_!jmXzxl?~ zJ+M=CwK1}Q6TSIMAiPW<{9E#%8d**U_aI%Qu(q{)_F!MAKNIkHLxBECjaLSqwcaut zplTQZW)`nOFMc$LIa`-V1bRsqi>3xSP-2eZxx7u>>8lC%7ya!P?&RNMw2cEUt6WO4 z_S5fJ$9uXSStS1qYLJQR;pbLIsbTS@IC{sb`A`}kxL-E)CHkXNDfutkn6678W$SBi zSY%R9Hh2pTLR5I4-G0V3sxNNc2zz`!)$3UP?n2Y46`K^CU{v5MP5!+(gmFgnd2ScT zFPuLb&Yy$F6b(pz`}^I8+qM=&_{Z#k7X)zFxtCOG_kt`HY`36WP3>9hq__Zl`zQ6= zF^2I~_0r!ipB00yFoQIvOS!0sIV$v*HuEIzf?!%eYzc?{FzGg_*%k$1YzRxFYOOhurP8j8FlSX>&`5|pi z?7KFmH5r3GQn1c@r$d15YOoW&O#xereWs%-$Gl31oj-@v^XCSXhZJc4M{e8m38$`)a?xl@4NRnI!pMuDGWTy99^^G zoM@lKvDD9gqr!?`&YbH^MXfgd!?dZglHQJr1%#wljAe3o4xbLorCfjYGvl@TJXNbUhIrT2O!X8M1DfXwf1?)$As^mn-ZG;ZjAMRf^EJ!5<}?XIP6(e-m+9#0=k|oBQQ!0W!luk3(ZxtPFw5KG zT;yza98&vL3aN7PF01opFvCTvy62oo?phdpt!ClP?pwF{#Ac3^t?aZOra^jxNG`1{ zQsG+$iWzr}TzJg3%*0j=J7EXJ|6>*E!#X43yi@zIPA;XtJ=m^fbn&-pIZFn>a757K zgbhTwrEHx=MTjb%TLrEDU-D?`odr^7|J)iW`7$BIriJC8jVuLh?%NUCba3p6*G|XO ze_QF^OD?gMc;iE=BKyn{k(dKD`EtB8eQ!^=H{kIY5+@H1brfR?1!Zm5U66chiOvO` zrW}BGv}@74R!F3H#Hf^k3Pi$``+*GAG*v z^NMvK?v)mfh#hj6cd%ymb|RaGTAg3Wlos{=%v`uYv%rfdjf-Hn!vBG{pkml;2zOKB zO9$Mu(AYYQ>`_p*IXd8UX~S;^Zxm|&t~|Pp@^~jl>5V|?@>g5@;QV!h8W^zRy&E?n zPKMwMUtRWY(0tCZbM#MV-#VG$n^@hI@BBzyYx@Ya+O{S%O>kUiT!x#Y-*j$_&pNza z#v2z$DUPzEgCv_NU*5bx<2d6@w00K_10*Kt&4GAdm%EuWnh6K^8P8!X@3%CgA%rV$ zyuR{-2LzE$*jEB# zI`_tT4KayohL7C6MgDP7ZO~$ASWi4Q_q@Y*gKaJ4B5-S3_79LtVVRYgI|Lp18bIuB z7@Q|6Z)N-ZLw}0#6%yfQdzE!FB+=|mhrvx3f0Z08R02igxxv=LboB#r1Hp9hU zUaXkMyFjF#8FRV)0fOwwiBUGke>62A*}!C=(1Wpd1s7}sS8?qhM?EL9K~hK2VEzpq zcX5a-S<$d4{0E)%`ePO=>j{>+v6R36A9`J}!Z3{ALzeN<<7sVbG9}9VRQ`$)%%ESy zqHn~3F1ll?YQ#+J`jMfTESS~O|FS0j8WfN4C0w8vPGb~5EE(kiBA7h7zrN}mF?NmA zpP=C1`WM4j82^YQXm=bEfwHa^C6}cCqV``$Kz|j?zdGh8amr`YqR^ct?}UHs*#Gs3 zmGveGco3QTuhII4;Q#u}jeVCpqc1}t;6s;B33n!f#iAyO7f|9k(74*UOXJAe*^v}Hf;|DyX}p8L>5 zDDj^jw!Ht#lmG8`Ort1UM^A*){}0jsnhofmZ|R}LhhF!BoBn(F{`;n1IJq;VS=eg- z^)~-TJ+3O0c+|rXkbT0x{rb;+pJ~Mk`30zo{%gqpZTRU5pu`i*AWg1*{I_5Kxi4H1 z`p#}r^5Xwz%K!CFf8Rk_21-0j0cLjdKgQ*MH$cDq{}cAVxbgq%gei0Ryo*ar#1$P* z{r3b4>i0^Qv;Oc^O>&eD%7hI&Ib{NrPtQe|BLRR~Oi|FIYYd12@V z%BWK44CNzm=@}$%P~CQ*ku&hKVRYjTp+uVdHX4j&3t2_hNRxYr|MB!2l)bO8UygJ- zp%BW;0hHnq1cFRozDO3fFic^U^ZDBJISqdgZT%GLqMizNMlhcK&(-uBCorhRbl@uf zm%|DYCV6as|L12xvyRF<0*Oyd<4bWC_KHQn(J22hhW~wi1i{Mncxjf)-9ml4KyvmN zB5?Xo0?Za0q$D7hd0J4{TRZ!$sJQ=FhhFbwWMtSzIDk9^jGANG8EUFKG+DkpiI$&- zi~iSC{PU!k$@@2pCw*b;)kFbbg68wpMHP#=;E-Vh|8CI#_mx|N75F={+iX5;d}g%N z$2oi^6ZmV~Jrz}MV9WnMXIrMH4Gp#ag{Y99X1V|Atx7sd#D8=N-HNDS2;(}b36aN+ zjhSvM^}1C66oTSFR^8yKwc0=N-hYXPPGXeXQiHR^;YQ`pxl)5!K?VHVI^j`Rp=?>` zR#Age^US<7$*bv^GJZp?IiTOcqqsqg&03#Q{_w6`K22#1rQbT5nIwD-pjgLMM$%5Z zU&uEEJzwL}>Jd-PHHjF6KM;PzkgX)vsCRQcFz#Z%6Xq|55Y6Nn=!qXVyhYi=4MHOK zl`{0Xz07PQtxiBf&Lzuu+TZ# zf{pk0SV+)*AN<>ISIoeIPLqfu$By<5x~o-p1>*z>kA2cSW6GB_4)X#i=%;F%n>D>g zSt5&|#+o3OcEuSQz6ISF*(!xnD8Ht18Yc8OOBtPC3K z$Su=%wlq9ww?Q;Lx6M5wE2$gW9&-uWcvvjUiBHm$?Kyf)4CASN@+l`zT8G-E@BW;_ z%eUhk6TY>Zyz*@{zK8O=OW>Q;P0~R9H!N|?H`G%4R_4*ze@G`jLZ7{580Sa(nL_pN zZ2iyoAZ@5%C6!Ab`}rRnkt>D`mASd(w2uF|DN!avMafY7NAQRL=#ozPuaqen8qRqC zA8p|@P-$0NFx>HfGc=U@aG^~IN6uFk|Ji2xCD)3H^QWX?z>BaBfa0;3nwt7!d9yDoD;v+*mSb9f`Si5+(`2U3Tg`gDE)C>- zMhzoJ$$8^+_7uutz9QfHaP`xj%q>IhL8<$O`#EhbVH4$KGe8W(R!^fzp6EwRjPe&D z2izN=(~QX>G^mU}U32rn#(z}XkbK#d00kEr77A~jkQ4B1;FAR?n=3J_6kEe_{NRhS z)O4NB$TG2k$_~+guiO~?Cop4Ye~IeZ2+(U2_ZUV!x?u1!iIP&MBOLcja>s0;F0xW{ ztx+SB$uQx=adYS>zkR?cqVv_#zVp?L7Cnc%^I%m%vfN(fs{sY;Kt%XjvHn&1-6ElD z$sUnPstPsTA|GwOEV!8pkX$-zIYP#3L@Pa4d3tiR)4<3hilY zb}cn=QF-^bQ8YTf0_ZgxQgK-gnWzSgbt&L|8lpyHe@8Oq8Si$m4oYc>1q9F=jwC4> z#Tq<*Rt#X%+329kwe#o_^fBZ~d29aJu*EP5*y!xCkD7P>CS&kkr@Zb@+`Qz`5#RRQ zzyww8s)%CyvLw___!$pMv|vSbg;tv=R2%a6zG?LOL<^NHXeNj5iZZZ^1lm8L$u0J` ziVvP;*Ok6aj0oL>!97=ajQgCYvBEchI&1&1NMDJCO;ob&{;uipe_G~c*T~etblr;s z9!a{CiB!%8C5lUkaE(A2d}VkVJvtsLFaMNr+nV2`yLaIM^+N}Kg7|{mk9kKb_@Oz# z?Owwi_Xl7{(=xu|12KlZ-{4Q1t(OAC60C@mLp@pipL4(LexGU5klR&g`3h1@<2w|+ zcUf}E*XI#za>hvd@N90n9z*=lYv~eyTfEw6pY&q7mOukrC}(lz)(tw;j;22Hy5rrI zNZN^7gt)SE|=Wo5^J5U-#=JLH5w@&dxl|t9EO}xp($Zr-y}-@K`1{R{p7^ zKiX-iFaGwe@4BbSN{ZUe;ZRNh=Uec63M8pWffxasqMWSPDL!G6X{E7l2JpRIRNLya z>YNV|zngSRUFa&sna}j?c*S%f4dc!;XxODUq;C>_M6NtJkA=_4`N^Z7wa)(0V>f%DcKtG2mK|su^XeLZ zvfey7Mj-@_oc)yybLIrC2{DB|wi66C;je~98)v#M5WSBA?-Y*i$2j94Xi~T6@d&kb zxmhjpP>;K^b~;Sxwb>Ou3A(p^f!s+zWG+ny_M1fqM@ik=R~vnsTf=bN%xWL$Y2}vd zj$-nHU7#TlDr^s!s3KKCFcEUB0$S&m-Dt1Q1$HYP1DU9DqCf|z!!uYCxM4E>=^k1p zg2>H)0xILvxV$62Y`g}*jf40tZ@41NgBglMqn{Lj%-VKmoq$fN{fcbi>lJQ!fwl8T z3jg=bYfed#cZXU!b#|eNj9QxH0T<+;eOJGB^-@(}Ywy4avy=U|fg)QFPNAZG0CQfB z&tHs?`<;yKI-gSk*WvF&T(Kib3~j8A`Yz9NTA&}w41hcHL=4h#?n#&t1ONfn95>pV zZ{_s%&2rcIagt9spS7UOYNDOgJ^HNX2^l+vGvg0js$(D*-*ZQ7l>8c_D~_&>D(z@2Q*32_e%&a)OewKk1hjw{rk zO>TGgL1|D|(puPNbxzZO5(%Wa2^$)~%6>y~Q!Se`GNZSuTz(>L%b2-ew+hXR<8?UN zf)&qdiU2Dt8nO_2WeHn$WzLs}`Q;@SR0{Am+8^(#qo8Sv4n5yD`+OK@w>KJiizhGL zW*MUvQq^ab(oM|syFqEbOtUs6Y4}wkVLCt_dc@nJhh(yg0O7H1YC@sSDX~ASE+WJ5 zQpn16=4(SVSdXJv*(_X{$U~RX^Neo`^*ElPPxUlh4dlh>pyMd#3Tw|{ZLHL*wTW8u z9(C`9;AZc(mvuCVb~R#Td)|!Wxkd+&a2m2nns&Dp?5r;UTkvP@3vPQo`B$_&XIytVVx;uGTTB6i8z18x&4gnwrA;055{>-@U`m*YHwnwA!9aVY1-N_hN^Sztucb zoj%6I5}WSmJW4*Uday8Sc26eGXd*IYBpvaGQ&xtNLh`ua>>2ReM`xE;NoSXDZ7vcz zMz!L5XU!5I$DDa~#j{pQMcI@oJ1%tnuFEtnnn{N71N|);q}q{0Uy_lZ_2QgA@}KH` zC&_hMeWKh%YD2uY^PK~)cGT0^--S(x2(W^()phpo3a) ztseUBEe{Y^D&ch1Gc|>^YF!5;A^sza?ULeN+hE(F>r8gG=L6;%Ma80~&NH}LEdBAU zO|s$xOsLn=MGlP$iLzURY%){ch_BABX@l|hyV>oKcg?CK`vG%!WLad|`IJqhUU4}q zjrG=RP)1SQP~}zH)6zWa^gt{_ogN~zX1AJNkgj5_dhSgunTRJrqL}Z5F(-xOxtth8+9nVJpcCnIXPM zM1Ph^dNnP|Pb2_JO1wXT8b3nU{q^5-7DcC-cyCEXZ?G0NgB;PhCtIIVlO!^#@}DfWy$64h32HMSMXKVaL-`K5LZ_G4Y*{pF!h!}&_)kKygj7% z>&1|--B5H^FumRdozK;_Z=rf=59Q9#1KRZ24*!lY@;(Y&cbr+*!v49`6H}C1F1SOa zbF62f1tlQke)2#|%!#$)XB-`HgMAJzeAabHDQB$d{Q=y~*|Nbc#Z+#jZa^pexg1&( zH0ApK5Z9srb1`N_Ue8eW@jKZ{BRkv|E7zFmpv!m~SbOFp&W9YrI}xbaTIas%0T$HG z54nM?Ijhwu0DV`E=^r*N@hsq{%Bq@T(vzNYj%jIdwTt^GyC~G8spH0nNugd0La5j^0ooQrJM7nc!t+B`Xgj;XDQBnbPC@A}B!M z3RQd;CKOk*w%a|CLfmbnJRXhc=8+HV{TQ_b1}j=Rf?*ll^c8ZgE8X}jb8fiWZk zhN&YTKw+Ek`KjY@g)vzC50>@gA;^dwotUWti;hLpyVo=F)31k-N60N=hGvy+$ek@TvtHgN)c57wEZMv zDf@`ClDe)CV^7W6aaE);k=LQuYbA6BlXcxeNyt87MZM#+f2)T1n!(eD_O|y3h7lEh zgl>)Ia;9OYxohS67H1MRnd_J=Xksg25`&FQ-I7bY`hnYWOPOBW{8i(#fV)`VT(<2$9TOiNWRYgSZYyoEc~8`jRd;cR)l1;b>bHm4dE zWc4H-)N~pw%=%8NVIKM7zGuC8WpAcK2gkeP^(Ro+VwK7FB74;Vd4qG~o)8+k?$D{? zr4o?K)@klsp!Z8Up-Hy%a&8v6v_g6>KCOz!= z_WVcOLd;wKFfrVqOrYaDG(!&$c6UVtqr=}===bBI&$qltKP6w`(kXvsHlrs2$q7CY zMS(_SQdh~2&RaU>W52DXFd>gu! zcIaJ92@XRvqf5)P@G$R zY)9NXTO{hyr#XvvAbr0G4r_ASU5g@_ZdAC%HfQ5HCV35oj}zG5hl5j(uF0&;&ESfe zOatQ=6*;mB^qSA40&aic0f93@l6bh~HN8;?=;0cvD)_cHP5Z&Y;Zk@kOcSkR(&Z%4 z6ynAGM2#e@8gy(Vss?ZE$!pqn3Zr^(ALrs>__yAE`;JIF-PQ?z)~V+P)-xY&lm1{Y=}sfv)>-yOf{oRpJ^IgA#k)*z@VLlRi0>yPV<>p}ds%@`-;p1sD zR0QG(y{TyDxqU3HzI~F(S@?t~7pc~9l;R(aoNbCA>*A~7(=|U0hNkuZn022o`f>du zx}g5++QmVS|5vx1ciCd^o+{OUJ!_gikNs+7o?dDBO2W&bsilp-9R)RUf9CE4Ki2Nw z%xY2!Q_uhT$qW{*VXYynW|>uV_JFV5eA&oMra1}wg$fOLnj51VXI9s7mEDz+9_>~u zA}K`0_L)YJ?0Jv&p-1nJVvcim!uqOHq4YO*FGc^m!^MvmCQ?->1lNW~Nqbu9Tp}N- zOa0G{X@z{Y>d+XOH)shXLo~*i<62glh}D#*@OyVr46VIlS~qjNngx+i?0jv7O|Y2I zcMSPqHu&QbfyUmmWxtjY7X6!RSlWKZ(xRg$F=V%l1ynC65v+ z=S$yXk+dxML9rs$c(;0Cs~H3-&*GR)SXOxkcb-vWc)S26%6}NfTG)E}JMwD+@cri0W8X{1 zhrG_+4pjzbB%qg0Io(=4lqNY%u`hvU4V+QlRJW3M2eYiHLMNgLuNmY}t#iaHl7~=H z{3|R03uC*-DSO6Ew9xM}jvOb)OR5^f7KCJxNCsVTB*X|J_~S}b1shi9$q;ekG0DLv zJi6usteyxA+cv#5hsoPe!*UB_K&=9%`S{yJFJyr@+b)z2>J#>YKH}Q#ei2T$@HJTT zHF}M53sb_0G;y?M1#s*N6{bA{2LQYK28)$LNU&op%cWoer+DV1xJzKNdG*4f=O>Ar z-Mp4o9et@G;aO{<;SDO8Uv=GbFUeeYB*XYw>xxrUzuYQgdTS>VAYv})Mok#ya@9q* zX3fs@X6&h%Ic}BD?XzR+w$0ah3Ewxz!vUN1%+k9^vP z8YGpK^Ab(o2C1#x`0Ngj1KCzgiq)rj<^L2Z!2Ik2r@F3U2=GHpNYoTFgFO*PzTFaz zkQEggpY2fW(x!`Q_pmA-tNl=4`~qrSZbV-+lYe&V3gB>I=3TV{=|c9L4j-;X3=HQ> z>0@WxJ-Ktv0)9CUbS{<3DJQcNZgXd$8k#8vg(aw+`0Y`}D2qym@!`=Aoo#(w(b9f* zN$yab{mIMdw+$fMN2fK^eOiGU)Xi(VqCLTPnz$8eLhW_f*gSsyc1$$v^Krs*SLvfS zBB#Q-m>yDZppFH#ragaElsJt{=N$jq-`E>Amb^~Sx0|zy#2c%$+&Hau(wTl70+Qt3 ztUAPV_&Nn&F`b4z7{~s+r}CHm+}^dPW4n#6A5a~pYAc5r3K0u_#=ny%(Z^Viaop9I z4!ELX*MtK4?e6_zapv%liV%7+k(I$>ryEpsHp=%q6jNRyF?cI-hR3pr)v?0^UuI++ z_JT^|=hG%etH#@yIGOx_`agV}?hf%E{+jha8 z1b(b-3it<=S3d8P-u4g(F-xnO3TDbj+S{*+AK=ddok~fsUTAgNDmHzP&*54|`#5$Q zdK_Hm6-005%X?B8Mb+T&-eWBVOUjL}sh+qpwx2zOc8nb7Vvc-o16D|A_6|JRG7bxf zF4J!#YAAQF2Yd9)5kX)8nC}8XFlL8WN!UriNTGUTU{zFP19T7OGmA17^ene!s_^c9 z;u&ngFij*Hag|zmt0(XE%^1K1EPW{pa{(|-X*4I z&{S~?OZqN+BCDy7LCe|~R0F!~r%8&wNzEq;#K=_Kj+Qf721N@Z$?ex47uTIMk&Y|YZ6B=ET#Y)SNBQ*hp z5$0)4^cs7j2l!iq2q88~vMPMn2Qt*;Or2gFps2!H!lZ7T4c) zLpYUFxPdwSV0OzJ1tTaJc%tD0FVe_P<`gARdgDQ@=J=`#c!C{R*r|hW4Sy>xTDa9*s7jkz78@&OQHUEVhLS{J zuLapnab`G9k{1((5_!My+85QWKRy%JGL*3_ zDf?vN6WXQ?wcaaC1-W`Aa{6Erm4l)89i*!zw{}6r`2_fsC(#`Kw#Uz_qR@Z4 zwUdg$;8w_QrRU2}w=cxSl<$Q5lgY?2Y9a)Wzwo6DY8~z0WXS4fKQBH~g@4OH8@)7~^l{td_+7({;|3lqdg~icr@4ms^-QC^Y z-JL-2U?I4>TY?972|_E08jTQ6$BF^!hu6t}tbd9)PYg_gAyeXDPn)N{%zlp6z@ zR#5L=NP`5xYkH=mlEhhY_%l6j^4V9Capa=W&5M?|(s46o_$J5sHfAy!ZMXuFs;`qZ z?z7#dzqBFsJP6d*xdy+)P8`Dk!Enu7QYL4`!*z<79y?Gl7q;Lswy zAZV1Y@20^3qhBKYx=q0MGF#C-We4)CsxZN$*MUNv%$su@gQ9TA?~ih)j%vNN zFAJU4svIsKY#{YVvw9KS%cNH%&!JuLQM;F86X`HMLFP@8(v$O7!a4;$#cF||^*gju zhEYVG_evj_(6hzo2 z*5P+p+TL+BgKS~^kU7jxL+pwVkBf=ixYBVYXw$oP&Qo4{ZYa7*tTR2SCVEaSnOZL| zFXZYgDN4j>H2(oqM*i{1UF3OteB4JS9J`ZGGEpWg;1XhYyD3>oXa*FX_3bbr=TfFr zWe}n>U(dd2SQ4M)d3}I7S92$9;8Werv;|)Q1ilomGxdvbt?cgbrxo;{(HUjT!ybDB zoW=I!_$+!I!?eF`_3AmL2Icj@&d7A|FIodbaj%Ndg3020g7~8zM!z@)#!&9DR~U45 zOQy*XMgiH445vA5D3YLum;zULF+MG~t!2aoJ(h=yl#MzJRF=6pa_f`@@iu7K!q3L% z@M0zB8NoDIeiGw+9fqF$pGowm@(?%E)6hnPM-?-oNsW{PLGKBCPrrm;%v9!&ZE|bZ zi4ufzU(K*|Cdr(2j?7wl8&X!d1wh!HSKXV0g0(4dP#A}3kyERrStMgfuEVP0M5hrM z6P4QC;okphZS;Alams9{JUvi3b;9az*|&(B*|=DdFz9vi2k1tQK)ETJBbSf46Z=If zVH>Q1Jd_3z0yz9V#Nt@bSo?3 z7XHFL;`1}QPV$izFcF<$}I8Qr7hWZkH_c{y^vZw}5GjV1(u?ezkJ6 zssNJ{V@bwe>R=T9PrZyrL3IxGK~V3U<;si=bQ#%#JoD~@jfH9TO96G3Oz+%NZDj|S zoEmr3;)*fFKq(htvI%F$eJ=veFkEt0#LU}wIDO2iDbYGx*q|)qEHf9*GEq6Fmigru zKjEoQrj%|`Zm_5(QxP0rkjZIHv*abMf05|Nv-U)|f~j#!%7&7obiYuv&{96GrI{^V zs?caHRw>uHa<~~nMi9QXL7uu6^URBHCP6>3e?Y11d}QY+8WxtB#beZh$kyHz+A0Fp zJMJaJ$HzOx&-pyG38#_4_cl%FN(Nk%Y^>Fsvn?C*iq{IX+f=Uk$j@;gU9W#2pD60u zv~aYzYb~rG7508rrh!z@B#S6}5;HZ1%0d9Olfx1iUO#A&&lSnu{caRq5=2#cOFpV( zKS5ot0&H(qB>HzCjezIC^@;dC%e2Z$(27?>Eiy`{kqP|yVj5Iz;UFl8609X zP)8Y*eS0RCLe0^@8+$MOm7Nr6;Mz$*4-Pqfq*%~aC!$Q?+r}qew#?DSa*RzH5_ALX zR;bU+6O+akxUhQ4#dh++?u(1?@G1+d9sx0CXe z6{`Gdv8Rc8mAHo6RPh@XlwplK!2(HP)3)zslO9 z%q6RVdTITE0n>z!ZETw%q+XwLs=h-%YT3c9{403@3O(DmBoBl112B#AK`~BG4^NYb zD5S}VuDUOUCw_0(zwYXAibjuDD&;LKew}%qLw=c+&)A89JUtt);fsfI6L8C%=T>fM zR8nYnit8+-lw)L$^)49gATp^J?BQv!eb}L@b&CEvssX@z8R<1Y-SX$W;jrz2ETyk(iX9w`q(sSE-HI2ux)`g>wD?XS4^-taJ)O^=;)}nv zp0!_D>?8-fil8}kD{MB@8X48WN@EPkZ@V%N$9lR|nF)Y?c@>Ne3pd$(&vf9qPA3WD z^U-m~>7K}ZL7U~HwWcLu6sS%fo$b^~A=7>^aexk{!p%aa@jGG|ABaTL{^%lZTc>k8 zaMg8bg!>$bzA1wmN^Cv5O?BkcG46aJ-fs_gLl_aHcV2c*5O!(*{g?Gq+8jUH5s2jI zc@^>F!y59Vv2fvn{qzJ!q-ORf+sF-W-zktrr<$0jJjrQMCt5Z*m>)L8Fb(+pI3pY5=DUyV+m1#+a|L zjr=fxRAvD=5gOj6#Fk;Ek3mEIhoRCpS3NBnxDZ&lAq3|fG)CLVu&)6lZrbKOU3FG| zX+ze%h2=F~yX?M|DQwwhso&M)i#$w3d5-MAjV99e@ZF19go6`q#hajP72qnTxn@IB zbrCUkdkRi-<1@PF?<$gWg$G69hlRWz&r-e?qOT0_K|6jcL?Tc!f+!+tW92Eq0zKS}*Qzb@ zG%`7+_9MM5pX7c=PspC6)rR0%QDlKaSCAKCFRxx*L3ju5XeVnXSKHpp@v=`L+umae zV39V<>(7m@vb}n|DS^X>L|tRv>%-^GU6*Z~sA1$j-vym0C+NmJLl7dxz@gGbXG{t3 zQVtr!e2n*4O)-(COxR+HzgcXFE0G00OTIf@b@(Z{Nn2JB!T05STZ|BV)Jm#LhJ_{k z*(H|I`js?*)D0K4C;okEY3wdWwc;*kbOyM>O(@7PTf{D;fbw5l80;VLO!AvEL24~@ z3O<1LVS|m5gd-gJAiWDAwdvng1tI@Q@aYOu3#>h~pW-=-zkE1p8wYi?Ns%@#^D@qG zx-K(vyu6ziRxIy+p68h0WirDTSDp+q9~7dZqN&iEto%mO)N_J0Q^^{@@qR@IvM|`Z zq5xW1;O7n;ANKy(eCTu6XmV1HXUe;c8PB_=Dl@)6;QF`&ax*;mfvPNNBb@P?>hms? z=l;6Dr8t{T7%tjiIXAM}ipuE^4WDb4MyPJrbD6J9fzLfy!CX3b#Qx1p523nt&f7N> z7@OYw>?FiY_mJOZ%IWVBJ;MK+2mfF1H{}B4YiY0nB<*jU*B~Ya^=}dm_)i;vW8d!e zwEx5Y;>MCK42qed7~1|t?~=m6rMkf&nD?tMX7~M@)uYV?Xh`fy7y`f`nxwW(~DNVWOcaxy97)+U(rlJ-v5S_Z%5$P9_RDHrtd(O4r@N`V7#ymZCX zdhVLo6NZ9`H}=1Q!`xgn<0rkz>`%9=Du3Y2r2AXvc&gU&&4ys~+bO<*x#% zai5-x)V?MpBv8@O$)RIlm``*BtCvd8F&cTVJw{(XEy4lJ1-jZ8-^c?S#Gg>YDu@~s zo1ldS=@q$+RSmIw2b}0y3|$BUgeJHOKxhW~|6*(IwyB_DqszCb@vzNo<03b-V0nK- zrJcr}lZp{ggNVR^xhL&f`}%|1W*Mm4z?{HtJ}lepwEF`v_*5^^q@y!zYcgw{CUoW> zjr$9o41oM7OBz3&S$?|2)&^q%MD4$+`G39V`vcs^P=KKRCY3J{02K0JSFSqq|J~Qf z19_2jK-lzScI3~>e;rbFAJ&6zGywHGCxzOkiu;jUJhPb8--sr_u*!EGwo64|qCMe| zkW|vMy>5#qSs;)x3oA#IthWLDHT^g?yvW}_8z4AoM*-neyn#TY0^mV0X4H1rD{VEe zBCB>sB$2@U9Yz6@h58AFa&WI)z(wn1Xhw6mzLfxE{&K;gFMId zis`NX|I&N?U(mLwA&qf#e`>GIT1_4+4vV3<>()nUl7<#X{sKH67$`h=HszWPlkdv% z{ACd?5!IX-5YOTN&`d7rz-R%i7tt4Im>u)ee)s$nek0a*PCZ)!7zg$rl3zukdGYL) zs)To9#s9jXqTml{?-2X%5hTZ?NvlL>ZG2Fyt=#CxrMOI-YNU}Fuvvz@oZ>&EX`H8s z7x0O^M@-K8>leIZYuA(uE6{!0K0((gkvs}Km*?ox?ZeCK*fw$0et&Y0IoGiR(cQ_L zk~a4@AxzqdQPomePcNYx2HAP!_Qkt_Fa0rAXvwGVYY8um@`6xQ=80tAKd5M)Wl6Q< zq^Gj@tDFVOrjG^TlFdPK+E3%LiaHj5T{bZEb8^)0cZJkniaA#l+m!1D?&ba=7*I(8 z;|~8s*1v=E06^wrsnDl?=ubs~-qeEYZ>bPn`m+HAs!skDIq?6;5*Ng=LW&J6u~fJgsJe(>8G+)=&FgN;tN zq%M)xaFupM+GO6_iGKW#W>KQfq^PIOc7^+=dO-vxiyFC6zVX)IceHGm5-A&*Sngdi zgOPAC@4$d{n@v5xo4V{Zq4T zkcPA6Io@?q2{bZ|0=zvF?L^SsUveWgI8~U42--ck+&g07OtaT`jriyq8!QWbMoBAV zVpg$E@wNt2Lskpfe`o=o!J-k>tTa>ymL=OZ!6rorkl31;SJFeee`~w|wTv6M!&pqD zg1P~p?E~HZ09mqy9z6YcGUPw?Y!G1k-S-O0t}P8-)kV|#xB=(H{}e6sDtYz}fySSl zdg9I3&His-ORe_)Eh2(HqB9!h)z#Ho;;E>Knokr)5R*Rr2{^@|64oS@hhI^KdzPV;0>R@ImrJUO6K9vyT2 zyzv3RCF#rch#8U?wBpPe<_D*4Vp#`}t(R?A+_4B@+c3}vL6F!?22|Dy_Ol!IYJ4vH zRS!fY`JD@oGIL||0?rL+-_4eS~#6KqViJ5Or!`0Xi%tI?~W6Y2O1HRi%na#)l1Ca0?j=yYP8hzy^?a( zi4+rCz#?61KChXNB)XAo18p*pkW{ruXbxAKgU?*$BXi2zf$=R*fn zJmIv`5z+bVlKc&sWzyq>5)fPI^Sj$%Op_r~?*XDJ(HPWXwepAyMBRFecD3~07v-tu z7w2hBj>D|POv(aq+4FMc@98e@I%noy zt^y28iyhUDR`a4otF!ss^Ns;Lh;~fs+6y;PVD|^ck0$y(KztRrO2z8^`f*0MivX!c zvwdpbe$i$bs*cXkR;8>g7}(YXrg1^j>M}1(e);SYGe;LhVa} zjYB(6Q`Et;c5vTr40QaNDA^#m65d#19=VoGmo~Z+KdrcOMuX}POse6#*LE~`JZIJZ zuMF1nwuux+^EGmR9xibRkR?BqOS=ky2$^g#X|yD|Oyg8&Sd8mZj>Xy*pQdmHwdQe> z@Or2!f8LO0>M;*`I)Uc@HI_kWx>{k9l+JEip_ZOsPf{i6E>e(Rts|zEljl!Ul7c`f zRsMdhHM=+~%p)zA$k7EIe|E$$#o1fr8JGjWs%)k{%A{}b`rWB|u?hWn{pLCfx3wJP zaDw`Tu*G*J+fBjhIed)T8~^^8ROj15`39pw{YJFd?`D1jr?D0!=>Ah7Z&&YxOfM&I zjxT~rLZW*=8}~2xo0RAlfrlCB3KZEq7S4H&x(C$asenpR?tr{{2sXb!fCOt&m*{y> zMk_MDSw^}X$Vt$`6B0ipQd@zfl9Q7|R!e1yQm0bzq948GEp!kq(X1$b>mRx$Y9(*c z+Tq=E)Ww--as3R>|4us&1=9jne{xbc_m93wIIzpU_Dh+D0&d}FN=icZvlmDr$Y76p zsI6tPTO6l1&l*TRnaxyanJ^E>rK|Fru7R=PaqmW4PU)>pl%&=$f zDH4NJ{>aQPquQlgY^EF#D|Gy=x>zKqq(1pDr1sXCg6v!;vY9XVFQF9BzT19GqX$H7 zqT{FmTJ9^iM3+aYoug+}W#bzC`r82+b*ar^hMhCzD{iouS~}hr=+c|auzS*E>ZkF$ zrK1R~zIxFmv5}JBu+kT{3mC@u&V{SBf6JZ>%-afZS@eiddpG5~^1e3BmWCLzD6h3^ zY(LYTsYML+0#^%HPoEq zEsI+w-nE$ya;q*dRbjHUIeV^^aEHGbLc*3{$Mh zhLqWTh~i~$&FbXN0Ti+ldzG6gqcLjx`lkaL^aY0UoRyZei8Tx zCrf&U)@4tX*%LM0BM_&A+Q(smk0~ z&sgp};m_dtqXKDR^4>1P`;k8`YwQeT8Qg4CSpxof$20rmfOYMht88qP?SYU}E8uoy zwp3$&dDfX={<|{&hwITi@z19Jn&p5)x;Y{_X+r^b61c4KWE_h5Mj*uActzW9@SnQ* zUSMq_nau^M^kZkf*PafuH|qlS-0tT?IK88mYDlbC&l)I}PX=qwDk9ASY#XOk4Nt7MB z{KW>z+Us-t@`N(jf87bxuhEXtnFHKZa>79=ARTlatS{i#-4 z$Y~YdaBcl7!D(xA`G&x3A0|hxG3bf8@}r`%7$Fl>+zG3sz%yjHC+%S@HzeQie^--UU@g_IkI0+e zPlc{gk>Lk4Rd{;xXilvUdwQeOx_7I-JXj#uOph=BnL?2Y1#%s67iPtN3dFp~Vsk;rcqwdGyuMUpan9kAw!W zmX}Ro7CC7WHyCG=4d{O7!2`&-6^(tFb!&d;`|56_uFESjjj;xj=r%T}r7K{fgqWO7 zR@gcyJ1Z&>Q`PF(HP1DDSY4?k!2;2id8GdD9~b?OY0QeT30N8e-1>|^A3jFsLHH@* z6H%O~e4=3BJe}Q0D3Xn@5J2MlQ3}au*rLBol+$S{GFPH2Vb^whnY$h39o*y7JC`zq zR+7-K{;`A;6v{WI%-gu;)>_TG;eOUG*7v8BfqIcLktMs`|5;@bjRTG>hPevNc?W4A z0ecn7#toO~w#Ru@jgKC$hOSFe78UljrUV)0hZdvoI_ch7exEzur=FNP(vQovl!&>} zjy7p4CRu>mc30D9Y{0yHKTdlaCQo&~kqNk;!qa^OtSEmkQb$YNnlGdX;R|sBnp}F# zFUKX-fHd=AH$`o_To32x2dd4moH_OfK)Y9wkmV`EFaZ?9`=R(}Z7uO`wJDfiW-lzm zE~O~jLnp>=lJtX{;Qzg%l?^HXGigqKS3W7IySi#ZIjhfYiQWAC+nM<_35MC$c7f9g z7S0G!qrN5M0WPD5_r8qNZ#35*?h~hMtplkQGf{kFuAJ(+`C&2{Fu~!$d}E8@rhH7n z(Ct)J>=Hmw7(1h$xm5JhaF`6qX9_3ccsBfGI*4zxmezBfe>uxTqxh;oBOymAOSMpA z?oD(6X0uL2bSQUhoeOR`Adt7)9?{7o&ze(FR1Pbf4LjnFW~sIm$QZJaBSdKs;vwiA z^rym!RI68uv1|Zk3`GulXoee=I($1uaJp^A8EQunB!5$UeZR+|vk#iJk#p{w;~6Z` zDy^kDivpbYlNO+YBbS&Ik{Oe}`YR1znkO?EF~2v$(UAjBLwd4ng42%Qjq){PyN@>bY+{XiUjdX68 z#(Ke`+u;*>QF7(Wjse&SV9J8SaEWfkQpmuZ*^Vu{1{L^Ewb{*61-@nR3whW)|B?&J zHF+?=X3?d#UMNo?Bk`f{a~0;mv>0_X1hhkp)27vlsUOm>0GER9;0^3i%P^f#zRJoa zp8?yzaV&lcgI1Shg33=Dp#_==e4< zS4!IP{UdT(uImDVie|w=_k_ZmU!*H->_rb*?$8M2mJ*kdJ%mVa$XcV!E!XlhcsYg1 zjI!Z?s3HI-n`6aa`B^X9^Au`EtPy^>{=E^|g1&1gpC4%LPGJLKB0+eWEwJl*RK(5Z z%Zu6QqT}Tf$moD}7TeiotEX@8s`*vrQ%INU!a19N6bFknXv8zp&FpFIu1f8*<3}r3 z8Dic|f52jtl+s_j;$?jDZg<1K2y!Is8PEcGT5X(q*vM;Na^p$f(=7XTM?{H1xe$}h zm1-r3mF$al;|cyYI2jzprtK&3e+>1xdqJTd<1z9>cS-Vm9uSDodoo>qG_%UY%die(()b9MjhJC;DL2~Q=&U!upD*ggZqmiV*+I#*~4ydH4F7^k@7 zeIw1lN@2IP@g~XwLpDC=bQaBG8{a+=-%}Y!uzamj@a4%`tIF_UKpa!8P?!o!#rV#+ zZ6ctujVD{+Ep=z<&e4n7)vSyrrLgmLD9vg=y7!7bI5moqyPH z+H?t%P$KQ5E29=Q*}TaTq)1>(umE=_c+yP30~Iv8Nfacuia%F6vS8$6g90PMmivWY zojSG^l^-THICP>A$wM@ksd;-kayLGb1+hZ`SfN5)bu+|wZPls%PIiO!)bnDO3G%Yd zuP`QU^=Wp>?i8qWZi)r+Tp-w7r4z@`I1gJOHR=e?M^e4trpQ1@8F79{qNS?Ym8r?i%0SI31<{o zS{XmLWGbq5hN@n8wapM-%N|T-vZ@MN1feg zG3qo8BC;E}k0m)xL4k%eCInghqloO`BQ}=9&)QA1y@hVpoAmt`9L};z73cs7xbEJV zkukK|xDcppa}!tj8SwbrMJQ*uSNgbls7v(hG(g-X_~<+?%u$TH?sAk>X!_D$3Nc#0 z#Vnx5RL&x}u;KIOcdxQs`24ZKgwOwVi6+?j{4JYPciqFfs+w_oQ3BdJ)UguyS0~>)A$)>p*}){fjL5#XNVZ*y>X5cnryJ&5hKiSUU#|CY5CTI!dKTj z%kzi$*)KUMJ0G}Ha%5uItyK7{g9UHTA(q#n3W9?W0Ch2wjB>!oA)ekC^mQPKU!DB-w7kqPGby2yICfbZ)23){@iee`S zD4>tV&=bV)w3yo{!jH2qX5UcZ@7#O+;~F;zX?F&mAKz}rAy^|mAMMbP0R_!axave< zl{EQbX^V|7_)yP_iZ^Dz^mQTAMS5EZl1| z%o5gq`*V&Sk7fzD`)N!F?aI#-D*aBgG#YiPDsn&gu`uIPO9+pWjd5;Q#kPoRo@@Ga zdwMcYMKf}@;YTH+M96Y+p=5pxLz3aDdQ9xGucr0OF7ZIaj~yOWkP5Lqt%*REt~M z01hH=rC#HBSvdIh4bP4e&4H%*yJYuupo&+V7+76*#*YH?s_G*n&ljW_yr7twSdr&S zH}K=SQg$Urk|?!U1lWocf1z8|elM6Sk=Jxv{wRm`-IQOyLj3QCgP`8_C)t=;7>x8C z*88oCtX8;fM{fd<`Dmx^V!b4pTO6vIa}0e>tvv0I!~J${9etNUG40w$GcoIO7@b^1 zfhaD6W*iL#7}}g>6~`7Npn}FtZMf?i8vC!XE~of zR#S&dQOIPAq8o}hTXBQ_{$D+|=BezbDG^DT?I((-PZFh!f~1AtT_ok819Qh z3VJwNxmW#<=E`^0Ly4vtTsMU8*g=X_Pj<7k&J$BnQ#qupoZ+LK*IM1 z1GocBxgPy-K^uE2oLh;l&?w z;lT?wVS!oLoDSF)dT0oiY7f7w4GRI68q~JoH!_msHBrbrF8+#1&w1*+`d?h1NZ+1! zma*k}C%Z#Zr|Z4W8fU$xhC*Tui*iSEYC*VtpXN#Y@gvbb+P zvm7nO!0Fq?a^Q_$fSdC8qiF*jHm$S0YxdMG=MEP{iUlFU_JI4@I>u~uE{5qPi$-)3 z&JC}?pz&0HH5ulBAp-$-Q;(c+=<)iAqN!X+Bpd%nROlc`w{F-(&JdWb^j{nZPa^KB z+6%YFhk}-(A~Pl-M@*eR#9Ih9DujO4*l0O5_{-dM2<6?)UF^_Y7JS`|0)@@gBAW2N z03wCwcHTWLcL4_#?>!e#P zA>2KMs>X%^!}|)vK~#td@x(svZ7S8IMoDlK5*k zDd_Ne-jy-19g3^736eN$kVCL+D>#;umO5g&E{TT|Vj44WO$+Rt6FAOl!)+Ab?X1tv zGCtKFsdZH3OCVh&I-F%bUuix_tVxwaUZJUvTy%rmp}L?$j?`O>9lG6FL|E=JYw7sP zK_Nv~PfI4e`f}I>JsiX~Go4v~&Y94$kA(uUr<#g^(>Be-w!M*oWqfnr$eXxLwn*6@ zbcV0MvKBKeov0uSo^Y+0cb8$h>))0HqVd}x*oibfQa4u#;`C>loI39dRyp^PoGBir}Eb(q>ToGy{iEK^zO z%dMI=JJgMst?4}nTbVqK^j<6%(vu)@56w0FsSGGpJNf1^zyi4YsCbU=XwEfjY7}oPtSvkS~)#PEb%t*akkoB)*0DJRg99Y^~=3o~>aZ z1egEUWRJR<9M&*=$+7t7QUQ_Q9o}c` z+8?eczW7HBm^FRyw1ZYaEmQcya7&}Q?np9rf8rkP`GNFpdc5l8NuP*OwQXGR>RORx zZK{twGd{CoYn}5Tgy{XGkPb16_v zq)-={g^vwmm02=WXPN81ieC^A8+^$_DWq7K@1=taAqYGb@`8V*rHmsAjAF?Qz!t{d#B^VbUoSe;hpK&>5BJsSoV;x_KJRke+f)hL zt~EN#ZQcfW4z}KgWfZ)1b>pM7j+O;oBI8Ubuyjmfk8u)tEm8H?1bQ@;R<;i5)Jv}m z^%zj|JV{gSPf2I6v>eN0VxxZu21J6BG;U< zEnfSoSO$HN3+KUM76ygWH$P1pr;tbSpA&$Pf_}*^BEvbI`fidrcK`!`FRg~7_bu3Q zTzJ4*jOfp^0B5LPkXezWc}c2>lhivR(n0oi2`q-F)|e-7xZsMpaYk57r6!e_mrehV z1190|fiX3!!v_ZZBXtKHkPk4%)shXCnf~_RcjwjVoMoorlsjHXk*x7zWZF1cf`PF7 z1Lthf%3wMzlVSnNeAZ*v+AyoFo%f$Jy^pOt`nnW)HI65p6oiCtg+`V(x}8Exj}m`& zrZ@CZ;bj>%nD)*eWRgGNhUtilGybyD+hxby8-IKrgK=i$wg+Q>;IW@<#zG}-yd`oJ8m%pX_4o#mMts>Al9290512+58 ztCA3XhjDusx_*Y*mhrC=ORAc%n2PRpYIy{|9m#cLSj1v$EU~BHb~*+%2}#oI+qnCE zW=#~iLAWEbwAZh#_UWz-?tC--j}Q{si)3Y)N5b>5pL{U+214B`!f#-|N^P!xjg_BJ zY;RuTxt)=m*^X^_L&}#FXA6ytN?M(_J-Xl?J8h~$Dl?yWd6L_ix5IGYJL-uuSg-lA zL(zZb!`}#TG$BlE9Tpi>fNNVFzg9n~%496!C!*|m!mcf2B(b#k$UCM7UL{M;!OB=t zKFc1WGYz!It`p+uqr173oCUJ&Y_2T2MS)>*wS@PT=wn8km?{kM&P4ph#%Mmb4=Jld z-K;M0Gf0ccD)J<+B{Fx2jW6r%Ed)|6SzfY`KCJo#zusG_Rq3P2ybf6!rI$@?`j=WH zN$b~Qsf^7%eCV2%}f zO?>i_?zeDRUA!5$<;`&h_i*nm-RVsKg|O4TqYKg9EJM!2Fr|ifCd(*MGi;en$TZP@ zUL(l<2&Zw}e^klFc5)v`nJn_gKIfvi!_%gE9QW-W>0q)WE2`KpR^7S{ATI$0kIId|N++u)y|5)GiP_3 zk9Ct7DPyZ ztG)xrYGTnaK~BUT!IB2ggD0blD)UeTju)CDx`!DVddKT&_UGvT~{ZSZ@`~G?+2v#=`GU-{zh&8zc|?a`*f8odR655 z&zek=FV3LG~)5bvymqJLmNOcQJ$iI$JcAT);$FIJUcTnvjI#%vqx1pGe>yQBpp>C6LLz>%?s{ z*89I%+c;4)QlQWPY^E78`Cllm=r|C{aXcQozf}OLkbj~KPLme?H_jTz0a*My_ebmf z?JSN725gG#lG`Ku+v@vw6mYpS9e1?h$!qcss+q%3rsMswAwWjEq9)rTGu><{C1g?Kq$;c`6Et zFf~APC4Tf0{oDK$3b@39;87hIqq8QizlZF3JltVt8A^w~mo%yHM`Are%m?(3E8vf?? zN&l2`2*+e$_;-c%|8BKk%z)NG@o-#y^7m!_QxAjhhsRarB?Ju0&`*t`QDhVnKKlC; z{bYZpt!!CRR&XfCtZm|@7F!u(B$q^fr|V(>=~<`_sij0Eh6HM#Z~3jSfP@QWVu=xA zp|Pj52}S%?eGP#Pc$lGCt+#_D&e-XlnUoDn)y|EKdd=L$zBR0HW-?u#N+beqg00)l zA3AwlI|&6{c;CAk?>s~%XFRbkVC_mkt#>S4WQA-clCCtIB_`0+3M<4M32aU@zx|d# zXpW4FnNe(ekTKpPx%y$bpsZda36~~h|BaB(P4RpFcSx2abgBr2))4IUn0B96t}ou? zfi|$1zg)fU&(R(%m*@dpr$qALWlLqgY=t(OV4g!tz2Pi=Os{d|APfD)w#St+?hG=X z$L<^8rJV619>}a+mfGA<`o5geR&N00?cF4S6Npv1FamJIl+@JHF(e{+GasiVb7+tV zX2-CnR-qIM#M(dXWf7H2{DfquB}8cVAge<~sT z1RUZRbsIh`ANcIbV;z6y5b|VKx;<7JtBVp5|07%b)P(I(?lJjmu9V5js>p0^`KA!( zFlzj(dWZfJxIF=ZD0s3c>4Mc^2gDzE`YNblrTZ}``%_z87K@Dwdco-mOMyZP`%2ex z7|t`>%O{~9?eiW>nM=)CYV@kuH=^#sxkvO`gZrBDOVtYTJT}Qd5>D3UVq3jP8|kNR zd&ydR+UbhFXv7hJ-Pkah*04efJ)KT?tof*hENLPnFo&od*72>4B=LVx8lOCBjWT?z zlFdyhPTau{8IV-0#%(8w$*{(_(;h;vUarQ^`1#et{>SNt{W=crTYNkRH~^|AD$A9R z^?MXPw>zW>+kdC3@UwLf&7R;9EK<&rPiU#PZpvX&ZMh@ir8U>&qsyBRge_Ajx)$8R zgYACkXLrdIFt@#=cJ2VMu=1OvS!Jwj6&jLp7@1so^93%XBO1&WAeA8({!9B*Ua-=# z_2b5C@MA~R)De$%<$&oP|BX8mP2H!%4sDM0QrzIX#3uplyT0z;@PdyIPHsFF@Ub;Q z-gj~jm!U`AjgfB40-ZcvX8~?sl3aX(bx=DN7jH86{@pW2rj~!t&sS6-^2*||S*9-6 zBU0t1g=_3_++d-$b&KJ6?HS=ovjQVJr1Q)#nwblseedc(>) z)38#Hsa}j**#VZ&RPKH7ah`;ua%ad*IN36op+Nc3e z)+Sr-9(u8kO6m;7ccb)jQE8)gpEQKZ=OBWse+PQr!S@&ozxz(A$gO-#=y_r~sYdF| z(*xTxf`vPK_g=sQ=vjj;vA^kcab>Wjp?MxgXy#`v(0%;&xj545qquWmsLk@|(sY?) zX4y)Wt^tm9M#|4nh!JMYz0;yb9&L+OLXEajZ+8(TCY!#fo+Evj(7co|E>W?rNNhtq z)qo5hYf1)fnpP1}0PX(0Zu2TK@jyOrqy%VCZ^15Ze1cn#Dm%vkJEJJo3(@ zlbb}f^@qs$Kk96y-oJ>ZR!8}R%iI`5#nc;k-=r{K&mRckx}b9H{EC z9OqZIyOy=a1zd|4yQMbs*6T@Rb{9-W?M7Q%Fw)n>MSi|9K47HFXNlve*JF?<4!)(_ zWmxo7|Nh||t^Sbz|KaSdgW~8KZEr##KyXMPKyY_=hv4q+7Tnzl5*&g%1Pku&?iSo# zg8QJuZSuV5JLlf#{&T-7Dn-?Vn(6M{(|fP|TWjf7dn$n>(CesjF|pUs<$vZtWefju z`tw8MgXw2#zRx)fp6HES8uK$A*q~aUZB--tTh%!_VOw^ZWfb)!U_)@VsFS8=uQZdoEQeg>T0SH~pO)#02#Biv2V**pFPSY}PWdxZI%ReL}7W3cy7RwP9W- z`q7|Ks|Wq3jdc=vV#N`bf%5l(I)gWZbr2?O>VTDl7_)QyIxpRK%#pN%s~7}(7~+a3 zB!?mkT~zQm+fbVl9q*g^t05GzNg!=X;*3&j#uI8CgYCK?%41U_e-RUBg6odk4C

%(01wv&Xsmw>>J4o@mW^%CFQt}AQa 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 Date: Fri, 31 Oct 2025 09:20:23 +0000 Subject: [PATCH 066/113] Fix image links --- docs/hello_nf-core/05_input_validation.md | 38 ++++++++++++++++++++--- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/docs/hello_nf-core/05_input_validation.md b/docs/hello_nf-core/05_input_validation.md index eff53186bc..ec7dfb1800 100644 --- a/docs/hello_nf-core/05_input_validation.md +++ b/docs/hello_nf-core/05_input_validation.md @@ -130,7 +130,7 @@ grep -A 25 '"input_output_options"' nextflow_schema.json The parameter schema is organized into groups. Here's the `input_output_options` group: -```json title="core-hello/nextflow_schema.json (excerpt)" +```json title="core-hello/nextflow_schema.json (excerpt)" linenums="8" "input_output_options": { "title": "Input/output options", "type": "object", @@ -206,7 +206,7 @@ Type `y` and press Enter to launch the interactive web interface. Your browser will open showing the Parameter schema builder: -![Schema builder interface](../img/hello_nf-core/schema_build.png) +![Schema builder interface](./img/hello_nf-core/schema_build.png) To add the `batch` parameter: @@ -219,7 +219,7 @@ To add the `batch` parameter: - Check the **Required** checkbox - Optionally, select an icon from the icon picker (e.g., `fas fa-layer-group`) -![Adding the batch parameter](../img/hello_nf-core/schema_add.png) +![Adding the batch parameter](./img/hello_nf-core/schema_add.png) When you're done, click the **"Finished"** button at the top right. @@ -232,7 +232,37 @@ INFO Writing schema with 18 params: 'nextflow_schema.json' Press `Ctrl+C` to exit the schema builder. -The tool has now updated your `nextflow_schema.json` file with the new `batch` parameter, handling all the JSON Schema syntax correctly. +The tool has now updated your `nextflow_schema.json` file with the new `batch` parameter, handling all the JSON Schema syntax correctly. You can verify this by checking the file: + +```bash +grep -A 25 '"input_output_options"' nextflow_schema.json +``` + +```json title="core-hello/nextflow_schema.json (excerpt)" linenums="8" hl_lines="19-23" + "input_output_options": { + "title": "Input/output options", + "type": "object", + "fa_icon": "fas fa-terminal", + "description": "Define where the pipeline should find input data and save output data.", + "required": ["input", "outdir", "batch"], + "properties": { + "input": { + "type": "string", + "format": "file-path", + "exists": true, + "schema": "assets/schema_input.json", + "mimetype": "text/csv", + "pattern": "^\\S+\\.csv$", + "description": "Path to comma-separated file containing information about the samples in the experiment.", + "help_text": "You will need to create a design file with information about the samples in your experiment before running the pipeline. Use this parameter to specify its location. It has to be a comma-separated file with 3 columns, and a header row.", + "fa_icon": "fas fa-file-csv" + }, + "batch": { + "type": "string", + "description": "Name for this batch of greetings", + "fa_icon": "fas fa-layer-group" + }, +``` ### 2.3. Test parameter validation From e243af6479807abd47bf31e6e272bccc423b8cca Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 31 Oct 2025 09:26:39 +0000 Subject: [PATCH 067/113] Fix image paths in input validation docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed image paths from ./img/hello_nf-core/ to ./img/ to match the convention used in other hello_nf-core lessons. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/05_input_validation.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/hello_nf-core/05_input_validation.md b/docs/hello_nf-core/05_input_validation.md index ec7dfb1800..6af72a93a0 100644 --- a/docs/hello_nf-core/05_input_validation.md +++ b/docs/hello_nf-core/05_input_validation.md @@ -206,7 +206,7 @@ Type `y` and press Enter to launch the interactive web interface. Your browser will open showing the Parameter schema builder: -![Schema builder interface](./img/hello_nf-core/schema_build.png) +![Schema builder interface](./img/schema_build.png) To add the `batch` parameter: @@ -219,7 +219,7 @@ To add the `batch` parameter: - Check the **Required** checkbox - Optionally, select an icon from the icon picker (e.g., `fas fa-layer-group`) -![Adding the batch parameter](./img/hello_nf-core/schema_add.png) +![Adding the batch parameter](./img/schema_add.png) When you're done, click the **"Finished"** button at the top right. @@ -278,7 +278,9 @@ nextflow run . --outdir test-results -profile docker ERROR ~ Validation of pipeline parameters failed! -- Check '.nextflow.log' file for details - * --input: required property is missing +The following invalid input values have been detected: + +* Missing required parameter(s): input, batch ``` Perfect! The validation catches the missing required parameter before the pipeline runs. From 3de3c4e458bc062da66f310df7dc196950ccf904 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 31 Oct 2025 09:53:12 +0000 Subject: [PATCH 068/113] Fix validation configuration approach MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced incorrect --validationSchemaIgnoreParams command-line flag with proper nextflow.config validation block configuration. Added before/after code blocks showing how to temporarily ignore input validation during section 2 and re-enable it in section 3 after configuring the input data schema. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/05_input_validation.md | 72 +++++++++++++++++++++-- 1 file changed, 66 insertions(+), 6 deletions(-) diff --git a/docs/hello_nf-core/05_input_validation.md b/docs/hello_nf-core/05_input_validation.md index 6af72a93a0..279412b5ca 100644 --- a/docs/hello_nf-core/05_input_validation.md +++ b/docs/hello_nf-core/05_input_validation.md @@ -264,7 +264,42 @@ grep -A 25 '"input_output_options"' nextflow_schema.json }, ``` -### 2.3. Test parameter validation +You should see that the `batch` parameter has been added to the schema with the "required" field now showing `["input", "outdir", "batch"]`. + +### 2.3. Configure validation to skip input file validation + +Since we haven't configured the input data schema yet (that comes in section 3), we need to temporarily tell nf-schema to ignore validation of the `input` parameter's file contents. + +Open `nextflow.config` and find the `validation` block (around line 246). Add `ignoreParams` to skip input file validation: + +=== "After" + + ```groovy title="nextflow.config" hl_lines="3" linenums="246" + validation { + defaultIgnoreParams = ["genomes"] + ignoreParams = ['input'] + monochromeLogs = params.monochrome_logs + } + ``` + +=== "Before" + + ```groovy title="nextflow.config" linenums="246" + validation { + defaultIgnoreParams = ["genomes"] + monochromeLogs = params.monochrome_logs + } + ``` + +This tells nf-schema to skip validating the contents of the file provided via `--input`, while still validating all other parameters. + +!!! note "Why ignore the input parameter?" + + The `input` parameter in `nextflow_schema.json` has `"schema": "assets/schema_input.json"` which tells nf-schema to validate the *contents* of the input CSV file against that schema. + Since we haven't configured that schema yet, we temporarily ignore this validation. + We'll remove this in section 3 after setting up the input data schema. + +### 2.4. Test parameter validation Now let's test that parameter validation works correctly. @@ -288,11 +323,9 @@ Perfect! The validation catches the missing required parameter before the pipeli Now try with a valid set of parameters: ```bash -nextflow run . --input assets/greetings.csv --outdir results --batch my-batch -profile test,docker --validationSchemaIgnoreParams input +nextflow run . --input assets/greetings.csv --outdir results --batch my-batch -profile test,docker ``` -Note: We use `--validationSchemaIgnoreParams input` to skip input data validation at this stage since we haven't configured the input schema yet (we'll do that in the next section). - The pipeline should run successfully, and the `batch` parameter is now validated. ### Takeaway @@ -522,9 +555,36 @@ You've successfully implemented input data validation using `samplesheetToList` ### What's next? -Test both parameter and input data validation to see them in action. +Re-enable input validation in the config and test both parameter and input data validation to see them in action. + +### 3.6. Re-enable input validation + +Now that we've configured the input data schema, we can remove the temporary ignore setting we added in section 2.3. + +Open `nextflow.config` and remove the `ignoreParams` line from the `validation` block: + +=== "After" + + ```groovy title="nextflow.config" linenums="246" + validation { + defaultIgnoreParams = ["genomes"] + monochromeLogs = params.monochrome_logs + } + ``` + +=== "Before" + + ```groovy title="nextflow.config" hl_lines="3" linenums="246" + validation { + defaultIgnoreParams = ["genomes"] + ignoreParams = ['input'] + monochromeLogs = params.monochrome_logs + } + ``` + +Now nf-schema will validate both parameter types AND the input file contents. -### 3.6. Test input validation +### 3.7. Test input validation Let's verify that our validation works by testing both valid and invalid inputs. From 35d3206acb461351d9366d85d0bc74c34db6df9a Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 31 Oct 2025 10:06:55 +0000 Subject: [PATCH 069/113] Improve schema_input.json section with before/after MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed section 3.3 to use before/after tabs showing the transition from the template's default paired-end sequencing schema to the simpler greetings schema. This helps users understand what they're changing and why. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/05_input_validation.md | 102 +++++++++++++++------- 1 file changed, 72 insertions(+), 30 deletions(-) diff --git a/docs/hello_nf-core/05_input_validation.md b/docs/hello_nf-core/05_input_validation.md index 279412b5ca..8fd753d010 100644 --- a/docs/hello_nf-core/05_input_validation.md +++ b/docs/hello_nf-core/05_input_validation.md @@ -374,40 +374,82 @@ For our use case, we want to: We'll structure this as an array of objects, where each object has a `greeting` field. -### 3.3. Create the schema file - -Replace the contents of `assets/schema_input.json` with the following: - -```json title="assets/schema_input.json" linenums="1" -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://raw.githubusercontent.com/core/hello/main/assets/schema_input.json", - "title": "core/hello pipeline - params.input schema", - "description": "Schema for the greetings file provided with params.input", - "type": "array", - "items": { - "type": "object", - "properties": { - "greeting": { - "type": "string", - "pattern": "^\\S.*$", - "errorMessage": "Greeting must be provided and cannot be empty or start with whitespace" - } - }, - "required": ["greeting"] - } -} -``` +### 3.3. Update the schema file + +The nf-core pipeline template includes a default `assets/schema_input.json` designed for paired-end sequencing data. +We need to replace it with a simpler schema for our greetings use case. + +Open `assets/schema_input.json` and replace the `properties` and `required` sections: + +=== "After" + + ```json title="assets/schema_input.json" linenums="1" hl_lines="10-14 16" + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/core/hello/main/assets/schema_input.json", + "title": "core/hello pipeline - params.input schema", + "description": "Schema for the greetings file provided with params.input", + "type": "array", + "items": { + "type": "object", + "properties": { + "greeting": { + "type": "string", + "pattern": "^\\S.*$", + "errorMessage": "Greeting must be provided and cannot be empty or start with whitespace" + } + }, + "required": ["greeting"] + } + } + ``` + +=== "Before" + + ```json title="assets/schema_input.json" linenums="1" hl_lines="10-29 31" + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/core/hello/main/assets/schema_input.json", + "title": "core/hello pipeline - params.input schema", + "description": "Schema for the file provided with params.input", + "type": "array", + "items": { + "type": "object", + "properties": { + "sample": { + "type": "string", + "pattern": "^\\S+$", + "errorMessage": "Sample name must be provided and cannot contain spaces", + "meta": ["id"] + }, + "fastq_1": { + "type": "string", + "format": "file-path", + "exists": true, + "pattern": "^([\\S\\s]*\\/)?[^\\s\\/]+\\.f(ast)?q\\.gz$", + "errorMessage": "FastQ file for reads 1 must be provided, cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" + }, + "fastq_2": { + "type": "string", + "format": "file-path", + "exists": true, + "pattern": "^([\\S\\s]*\\/)?[^\\s\\/]+\\.f(ast)?q\\.gz$", + "errorMessage": "FastQ file for reads 2 cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" + } + }, + "required": ["sample", "fastq_1"] + } + } + ``` -Let's break down the key parts: +The key changes: -- **`type: "array"`**: The input is parsed as an array (list) of items -- **`items.type: "object"`**: Each item in the array is an object -- **`properties.greeting`**: Defines a field called `greeting` +- **`description`**: Updated to mention "greetings file" +- **`properties`**: Replaced `sample`, `fastq_1`, and `fastq_2` with a single `greeting` field - **`type: "string"`**: Must be a text string - **`pattern: "^\\S.*$"`**: Must start with a non-whitespace character (but can contain spaces after that) - **`errorMessage`**: Custom error message shown if validation fails -- **`required: ["greeting"]`**: The `greeting` field is mandatory +- **`required`**: Changed from `["sample", "fastq_1"]` to `["greeting"]` ### 3.4. Add a header to the greetings.csv file @@ -419,7 +461,7 @@ Add a header line to the greetings file: === "After" - ```csv title="assets/greetings.csv" linenums="1" + ```csv title="assets/greetings.csv" linenums="1" hl_lines="1" greeting Hello Bonjour From ef9cfd3acabda2dfd89e4fe9d09713a8712ab5d4 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 31 Oct 2025 10:12:03 +0000 Subject: [PATCH 070/113] Replace file link with backticked string MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed file path from markdown link to backticks to match repository conventions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/05_input_validation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hello_nf-core/05_input_validation.md b/docs/hello_nf-core/05_input_validation.md index 8fd753d010..31799c8bfd 100644 --- a/docs/hello_nf-core/05_input_validation.md +++ b/docs/hello_nf-core/05_input_validation.md @@ -499,7 +499,7 @@ The `samplesheetToList` function: Let's update the input handling code: -Open [core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf](core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf) and locate the section where we create the input channel (around line 64). +Open `core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf` and locate the section where we create the input channel (around line 64). We need to: From 2da8036415f18b309170a3c377b441915db3747d Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 31 Oct 2025 10:35:34 +0000 Subject: [PATCH 071/113] Reorganize validation configuration section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moved validation configuration content from the oddly-placed section 3.5 note to a new section 1.4 that explains how nf-schema is installed and configured in nf-core pipelines. This section now includes: - Explanation of how nf-schema is pre-installed in the template - The validation{} configuration block - The before/after edit to add ignoreParams for input validation - Context for why we temporarily disable input file validation Removed the duplicate content from section 2.3 (now 2.3 is just testing). This provides better flow: users configure validation upfront in section 1, then work through parameter validation in section 2, and finally re-enable input validation in section 3. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/05_input_validation.md | 106 +++++++++++----------- 1 file changed, 55 insertions(+), 51 deletions(-) diff --git a/docs/hello_nf-core/05_input_validation.md b/docs/hello_nf-core/05_input_validation.md index 31799c8bfd..c3d5474c34 100644 --- a/docs/hello_nf-core/05_input_validation.md +++ b/docs/hello_nf-core/05_input_validation.md @@ -106,9 +106,54 @@ graph LR Validation happens **before** any pipeline processes run, providing fast feedback and preventing wasted compute time. +### 1.4. Configure validation to skip input file validation + +The nf-core pipeline template comes with nf-schema already installed and configured: + +- The nf-schema plugin is installed via the `plugins{}` block in `nextflow.config` +- Parameter validation is enabled by default via `params.validate_params = true` +- The validation is performed by the `UTILS_NFSCHEMA_PLUGIN` subworkflow during pipeline initialization + +The validation behavior is controlled through the `validation{}` scope in `nextflow.config`. + +Since we'll be working on parameter validation first (section 2) and won't configure the input data schema until section 3, we need to temporarily tell nf-schema to skip validating the `input` parameter's file contents. + +Open `nextflow.config` and find the `validation` block (around line 246). Add `ignoreParams` to skip input file validation: + +=== "After" + + ```groovy title="nextflow.config" hl_lines="3" linenums="246" + validation { + defaultIgnoreParams = ["genomes"] + ignoreParams = ['input'] + monochromeLogs = params.monochrome_logs + } + ``` + +=== "Before" + + ```groovy title="nextflow.config" linenums="246" + validation { + defaultIgnoreParams = ["genomes"] + monochromeLogs = params.monochrome_logs + } + ``` + +This configuration tells nf-schema to: + +- **`defaultIgnoreParams`**: Skip validation of complex parameters like `genomes` (set by template developers) +- **`ignoreParams`**: Skip validation of the `input` parameter's file contents (temporary - we'll remove this in section 3) +- **`monochromeLogs`**: Control colored output in validation messages + +!!! note "Why ignore the input parameter?" + + The `input` parameter in `nextflow_schema.json` has `"schema": "assets/schema_input.json"` which tells nf-schema to validate the *contents* of the input CSV file against that schema. + Since we haven't configured that schema yet, we temporarily ignore this validation. + We'll remove this setting in section 3 after configuring the input data schema. + ### Takeaway -You now understand what nf-schema does, the two types of validation it provides, and when validation occurs in the pipeline execution lifecycle. +You now understand what nf-schema does, the two types of validation it provides, when validation occurs, and how to configure validation behavior. You've also temporarily disabled input file validation so we can focus on parameter validation first. ### What's next? @@ -266,40 +311,7 @@ grep -A 25 '"input_output_options"' nextflow_schema.json You should see that the `batch` parameter has been added to the schema with the "required" field now showing `["input", "outdir", "batch"]`. -### 2.3. Configure validation to skip input file validation - -Since we haven't configured the input data schema yet (that comes in section 3), we need to temporarily tell nf-schema to ignore validation of the `input` parameter's file contents. - -Open `nextflow.config` and find the `validation` block (around line 246). Add `ignoreParams` to skip input file validation: - -=== "After" - - ```groovy title="nextflow.config" hl_lines="3" linenums="246" - validation { - defaultIgnoreParams = ["genomes"] - ignoreParams = ['input'] - monochromeLogs = params.monochrome_logs - } - ``` - -=== "Before" - - ```groovy title="nextflow.config" linenums="246" - validation { - defaultIgnoreParams = ["genomes"] - monochromeLogs = params.monochrome_logs - } - ``` - -This tells nf-schema to skip validating the contents of the file provided via `--input`, while still validating all other parameters. - -!!! note "Why ignore the input parameter?" - - The `input` parameter in `nextflow_schema.json` has `"schema": "assets/schema_input.json"` which tells nf-schema to validate the *contents* of the input CSV file against that schema. - Since we haven't configured that schema yet, we temporarily ignore this validation. - We'll remove this in section 3 after setting up the input data schema. - -### 2.4. Test parameter validation +### 2.3. Test parameter validation Now let's test that parameter validation works correctly. @@ -499,7 +511,7 @@ The `samplesheetToList` function: Let's update the input handling code: -Open `core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf` and locate the section where we create the input channel (around line 64). +Open `subworkflows/local/utils_nfcore_hello_pipeline/main.nf` and locate the section where we create the input channel (around line 80). We need to: @@ -551,15 +563,12 @@ Now update the channel creation code: === "After" - ```groovy title="core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf" linenums="64" hl_lines="4-8" + ```groovy title="core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf" linenums="80" hl_lines="4" // // Create channel from input file provided through params.input // - ch_samplesheet = Channel.fromList(samplesheetToList(params.input, "${projectDir}/assets/schema_input.json")) - .map { row -> - // Extract just the greeting string from each row - row[0] - } + ch_samplesheet = channel.fromList(samplesheetToList(params.input, "${projectDir}/assets/schema_input.json")) + .map { line -> line[0] } emit: samplesheet = ch_samplesheet @@ -568,13 +577,13 @@ Now update the channel creation code: === "Before" - ```groovy title="core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf" linenums="64" + ```groovy title="core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf" linenums="80" hl_lines="4 5" // // Create channel from input file provided through params.input // - ch_samplesheet = Channel.fromPath(params.input) - .splitCsv() - .map { line -> line[0] } + ch_samplesheet = channel.fromPath(params.input) + .splitCsv() + .map { line -> line[0] } emit: samplesheet = ch_samplesheet @@ -585,11 +594,6 @@ Let's break down what changed: 1. **`samplesheetToList(params.input, "${projectDir}/assets/schema_input.json")`**: Validates the input file against our schema and returns a list 2. **`Channel.fromList(...)`**: Converts the list into a Nextflow channel -3. **`.map { row -> row[0] }`**: Extracts just the greeting string from each validated row (accessing the first column by index) - -!!! note "Parameter validation is enabled by default" - - The nf-schema plugin is installed via the `plugins{}` block in `nextflow.config`, and the pipeline template already includes parameter validation enabled via `params.validate_params = true`. The validation is performed by the `UTILS_NFSCHEMA_PLUGIN` subworkflow during pipeline initialization. ### Takeaway From cdebcd24543d78b08f4e3dfb6e439fb728ff3749 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 31 Oct 2025 10:52:13 +0000 Subject: [PATCH 072/113] Polish validation docs for consistency and clarity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fixed test command to use 'nextflow run .' consistently - Fixed cross-reference from section 3.6 (was 2.3, should be 1.4) - Improved clarity of verification step with bold header - Updated test output examples to be more accurate 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/05_input_validation.md | 64 +++++++---------------- 1 file changed, 20 insertions(+), 44 deletions(-) diff --git a/docs/hello_nf-core/05_input_validation.md b/docs/hello_nf-core/05_input_validation.md index c3d5474c34..603e9575e6 100644 --- a/docs/hello_nf-core/05_input_validation.md +++ b/docs/hello_nf-core/05_input_validation.md @@ -277,7 +277,9 @@ INFO Writing schema with 18 params: 'nextflow_schema.json' Press `Ctrl+C` to exit the schema builder. -The tool has now updated your `nextflow_schema.json` file with the new `batch` parameter, handling all the JSON Schema syntax correctly. You can verify this by checking the file: +The tool has now updated your `nextflow_schema.json` file with the new `batch` parameter, handling all the JSON Schema syntax correctly. + +**Verify the changes:** ```bash grep -A 25 '"input_output_options"' nextflow_schema.json @@ -605,7 +607,7 @@ Re-enable input validation in the config and test both parameter and input data ### 3.6. Re-enable input validation -Now that we've configured the input data schema, we can remove the temporary ignore setting we added in section 2.3. +Now that we've configured the input data schema, we can remove the temporary ignore setting we added in section 1.4. Open `nextflow.config` and remove the `ignoreParams` line from the `validation` block: @@ -639,41 +641,27 @@ Let's verify that our validation works by testing both valid and invalid inputs. First, confirm the pipeline runs successfully with valid input: ```bash -nextflow run core-hello --outdir core-hello-results -profile test,docker +nextflow run . --outdir core-hello-results -profile test,docker ``` Note that we no longer need `--validate_params false` since validation is working! ```console title="Output" - N E X T F L O W ~ version 25.04.3 - -Launching `./main.nf` [nasty_kalman] DSL2 - revision: c31b966b36 - -Input/output options - input : /private/tmp/core-hello-test/assets/greetings.csv - batch : test - outdir : core-hello-results +------------------------------------------------------ +WARN: The following invalid input values have been detected: -Institutional config options - config_profile_name : Test profile - config_profile_description: Minimal test dataset to check pipeline function +* --character: tux -Core Nextflow options - runName : nasty_kalman - containerEngine : docker - profile : test,docker -!! Only displaying parameters that differ from the pipeline defaults !! ------------------------------------------------------- -executor > local (7) -[cc/cc800d] CORE_HELLO:HELLO:sayHello (1) | 3 of 3 ✔ -[d6/46ab71] CORE_HELLO:HELLO:convertToUpper (1) | 3 of 3 ✔ -[b2/3def99] CORE_HELLO:HELLO:CAT_CAT (test) | 1 of 1 ✔ -[a3/f82e41] CORE_HELLO:HELLO:cowpy | 1 of 1 ✔ +executor > local (10) +[c1/39f64a] CORE_HELLO:HELLO:sayHello (1) | 4 of 4 ✔ +[44/c3fb82] CORE_HELLO:HELLO:convertToUpper (4) | 4 of 4 ✔ +[62/80fab2] CORE_HELLO:HELLO:CAT_CAT (test) | 1 of 1 ✔ +[e1/4db4fd] CORE_HELLO:HELLO:cowpy | 1 of 1 ✔ -[core/hello] Pipeline completed successfully- ``` -Great! The pipeline runs successfully and validation passes silently. +Great! The pipeline runs successfully and validation passes silently. The warning about `--character` is just informational since it's not defined in the schema. If you want, use what you've learned to add validation for that parameter too! **Test with invalid input:** @@ -693,34 +681,22 @@ This file uses `message` as the column name instead of `greeting`, which doesn't Try running the pipeline with this invalid input: ```bash -nextflow run core-hello --input /tmp/invalid_greetings.csv --outdir test-results -profile docker +nextflow run . --input /tmp/invalid_greetings.csv --outdir test-results -profile docker ``` ```console title="Output" - N E X T F L O W ~ version 25.04.3 - -Launching `./main.nf` [stupefied_poincare] DSL2 - revision: c31b966b36 - -Input/output options - input : /tmp/invalid_greetings.csv - outdir : test-results - -Core Nextflow options - runName : stupefied_poincare - containerEngine : docker - profile : docker - -!! Only displaying parameters that differ from the pipeline defaults !! ------------------------------------------------------- ERROR ~ Validation of pipeline parameters failed! -- Check '.nextflow.log' file for details The following invalid input values have been detected: +* Missing required parameter(s): batch * --input (/tmp/invalid_greetings.csv): Validation of file failed: - -> Entry 1: Missing required field(s): greeting + -> Entry 1: Missing required field(s): greeting + -> Entry 2: Missing required field(s): greeting + -> Entry 3: Missing required field(s): greeting - -- Check script 'subworkflows/nf-core/utils_nfschema_plugin/main.nf' at line: 39 or see '.nextflow.log' file for more details + -- Check script 'subworkflows/nf-core/utils_nfschema_plugin/main.nf' at line: 68 or see '.nextflow.log' file for more details ``` Perfect! The validation caught the error and provided a clear, helpful error message pointing to: From 06a246db3fbc34b34020cfc11ffdd0a2f9435265 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 31 Oct 2025 10:55:24 +0000 Subject: [PATCH 073/113] Add context about Hello Nextflow workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added a "What does the Hello Nextflow workflow do?" subsection at the start of section 2 to provide context for users who may not have completed the Hello Nextflow training course. This explains the workflow's purpose and functionality before diving into making it composable. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/02_rewrite_hello.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index 11795f0792..67d28c4553 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -280,6 +280,18 @@ Learn how to make a simple workflow composable as a prelude to making it nf-core Before we can integrate our workflow into the nf-core scaffold, we need to make it **composable**. A composable workflow must be called from a parent workflow—it cannot run on its own—which is exactly how the nf-core template is structured. +### What does the Hello Nextflow workflow do? + +If you haven't completed the [Hello Nextflow](../hello_nextflow/index.md) training, here's a quick overview of what this simple workflow does: + +1. **Reads greetings** from a CSV file (e.g., "Hello", "Bonjour", "Holà") +2. **Says hello** by adding "world!" to each greeting +3. **Converts to uppercase** (e.g., "HELLO WORLD!") +4. **Collects results** into a single file +5. **Adds ASCII art** using cowpy to display the final output with a fun character + +The workflow uses four Nextflow processes organized into separate module files, takes an input CSV file of greetings, and produces a whimsical output file. + We provide you with a clean, fully functional copy of the completed Hello Nextflow workflow in the directory `original-hello` along with its modules and the default CSV file it expects to use as input. ```bash From 0083c0ed8ebae8945f8b90f65d8bf5ad32a4eff4 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 31 Oct 2025 11:03:06 +0000 Subject: [PATCH 074/113] Add course overview to orientation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added "What you'll learn" section to orientation that explains the five core concepts covered in the course and why they matter. Sets expectations that we can't be exhaustive but focus on essential concepts for getting started with nf-core. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/00_orientation.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/hello_nf-core/00_orientation.md b/docs/hello_nf-core/00_orientation.md index 80339368f3..aa825af334 100644 --- a/docs/hello_nf-core/00_orientation.md +++ b/docs/hello_nf-core/00_orientation.md @@ -33,6 +33,29 @@ cd hello-nf-core/ Now let's have a look at the contents of this directory. +## What you'll learn + +This training course teaches you the core concepts for building nf-core-style pipelines. +We can't cover everything—nf-core pipelines have many features and conventions developed by the community over years—but we focus on the essential concepts that will help you get started and understand how nf-core works. + +First, you'll **run an existing nf-core pipeline** to understand how they're structured and what makes them different from basic Nextflow workflows. +The extensive directory structure, configuration system, and standardized conventions might seem overwhelming at first, but understanding them is essential for working within the template. + +Next, you'll **adapt a simple workflow to the nf-core template scaffold**. +Most real pipelines start from existing code, so learning how to restructure workflows to work within nf-core's nested workflow system is a practical skill you'll use repeatedly. + +Then you'll discover one of nf-core's biggest advantages: the **community modules library**. +Instead of writing every process from scratch, you'll learn to integrate pre-built, tested modules that wrap common bioinformatics tools. +This approach saves time and ensures consistency across pipelines. + +Of course, the modules library doesn't have everything, so you'll **create your own nf-core-style module**. +You'll learn the specific structure, naming conventions, and metadata requirements that make modules shareable and maintainable by the community. + +Finally, you'll implement **comprehensive validation** for both command-line parameters and input data files using nf-schema. +This catches errors before pipelines run, providing fast feedback and clear error messages—a key differentiator between research scripts and production pipelines. + +By the end, you'll have transformed a basic Nextflow workflow into a production-ready, nf-core-style pipeline with standardized structure, reusable components, and robust validation. + ## Materials provided You can explore the contents of this directory by using the file explorer on the left-hand side of the training workspace. From a194442d61009d1634e30e1b033fe690865730b8 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 31 Oct 2025 11:10:40 +0000 Subject: [PATCH 075/113] Add summary page to Hello nf-core course MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Created a new summary page that wraps up the course by: - Highlighting what learners built (the core-hello pipeline) - Summarizing the five key skills acquired - Explaining the transformation from research script to production pipeline This provides closure before the survey and next steps pages. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/summary.md | 43 +++++++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 44 insertions(+) create mode 100644 docs/hello_nf-core/summary.md diff --git a/docs/hello_nf-core/summary.md b/docs/hello_nf-core/summary.md new file mode 100644 index 0000000000..663c399399 --- /dev/null +++ b/docs/hello_nf-core/summary.md @@ -0,0 +1,43 @@ +# Summary + +Congratulations on completing the Hello nf-core training course! 🎉 + +## Your journey + +You started with a simple Nextflow workflow from the Hello Nextflow course—a straightforward pipeline that processed greetings through a few steps and added some ASCII art. +Through five parts, you've transformed that basic workflow into a production-ready nf-core pipeline. + +### What you built + +Your final `core-hello` pipeline now has: + +- **Standardized structure** using the nf-core template with organized directories for workflows, subworkflows, modules, and configuration +- **Community modules** from the nf-core repository (`cat/cat`) alongside your custom modules +- **Comprehensive validation** that checks both parameters and input data before the pipeline runs +- **Professional configuration** with profiles for different execution environments +- **Complete documentation** and metadata following nf-core conventions + +### Key skills acquired + +Through this hands-on course, you've learned to: + +1. **Navigate and understand** nf-core pipeline structure by exploring an existing pipeline +2. **Restructure workflows** to be composable and fit within the nf-core template +3. **Find and integrate** pre-built modules from the community repository +4. **Create custom modules** following nf-core standards for naming, structure, and metadata +5. **Implement validation** using nf-schema to catch errors early with clear feedback + +## From research script to production pipeline + +The transformation you've made illustrates the difference between a research script and a production pipeline. +Your original Hello Nextflow workflow worked fine for its purpose, but the nf-core version you've built is: + +- **More maintainable**: Standardized structure makes it easy for others (and future you) to understand +- **More reusable**: Modules can be shared across pipelines and with the community +- **More robust**: Validation catches errors before wasting compute time +- **Better documented**: Clear conventions for configuration and parameter descriptions +- **Community-ready**: Follows standards that make collaboration and contribution possible + +You're now equipped with the foundational knowledge to build production-ready nf-core pipelines that follow community best practices. + +**Thank you for completing this training. Happy pipelining!** 🚀 diff --git a/mkdocs.yml b/mkdocs.yml index 0288469261..0478c3f16e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -35,6 +35,7 @@ nav: - hello_nf-core/03_use_module.md - hello_nf-core/04_make_module.md - hello_nf-core/05_input_validation.md + - hello_nf-core/summary.md - hello_nf-core/survey.md - hello_nf-core/next_steps.md - Nextflow for Science: From cae5504e0fa986bf8c5e8fc8e2f21cb57aa698af Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 31 Oct 2025 11:12:47 +0000 Subject: [PATCH 076/113] Update Part 5 ending to point to summary page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced the large Congratulations section with a simple "What's next?" that directs users to the new summary page. This avoids duplication and maintains consistent flow across all parts that end with "What's next?". 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/05_input_validation.md | 38 ++--------------------- 1 file changed, 3 insertions(+), 35 deletions(-) diff --git a/docs/hello_nf-core/05_input_validation.md b/docs/hello_nf-core/05_input_validation.md index 603e9575e6..5994b50059 100644 --- a/docs/hello_nf-core/05_input_validation.md +++ b/docs/hello_nf-core/05_input_validation.md @@ -717,40 +717,8 @@ You now know how to implement and test both parameter validation and input data --- -## Congratulations! +## What's next? -You've completed the Hello nf-core training course! 🎉 +You've completed all five parts of the Hello nf-core training course! -Throughout this course, you've learned how to: - -- **Run nf-core pipelines** using test profiles and understand their structure -- **Create nf-core-style pipelines** from scratch using the nf-core template -- **Make workflows composable** with `take`, `main`, and `emit` blocks -- **Integrate nf-core modules** from the community repository -- **Implement parameter validation** to catch configuration errors before pipeline execution -- **Implement input data validation** to ensure sample sheets and input files are properly formatted -- **Use nf-schema tools** to manage validation schemas and test validation rules -- **Follow nf-core conventions** for code organization, configuration, and documentation - -You now have the foundational knowledge to develop production-ready Nextflow pipelines that follow nf-core best practices. Your pipeline includes proper module organization, comprehensive validation, and is ready to be extended with additional features. - -### Where to go from here - -Ready to take your skills further? Here are some recommended next steps: - -- **[nf-core website](https://nf-co.re/)**: Explore the full catalog of nf-core pipelines and modules -- **[nf-core documentation](https://nf-co.re/docs/)**: Deep dive into pipeline development guidelines and best practices -- **[nf-schema documentation](https://nextflow-io.github.io/nf-schema/latest/)**: Learn advanced validation techniques -- **[nf-test](https://www.nf-test.com/)**: Add comprehensive testing to your pipeline -- **[Nextflow patterns](https://nextflow-io.github.io/patterns/)**: Discover common workflow patterns and solutions -- **[Side Quests](../side_quests/index.md)**: Explore advanced Nextflow topics like metadata handling, debugging, and workflow composition - -### Get involved with the community - -The nf-core community is welcoming and always happy to help: - -- **[nf-core Slack](https://nf-co.re/join/slack)**: Join the community to ask questions and share your work -- **[GitHub Discussions](https://github.com/nf-core/modules/discussions)**: Participate in discussions about modules and pipelines -- **[Contribute](https://nf-co.re/docs/contributing/overview)**: Consider contributing your own modules or improvements back to the community - -Thank you for completing this training. We hope you enjoyed learning about nf-core and feel confident building your own pipelines. Happy pipelining! 🚀 +Continue to the [Summary](summary.md) to reflect on what you've built and learned. From 31b2ac43d08381e0b3fd5475f1368694763fd419 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 31 Oct 2025 11:17:51 +0000 Subject: [PATCH 077/113] Fix solutions --- .../solutions/core-hello-part2/.nf-core.yml | 106 +++++ .../solutions/core-hello-part5/.nf-core.yml | 106 +++++ .../solutions/core-hello-part5/README.md | 75 ++++ .../core-hello-part5/assets/greetings.csv | 4 + .../core-hello-part5/assets/samplesheet.csv | 3 + .../core-hello-part5/assets/schema_input.json | 18 + .../core-hello-part5/conf/base.config | 66 +++ .../core-hello-part5/conf/modules.config | 26 ++ .../core-hello-part5/conf/test.config | 31 ++ .../core-hello-part5/conf/test_full.config | 24 + .../solutions/core-hello-part5/docs/README.md | 8 + .../solutions/core-hello-part5/docs/output.md | 29 ++ .../solutions/core-hello-part5/docs/usage.md | 211 +++++++++ .../solutions/core-hello-part5/main.nf | 85 ++++ .../solutions/core-hello-part5/modules.json | 36 ++ .../modules/local/convertToUpper.nf | 20 + .../core-hello-part5/modules/local/cowpy.nf | 21 + .../modules/local/cowpy/environment.yml | 10 + .../modules/local/cowpy/main.nf | 47 ++ .../modules/local/cowpy/meta.yml | 51 +++ .../modules/local/cowpy/tests/main.nf.test | 78 ++++ .../modules/local/sayHello.nf | 20 + .../modules/nf-core/cat/cat/environment.yml | 7 + .../modules/nf-core/cat/cat/main.nf | 78 ++++ .../modules/nf-core/cat/cat/meta.yml | 46 ++ .../nf-core/cat/cat/tests/main.nf.test | 191 ++++++++ .../nf-core/cat/cat/tests/main.nf.test.snap | 147 ++++++ .../cat/tests/nextflow_unzipped_zipped.config | 6 + .../cat/tests/nextflow_zipped_unzipped.config | 8 + .../core-hello-part5/nextflow.config | 252 +++++++++++ .../core-hello-part5/nextflow_schema.json | 168 +++++++ .../local/utils_nfcore_hello_pipeline/main.nf | 136 ++++++ .../nf-core/utils_nextflow_pipeline/main.nf | 126 ++++++ .../nf-core/utils_nextflow_pipeline/meta.yml | 38 ++ .../tests/main.function.nf.test | 54 +++ .../tests/main.function.nf.test.snap | 20 + .../tests/main.workflow.nf.test | 113 +++++ .../tests/nextflow.config | 9 + .../nf-core/utils_nfcore_pipeline/main.nf | 419 ++++++++++++++++++ .../nf-core/utils_nfcore_pipeline/meta.yml | 24 + .../tests/main.function.nf.test | 126 ++++++ .../tests/main.function.nf.test.snap | 136 ++++++ .../tests/main.workflow.nf.test | 29 ++ .../tests/main.workflow.nf.test.snap | 19 + .../tests/nextflow.config | 9 + .../nf-core/utils_nfschema_plugin/main.nf | 74 ++++ .../nf-core/utils_nfschema_plugin/meta.yml | 35 ++ .../utils_nfschema_plugin/tests/main.nf.test | 173 ++++++++ .../tests/nextflow.config | 8 + .../tests/nextflow_schema.json | 103 +++++ .../core-hello-part5/workflows/hello.nf | 67 +++ 51 files changed, 3696 insertions(+) create mode 100644 hello-nf-core/solutions/core-hello-part2/.nf-core.yml create mode 100644 hello-nf-core/solutions/core-hello-part5/.nf-core.yml create mode 100644 hello-nf-core/solutions/core-hello-part5/README.md create mode 100644 hello-nf-core/solutions/core-hello-part5/assets/greetings.csv create mode 100644 hello-nf-core/solutions/core-hello-part5/assets/samplesheet.csv create mode 100644 hello-nf-core/solutions/core-hello-part5/assets/schema_input.json create mode 100644 hello-nf-core/solutions/core-hello-part5/conf/base.config create mode 100644 hello-nf-core/solutions/core-hello-part5/conf/modules.config create mode 100644 hello-nf-core/solutions/core-hello-part5/conf/test.config create mode 100644 hello-nf-core/solutions/core-hello-part5/conf/test_full.config create mode 100644 hello-nf-core/solutions/core-hello-part5/docs/README.md create mode 100644 hello-nf-core/solutions/core-hello-part5/docs/output.md create mode 100644 hello-nf-core/solutions/core-hello-part5/docs/usage.md create mode 100644 hello-nf-core/solutions/core-hello-part5/main.nf create mode 100644 hello-nf-core/solutions/core-hello-part5/modules.json create mode 100644 hello-nf-core/solutions/core-hello-part5/modules/local/convertToUpper.nf create mode 100644 hello-nf-core/solutions/core-hello-part5/modules/local/cowpy.nf create mode 100644 hello-nf-core/solutions/core-hello-part5/modules/local/cowpy/environment.yml create mode 100644 hello-nf-core/solutions/core-hello-part5/modules/local/cowpy/main.nf create mode 100644 hello-nf-core/solutions/core-hello-part5/modules/local/cowpy/meta.yml create mode 100644 hello-nf-core/solutions/core-hello-part5/modules/local/cowpy/tests/main.nf.test create mode 100644 hello-nf-core/solutions/core-hello-part5/modules/local/sayHello.nf create mode 100644 hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/environment.yml create mode 100644 hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/main.nf create mode 100644 hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/meta.yml create mode 100644 hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/tests/main.nf.test create mode 100644 hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/tests/main.nf.test.snap create mode 100644 hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/tests/nextflow_unzipped_zipped.config create mode 100644 hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/tests/nextflow_zipped_unzipped.config create mode 100644 hello-nf-core/solutions/core-hello-part5/nextflow.config create mode 100644 hello-nf-core/solutions/core-hello-part5/nextflow_schema.json create mode 100644 hello-nf-core/solutions/core-hello-part5/subworkflows/local/utils_nfcore_hello_pipeline/main.nf create mode 100644 hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nextflow_pipeline/main.nf create mode 100644 hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nextflow_pipeline/meta.yml create mode 100644 hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test create mode 100644 hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap create mode 100644 hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test create mode 100644 hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config create mode 100644 hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/main.nf create mode 100644 hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/meta.yml create mode 100644 hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test create mode 100644 hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap create mode 100644 hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test create mode 100644 hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap create mode 100644 hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config create mode 100644 hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfschema_plugin/main.nf create mode 100644 hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfschema_plugin/meta.yml create mode 100644 hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test create mode 100644 hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config create mode 100644 hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json create mode 100644 hello-nf-core/solutions/core-hello-part5/workflows/hello.nf diff --git a/hello-nf-core/solutions/core-hello-part2/.nf-core.yml b/hello-nf-core/solutions/core-hello-part2/.nf-core.yml new file mode 100644 index 0000000000..1a638d8288 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part2/.nf-core.yml @@ -0,0 +1,106 @@ +repository_type: pipeline + +nf_core_version: 3.4.1 + +lint: + files_unchanged: + - .github/CONTRIBUTING.md + - .prettierignore + - .prettierignore + - .prettierignore + - CODE_OF_CONDUCT.md + - assets/nf-core-hello_logo_light.png + - docs/images/nf-core-hello_logo_light.png + - docs/images/nf-core-hello_logo_dark.png + - .github/ISSUE_TEMPLATE/bug_report.yml + - .github/CONTRIBUTING.md + - .github/PULL_REQUEST_TEMPLATE.md + - assets/email_template.txt + - docs/README.md + - .github/ISSUE_TEMPLATE/bug_report.yml + - .github/ISSUE_TEMPLATE/config.yml + - .github/ISSUE_TEMPLATE/feature_request.yml + - .github/PULL_REQUEST_TEMPLATE.md + - .github/workflows/branch.yml + - .github/workflows/linting_comment.yml + - .github/workflows/linting.yml + - .github/CONTRIBUTING.md + - .github/.dockstore.yml + - .github/CONTRIBUTING.md + - assets/sendmail_template.txt + - .prettierignore + - LICENSE + nextflow_config: + - manifest.name + - manifest.homePage + nf_test_content: false + multiqc_config: false + files_exist: + - .github/workflows/nf-test.yml + - .github/actions/get-shards/action.yml + - .github/actions/nf-test/action.yml + - nf-test.config + - tests/default.nf.test + - assets/email_template.html + - assets/sendmail_template.txt + - assets/email_template.txt + - CODE_OF_CONDUCT.md + - assets/nf-core-hello_logo_light.png + - docs/images/nf-core-hello_logo_light.png + - docs/images/nf-core-hello_logo_dark.png + - .github/ISSUE_TEMPLATE/config.yml + - .github/workflows/awstest.yml + - .github/workflows/awsfulltest.yml + - .github/ISSUE_TEMPLATE/bug_report.yml + - .github/ISSUE_TEMPLATE/feature_request.yml + - .github/PULL_REQUEST_TEMPLATE.md + - .github/CONTRIBUTING.md + - .github/.dockstore.yml + - CHANGELOG.md + - assets/multiqc_config.yml + - .github/workflows/branch.yml + - .github/workflows/nf-test.yml + - .github/actions/get-shards/action.yml + - .github/actions/nf-test/action.yml + - .github/workflows/linting_comment.yml + - .github/workflows/linting.yml + - .prettierignore + - .prettierrc.yml + - conf/igenomes.config + - conf/igenomes_ignored.config + - CITATIONS.md + - LICENSE + readme: + - nextflow_badge + - nextflow_badge + - nfcore_template_badge + +template: + org: core + name: hello + description: A basic nf-core style version of Hello Nextflow + author: pinin4fjords + version: 1.0.0dev + force: true + outdir: . + skip_features: + - github + - github_badges + - changelog + - license + - ci + - nf-test + - igenomes + - multiqc + - fastqc + - seqera_platform + - gpu + - codespaces + - vscode + - code_linters + - citations + - rocrate + - email + - adaptivecard + - slackreport + is_nfcore: false diff --git a/hello-nf-core/solutions/core-hello-part5/.nf-core.yml b/hello-nf-core/solutions/core-hello-part5/.nf-core.yml new file mode 100644 index 0000000000..1a638d8288 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/.nf-core.yml @@ -0,0 +1,106 @@ +repository_type: pipeline + +nf_core_version: 3.4.1 + +lint: + files_unchanged: + - .github/CONTRIBUTING.md + - .prettierignore + - .prettierignore + - .prettierignore + - CODE_OF_CONDUCT.md + - assets/nf-core-hello_logo_light.png + - docs/images/nf-core-hello_logo_light.png + - docs/images/nf-core-hello_logo_dark.png + - .github/ISSUE_TEMPLATE/bug_report.yml + - .github/CONTRIBUTING.md + - .github/PULL_REQUEST_TEMPLATE.md + - assets/email_template.txt + - docs/README.md + - .github/ISSUE_TEMPLATE/bug_report.yml + - .github/ISSUE_TEMPLATE/config.yml + - .github/ISSUE_TEMPLATE/feature_request.yml + - .github/PULL_REQUEST_TEMPLATE.md + - .github/workflows/branch.yml + - .github/workflows/linting_comment.yml + - .github/workflows/linting.yml + - .github/CONTRIBUTING.md + - .github/.dockstore.yml + - .github/CONTRIBUTING.md + - assets/sendmail_template.txt + - .prettierignore + - LICENSE + nextflow_config: + - manifest.name + - manifest.homePage + nf_test_content: false + multiqc_config: false + files_exist: + - .github/workflows/nf-test.yml + - .github/actions/get-shards/action.yml + - .github/actions/nf-test/action.yml + - nf-test.config + - tests/default.nf.test + - assets/email_template.html + - assets/sendmail_template.txt + - assets/email_template.txt + - CODE_OF_CONDUCT.md + - assets/nf-core-hello_logo_light.png + - docs/images/nf-core-hello_logo_light.png + - docs/images/nf-core-hello_logo_dark.png + - .github/ISSUE_TEMPLATE/config.yml + - .github/workflows/awstest.yml + - .github/workflows/awsfulltest.yml + - .github/ISSUE_TEMPLATE/bug_report.yml + - .github/ISSUE_TEMPLATE/feature_request.yml + - .github/PULL_REQUEST_TEMPLATE.md + - .github/CONTRIBUTING.md + - .github/.dockstore.yml + - CHANGELOG.md + - assets/multiqc_config.yml + - .github/workflows/branch.yml + - .github/workflows/nf-test.yml + - .github/actions/get-shards/action.yml + - .github/actions/nf-test/action.yml + - .github/workflows/linting_comment.yml + - .github/workflows/linting.yml + - .prettierignore + - .prettierrc.yml + - conf/igenomes.config + - conf/igenomes_ignored.config + - CITATIONS.md + - LICENSE + readme: + - nextflow_badge + - nextflow_badge + - nfcore_template_badge + +template: + org: core + name: hello + description: A basic nf-core style version of Hello Nextflow + author: pinin4fjords + version: 1.0.0dev + force: true + outdir: . + skip_features: + - github + - github_badges + - changelog + - license + - ci + - nf-test + - igenomes + - multiqc + - fastqc + - seqera_platform + - gpu + - codespaces + - vscode + - code_linters + - citations + - rocrate + - email + - adaptivecard + - slackreport + is_nfcore: false diff --git a/hello-nf-core/solutions/core-hello-part5/README.md b/hello-nf-core/solutions/core-hello-part5/README.md new file mode 100644 index 0000000000..94844f6c5e --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/README.md @@ -0,0 +1,75 @@ +# core/hello + +## Introduction + +**core/hello** is a bioinformatics pipeline that ... + + + + + + +## Usage + +> [!NOTE] +> If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/usage/installation) on how to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/usage/introduction#how-to-run-a-pipeline) with `-profile test` before running the workflow on actual data. + + + +Now, you can run the pipeline using: + + + +```bash +nextflow run core/hello \ + -profile \ + --input samplesheet.csv \ + --outdir +``` + +> [!WARNING] +> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; see [docs](https://nf-co.re/docs/usage/getting_started/configuration#custom-configuration-files). + +## Credits + +core/hello was originally written by pinin4fjords. + +We thank the following people for their extensive assistance in the development of this pipeline: + + + +## Contributions and Support + +If you would like to contribute to this pipeline, please see the [contributing guidelines](.github/CONTRIBUTING.md). + +## Citations + + + + +This pipeline uses code and infrastructure developed and maintained by the [nf-core](https://nf-co.re) community, reused here under the [MIT license](https://github.com/nf-core/tools/blob/main/LICENSE). + +> **The nf-core framework for community-curated bioinformatics pipelines.** +> +> Philip Ewels, Alexander Peltzer, Sven Fillinger, Harshil Patel, Johannes Alneberg, Andreas Wilm, Maxime Ulysse Garcia, Paolo Di Tommaso & Sven Nahnsen. +> +> _Nat Biotechnol._ 2020 Feb 13. doi: [10.1038/s41587-020-0439-x](https://dx.doi.org/10.1038/s41587-020-0439-x). diff --git a/hello-nf-core/solutions/core-hello-part5/assets/greetings.csv b/hello-nf-core/solutions/core-hello-part5/assets/greetings.csv new file mode 100644 index 0000000000..f5c9849604 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/assets/greetings.csv @@ -0,0 +1,4 @@ +greeting +Hello +Bonjour +Holà diff --git a/hello-nf-core/solutions/core-hello-part5/assets/samplesheet.csv b/hello-nf-core/solutions/core-hello-part5/assets/samplesheet.csv new file mode 100644 index 0000000000..5f653ab7bf --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/assets/samplesheet.csv @@ -0,0 +1,3 @@ +sample,fastq_1,fastq_2 +SAMPLE_PAIRED_END,/path/to/fastq/files/AEG588A1_S1_L002_R1_001.fastq.gz,/path/to/fastq/files/AEG588A1_S1_L002_R2_001.fastq.gz +SAMPLE_SINGLE_END,/path/to/fastq/files/AEG588A4_S4_L003_R1_001.fastq.gz, diff --git a/hello-nf-core/solutions/core-hello-part5/assets/schema_input.json b/hello-nf-core/solutions/core-hello-part5/assets/schema_input.json new file mode 100644 index 0000000000..75a1d06d17 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/assets/schema_input.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/core/hello/main/assets/schema_input.json", + "title": "core/hello pipeline - params.input schema", + "description": "Schema for the file provided with params.input", + "type": "array", + "items": { + "type": "object", + "properties": { + "greeting": { + "type": "string", + "pattern": "^\\S.*$", + "errorMessage": "Greeting must be provided and cannot be empty or start with whitespace" + } + }, + "required": ["greeting"] + } +} diff --git a/hello-nf-core/solutions/core-hello-part5/conf/base.config b/hello-nf-core/solutions/core-hello-part5/conf/base.config new file mode 100644 index 0000000000..e0fe40762f --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/conf/base.config @@ -0,0 +1,66 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + core/hello Nextflow base config file +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + A 'blank slate' config file, appropriate for general use on most high performance + compute environments. Assumes that all software is installed and available on + the PATH. Runs in `local` mode - all jobs will be run on the logged in environment. +---------------------------------------------------------------------------------------- +*/ + +process { + + // TODO nf-core: Check the defaults for all processes + cpus = { 1 * task.attempt } + memory = { 6.GB * task.attempt } + time = { 4.h * task.attempt } + + errorStrategy = { task.exitStatus in ((130..145) + 104 + 175) ? 'retry' : 'finish' } + maxRetries = 1 + maxErrors = '-1' + + // Process-specific resource requirements + // NOTE - Please try and reuse the labels below as much as possible. + // These labels are used and recognised by default in DSL2 files hosted on nf-core/modules. + // If possible, it would be nice to keep the same label naming convention when + // adding in your local modules too. + // TODO nf-core: Customise requirements for specific processes. + // See https://www.nextflow.io/docs/latest/config.html#config-process-selectors + withLabel:process_single { + cpus = { 1 } + memory = { 6.GB * task.attempt } + time = { 4.h * task.attempt } + } + withLabel:process_low { + cpus = { 2 * task.attempt } + memory = { 12.GB * task.attempt } + time = { 4.h * task.attempt } + } + withLabel:process_medium { + cpus = { 6 * task.attempt } + memory = { 36.GB * task.attempt } + time = { 8.h * task.attempt } + } + withLabel:process_high { + cpus = { 12 * task.attempt } + memory = { 72.GB * task.attempt } + time = { 16.h * task.attempt } + } + withLabel:process_long { + time = { 20.h * task.attempt } + } + withLabel:process_high_memory { + memory = { 200.GB * task.attempt } + } + withLabel:error_ignore { + errorStrategy = 'ignore' + } + withLabel:error_retry { + errorStrategy = 'retry' + maxRetries = 2 + } + withLabel: process_gpu { + ext.use_gpu = { workflow.profile.contains('gpu') } + accelerator = { workflow.profile.contains('gpu') ? 1 : null } + } +} diff --git a/hello-nf-core/solutions/core-hello-part5/conf/modules.config b/hello-nf-core/solutions/core-hello-part5/conf/modules.config new file mode 100644 index 0000000000..51b19b4a1e --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/conf/modules.config @@ -0,0 +1,26 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Config file for defining DSL2 per module options and publishing paths +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Available keys to override module options: + ext.args = Additional arguments appended to command in module. + ext.args2 = Second set of arguments appended to command in module (multi-tool modules). + ext.args3 = Third set of arguments appended to command in module (multi-tool modules). + ext.prefix = File name prefix for output files. +---------------------------------------------------------------------------------------- +*/ + +process { + + publishDir = [ + path: { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + + withName: 'cowpy' { + ext.args = { "-c ${params.character}" } + ext.prefix = { "cowpy-${meta.id}" } + } + +} diff --git a/hello-nf-core/solutions/core-hello-part5/conf/test.config b/hello-nf-core/solutions/core-hello-part5/conf/test.config new file mode 100644 index 0000000000..13ecf2ad4b --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/conf/test.config @@ -0,0 +1,31 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Nextflow config file for running minimal tests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Defines input files and everything required to run a fast and simple pipeline test. + + Use as follows: + nextflow run core/hello -profile test, --outdir + +---------------------------------------------------------------------------------------- +*/ + +process { + resourceLimits = [ + cpus: 2, + memory: '4.GB', + time: '1.h' + ] +} + +params { + config_profile_name = 'Test profile' + config_profile_description = 'Minimal test dataset to check pipeline function' + + // Input data + input = "${projectDir}/assets/greetings.csv" + + // Other parameters + batch = 'test' + character = 'tux' +} diff --git a/hello-nf-core/solutions/core-hello-part5/conf/test_full.config b/hello-nf-core/solutions/core-hello-part5/conf/test_full.config new file mode 100644 index 0000000000..ceeaf40cac --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/conf/test_full.config @@ -0,0 +1,24 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Nextflow config file for running full-size tests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Defines input files and everything required to run a full size pipeline test. + + Use as follows: + nextflow run core/hello -profile test_full, --outdir + +---------------------------------------------------------------------------------------- +*/ + +params { + config_profile_name = 'Full test profile' + config_profile_description = 'Full test dataset to check pipeline function' + + // Input data for full size test + // TODO nf-core: Specify the paths to your full test data ( on nf-core/test-datasets or directly in repositories, e.g. SRA) + // TODO nf-core: Give any required params for the test so that command line flags are not needed + input = params.pipelines_testdata_base_path + 'viralrecon/samplesheet/samplesheet_full_illumina_amplicon.csv' + + // Fasta references + fasta = params.pipelines_testdata_base_path + 'viralrecon/genome/NC_045512.2/GCF_009858895.2_ASM985889v3_genomic.200409.fna.gz' +} diff --git a/hello-nf-core/solutions/core-hello-part5/docs/README.md b/hello-nf-core/solutions/core-hello-part5/docs/README.md new file mode 100644 index 0000000000..593e4a39e8 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/docs/README.md @@ -0,0 +1,8 @@ +# core/hello: Documentation + +The core/hello documentation is split into the following pages: + +- [Usage](usage.md) + - An overview of how the pipeline works, how to run it and a description of all of the different command-line flags. +- [Output](output.md) + - An overview of the different results produced by the pipeline and how to interpret them. diff --git a/hello-nf-core/solutions/core-hello-part5/docs/output.md b/hello-nf-core/solutions/core-hello-part5/docs/output.md new file mode 100644 index 0000000000..7a49820c81 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/docs/output.md @@ -0,0 +1,29 @@ +# core/hello: Output + +## Introduction + +This document describes the output produced by the pipeline. + +The directories listed below will be created in the results directory after the pipeline has finished. All paths are relative to the top-level results directory. + + + +## Pipeline overview + +The pipeline is built using [Nextflow](https://www.nextflow.io/) and processes data using the following steps: + +- [Pipeline information](#pipeline-information) - Report metrics generated during the workflow execution + +### Pipeline information + +
+Output files + +- `pipeline_info/` + - Reports generated by Nextflow: `execution_report.html`, `execution_timeline.html`, `execution_trace.txt` and `pipeline_dag.dot`/`pipeline_dag.svg`. + - Reformatted samplesheet files used as input to the pipeline: `samplesheet.valid.csv`. + - Parameters used by the pipeline run: `params.json`. + +
+ +[Nextflow](https://www.nextflow.io/docs/latest/tracing.html) provides excellent functionality for generating various reports relevant to the running and execution of the pipeline. This will allow you to troubleshoot errors with the running of the pipeline, and also provide you with other information such as launch commands, run times and resource usage. diff --git a/hello-nf-core/solutions/core-hello-part5/docs/usage.md b/hello-nf-core/solutions/core-hello-part5/docs/usage.md new file mode 100644 index 0000000000..bfbc37ab42 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/docs/usage.md @@ -0,0 +1,211 @@ +# core/hello: Usage + +> _Documentation of pipeline parameters is generated automatically from the pipeline schema and can no longer be found in markdown files._ + +## Introduction + + + +## Samplesheet input + +You will need to create a samplesheet with information about the samples you would like to analyse before running the pipeline. Use this parameter to specify its location. It has to be a comma-separated file with 3 columns, and a header row as shown in the examples below. + +```bash +--input '[path to samplesheet file]' +``` + +### Multiple runs of the same sample + +The `sample` identifiers have to be the same when you have re-sequenced the same sample more than once e.g. to increase sequencing depth. The pipeline will concatenate the raw reads before performing any downstream analysis. Below is an example for the same sample sequenced across 3 lanes: + +```csv title="samplesheet.csv" +sample,fastq_1,fastq_2 +CONTROL_REP1,AEG588A1_S1_L002_R1_001.fastq.gz,AEG588A1_S1_L002_R2_001.fastq.gz +CONTROL_REP1,AEG588A1_S1_L003_R1_001.fastq.gz,AEG588A1_S1_L003_R2_001.fastq.gz +CONTROL_REP1,AEG588A1_S1_L004_R1_001.fastq.gz,AEG588A1_S1_L004_R2_001.fastq.gz +``` + +### Full samplesheet + +The pipeline will auto-detect whether a sample is single- or paired-end using the information provided in the samplesheet. The samplesheet can have as many columns as you desire, however, there is a strict requirement for the first 3 columns to match those defined in the table below. + +A final samplesheet file consisting of both single- and paired-end data may look something like the one below. This is for 6 samples, where `TREATMENT_REP3` has been sequenced twice. + +```csv title="samplesheet.csv" +sample,fastq_1,fastq_2 +CONTROL_REP1,AEG588A1_S1_L002_R1_001.fastq.gz,AEG588A1_S1_L002_R2_001.fastq.gz +CONTROL_REP2,AEG588A2_S2_L002_R1_001.fastq.gz,AEG588A2_S2_L002_R2_001.fastq.gz +CONTROL_REP3,AEG588A3_S3_L002_R1_001.fastq.gz,AEG588A3_S3_L002_R2_001.fastq.gz +TREATMENT_REP1,AEG588A4_S4_L003_R1_001.fastq.gz, +TREATMENT_REP2,AEG588A5_S5_L003_R1_001.fastq.gz, +TREATMENT_REP3,AEG588A6_S6_L003_R1_001.fastq.gz, +TREATMENT_REP3,AEG588A6_S6_L004_R1_001.fastq.gz, +``` + +| Column | Description | +| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `sample` | Custom sample name. This entry will be identical for multiple sequencing libraries/runs from the same sample. Spaces in sample names are automatically converted to underscores (`_`). | +| `fastq_1` | Full path to FastQ file for Illumina short reads 1. File has to be gzipped and have the extension ".fastq.gz" or ".fq.gz". | +| `fastq_2` | Full path to FastQ file for Illumina short reads 2. File has to be gzipped and have the extension ".fastq.gz" or ".fq.gz". | + +An [example samplesheet](../assets/samplesheet.csv) has been provided with the pipeline. + +## Running the pipeline + +The typical command for running the pipeline is as follows: + +```bash +nextflow run core/hello --input ./samplesheet.csv --outdir ./results -profile docker +``` + +This will launch the pipeline with the `docker` configuration profile. See below for more information about profiles. + +Note that the pipeline will create the following files in your working directory: + +```bash +work # Directory containing the nextflow working files + # Finished results in specified location (defined with --outdir) +.nextflow_log # Log file from Nextflow +# Other nextflow hidden files, eg. history of pipeline runs and old logs. +``` + +If you wish to repeatedly use the same parameters for multiple runs, rather than specifying each flag in the command, you can specify these in a params file. + +Pipeline settings can be provided in a `yaml` or `json` file via `-params-file `. + +> [!WARNING] +> Do not use `-c ` to specify parameters as this will result in errors. Custom config files specified with `-c` must only be used for [tuning process resource specifications](https://nf-co.re/docs/usage/configuration#tuning-workflow-resources), other infrastructural tweaks (such as output directories), or module arguments (args). + +The above pipeline run specified with a params file in yaml format: + +```bash +nextflow run core/hello -profile docker -params-file params.yaml +``` + +with: + +```yaml title="params.yaml" +input: './samplesheet.csv' +outdir: './results/' +<...> +``` + +You can also generate such `YAML`/`JSON` files via [nf-core/launch](https://nf-co.re/launch). + +### Updating the pipeline + +When you run the above command, Nextflow automatically pulls the pipeline code from GitHub and stores it as a cached version. When running the pipeline after this, it will always use the cached version if available - even if the pipeline has been updated since. To make sure that you're running the latest version of the pipeline, make sure that you regularly update the cached version of the pipeline: + +```bash +nextflow pull core/hello +``` + +### Reproducibility + +It is a good idea to specify the pipeline version when running the pipeline on your data. This ensures that a specific version of the pipeline code and software are used when you run your pipeline. If you keep using the same tag, you'll be running the same version of the pipeline, even if there have been changes to the code since. + +First, go to the [core/hello releases page](https://github.com/core/hello/releases) and find the latest pipeline version - numeric only (eg. `1.3.1`). Then specify this when running the pipeline with `-r` (one hyphen) - eg. `-r 1.3.1`. Of course, you can switch to another version by changing the number after the `-r` flag. + +This version number will be logged in reports when you run the pipeline, so that you'll know what you used when you look back in the future. + +To further assist in reproducibility, you can use share and reuse [parameter files](#running-the-pipeline) to repeat pipeline runs with the same settings without having to write out a command with every single parameter. + +> [!TIP] +> If you wish to share such profile (such as upload as supplementary material for academic publications), make sure to NOT include cluster specific paths to files, nor institutional specific profiles. + +## Core Nextflow arguments + +> [!NOTE] +> These options are part of Nextflow and use a _single_ hyphen (pipeline parameters use a double-hyphen) + +### `-profile` + +Use this parameter to choose a configuration profile. Profiles can give configuration presets for different compute environments. + +Several generic profiles are bundled with the pipeline which instruct the pipeline to use software packaged using different methods (Docker, Singularity, Podman, Shifter, Charliecloud, Apptainer, Conda) - see below. + +> [!IMPORTANT] +> We highly recommend the use of Docker or Singularity containers for full pipeline reproducibility, however when this is not possible, Conda is also supported. + +The pipeline also dynamically loads configurations from [https://github.com/nf-core/configs](https://github.com/nf-core/configs) when it runs, making multiple config profiles for various institutional clusters available at run time. For more information and to check if your system is supported, please see the [nf-core/configs documentation](https://github.com/nf-core/configs#documentation). + +Note that multiple profiles can be loaded, for example: `-profile test,docker` - the order of arguments is important! +They are loaded in sequence, so later profiles can overwrite earlier profiles. + +If `-profile` is not specified, the pipeline will run locally and expect all software to be installed and available on the `PATH`. This is _not_ recommended, since it can lead to different results on different machines dependent on the computer environment. + +- `test` + - A profile with a complete configuration for automated testing + - Includes links to test data so needs no other parameters +- `docker` + - A generic configuration profile to be used with [Docker](https://docker.com/) +- `singularity` + - A generic configuration profile to be used with [Singularity](https://sylabs.io/docs/) +- `podman` + - A generic configuration profile to be used with [Podman](https://podman.io/) +- `shifter` + - A generic configuration profile to be used with [Shifter](https://nersc.gitlab.io/development/shifter/how-to-use/) +- `charliecloud` + - A generic configuration profile to be used with [Charliecloud](https://charliecloud.io/) +- `apptainer` + - A generic configuration profile to be used with [Apptainer](https://apptainer.org/) +- `wave` + - A generic configuration profile to enable [Wave](https://seqera.io/wave/) containers. Use together with one of the above (requires Nextflow ` 24.03.0-edge` or later). +- `conda` + - A generic configuration profile to be used with [Conda](https://conda.io/docs/). Please only use Conda as a last resort i.e. when it's not possible to run the pipeline with Docker, Singularity, Podman, Shifter, Charliecloud, or Apptainer. + +### `-resume` + +Specify this when restarting a pipeline. Nextflow will use cached results from any pipeline steps where the inputs are the same, continuing from where it got to previously. For input to be considered the same, not only the names must be identical but the files' contents as well. For more info about this parameter, see [this blog post](https://www.nextflow.io/blog/2019/demystifying-nextflow-resume.html). + +You can also supply a run name to resume a specific run: `-resume [run-name]`. Use the `nextflow log` command to show previous run names. + +### `-c` + +Specify the path to a specific config file (this is a core Nextflow command). See the [nf-core website documentation](https://nf-co.re/usage/configuration) for more information. + +## Custom configuration + +### Resource requests + +Whilst the default requirements set within the pipeline will hopefully work for most people and with most input data, you may find that you want to customise the compute resources that the pipeline requests. Each step in the pipeline has a default set of requirements for number of CPUs, memory and time. For most of the pipeline steps, if the job exits with any of the error codes specified [here](https://github.com/nf-core/rnaseq/blob/4c27ef5610c87db00c3c5a3eed10b1d161abf575/conf/base.config#L18) it will automatically be resubmitted with higher resources request (2 x original, then 3 x original). If it still fails after the third attempt then the pipeline execution is stopped. + +To change the resource requests, please see the [max resources](https://nf-co.re/docs/usage/configuration#max-resources) and [tuning workflow resources](https://nf-co.re/docs/usage/configuration#tuning-workflow-resources) section of the nf-core website. + +### Custom Containers + +In some cases, you may wish to change the container or conda environment used by a pipeline steps for a particular tool. By default, nf-core pipelines use containers and software from the [biocontainers](https://biocontainers.pro/) or [bioconda](https://bioconda.github.io/) projects. However, in some cases the pipeline specified version maybe out of date. + +To use a different container from the default container or conda environment specified in a pipeline, please see the [updating tool versions](https://nf-co.re/docs/usage/configuration#updating-tool-versions) section of the nf-core website. + +### Custom Tool Arguments + +A pipeline might not always support every possible argument or option of a particular tool used in pipeline. Fortunately, nf-core pipelines provide some freedom to users to insert additional parameters that the pipeline does not include by default. + +To learn how to provide additional arguments to a particular tool of the pipeline, please see the [customising tool arguments](https://nf-co.re/docs/usage/configuration#customising-tool-arguments) section of the nf-core website. + +### nf-core/configs + +In most cases, you will only need to create a custom config as a one-off but if you and others within your organisation are likely to be running nf-core pipelines regularly and need to use the same settings regularly it may be a good idea to request that your custom config file is uploaded to the `nf-core/configs` git repository. Before you do this please can you test that the config file works with your pipeline of choice using the `-c` parameter. You can then create a pull request to the `nf-core/configs` repository with the addition of your config file, associated documentation file (see examples in [`nf-core/configs/docs`](https://github.com/nf-core/configs/tree/master/docs)), and amending [`nfcore_custom.config`](https://github.com/nf-core/configs/blob/master/nfcore_custom.config) to include your custom profile. + +See the main [Nextflow documentation](https://www.nextflow.io/docs/latest/config.html) for more information about creating your own configuration files. + +If you have any questions or issues please send us a message on [Slack](https://nf-co.re/join/slack) on the [`#configs` channel](https://nfcore.slack.com/channels/configs). + +## Running in the background + +Nextflow handles job submissions and supervises the running jobs. The Nextflow process must run until the pipeline is finished. + +The Nextflow `-bg` flag launches Nextflow in the background, detached from your terminal so that the workflow does not stop if you log out of your session. The logs are saved to a file. + +Alternatively, you can use `screen` / `tmux` or similar tool to create a detached session which you can log back into at a later time. +Some HPC setups also allow you to run nextflow within a cluster job submitted your job scheduler (from where it submits more jobs). + +## Nextflow memory requirements + +In some cases, the Nextflow Java virtual machines can start to request a large amount of memory. +We recommend adding the following line to your environment to limit this (typically in `~/.bashrc` or `~./bash_profile`): + +```bash +NXF_OPTS='-Xms1g -Xmx4g' +``` diff --git a/hello-nf-core/solutions/core-hello-part5/main.nf b/hello-nf-core/solutions/core-hello-part5/main.nf new file mode 100644 index 0000000000..eb8d91361f --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/main.nf @@ -0,0 +1,85 @@ +#!/usr/bin/env nextflow +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + core/hello +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Github : https://github.com/core/hello +---------------------------------------------------------------------------------------- +*/ + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + IMPORT FUNCTIONS / MODULES / SUBWORKFLOWS / WORKFLOWS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +include { HELLO } from './workflows/hello' +include { PIPELINE_INITIALISATION } from './subworkflows/local/utils_nfcore_hello_pipeline' +include { PIPELINE_COMPLETION } from './subworkflows/local/utils_nfcore_hello_pipeline' +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + NAMED WORKFLOWS FOR PIPELINE +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +// +// WORKFLOW: Run main analysis pipeline depending on type of input +// +workflow CORE_HELLO { + + take: + samplesheet // channel: samplesheet read in from --input + + main: + + // + // WORKFLOW: Run pipeline + // + HELLO ( + samplesheet + ) +} +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + RUN MAIN WORKFLOW +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow { + + main: + // + // SUBWORKFLOW: Run initialisation tasks + // + PIPELINE_INITIALISATION ( + params.version, + params.validate_params, + params.monochrome_logs, + args, + params.outdir, + params.input, + params.help, + params.help_full, + params.show_hidden + ) + + // + // WORKFLOW: Run main workflow + // + CORE_HELLO ( + PIPELINE_INITIALISATION.out.samplesheet + ) + // + // SUBWORKFLOW: Run completion tasks + // + PIPELINE_COMPLETION ( + params.outdir, + params.monochrome_logs, + ) +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + THE END +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ diff --git a/hello-nf-core/solutions/core-hello-part5/modules.json b/hello-nf-core/solutions/core-hello-part5/modules.json new file mode 100644 index 0000000000..3e65b38609 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/modules.json @@ -0,0 +1,36 @@ +{ + "name": "core/hello", + "homePage": "https://github.com/core/hello", + "repos": { + "https://github.com/nf-core/modules.git": { + "modules": { + "nf-core": { + "cat/cat": { + "branch": "master", + "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", + "installed_by": ["modules"] + } + } + }, + "subworkflows": { + "nf-core": { + "utils_nextflow_pipeline": { + "branch": "master", + "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "installed_by": ["subworkflows"] + }, + "utils_nfcore_pipeline": { + "branch": "master", + "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "installed_by": ["subworkflows"] + }, + "utils_nfschema_plugin": { + "branch": "master", + "git_sha": "4b406a74dc0449c0401ed87d5bfff4252fd277fd", + "installed_by": ["subworkflows"] + } + } + } + } + } +} diff --git a/hello-nf-core/solutions/core-hello-part5/modules/local/convertToUpper.nf b/hello-nf-core/solutions/core-hello-part5/modules/local/convertToUpper.nf new file mode 100644 index 0000000000..b2689e8e9c --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/modules/local/convertToUpper.nf @@ -0,0 +1,20 @@ +#!/usr/bin/env nextflow + +/* + * Use a text replacement tool to convert the greeting to uppercase + */ +process convertToUpper { + + publishDir 'results', mode: 'copy' + + input: + path input_file + + output: + path "UPPER-${input_file}" + + script: + """ + cat '$input_file' | tr '[a-z]' '[A-Z]' > 'UPPER-${input_file}' + """ +} diff --git a/hello-nf-core/solutions/core-hello-part5/modules/local/cowpy.nf b/hello-nf-core/solutions/core-hello-part5/modules/local/cowpy.nf new file mode 100644 index 0000000000..ec00520a66 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/modules/local/cowpy.nf @@ -0,0 +1,21 @@ +#!/usr/bin/env nextflow + +// Generate ASCII art with cowpy (https://github.com/jeffbuttars/cowpy) +process cowpy { + + container 'community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273' + conda 'conda-forge::cowpy==1.1.5' + + input: + tuple val(meta), path(input_file) + + output: + tuple val(meta), path("${prefix}.txt"), emit: cowpy_output + + script: + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + """ + cat $input_file | cowpy $args > ${prefix}.txt + """ +} diff --git a/hello-nf-core/solutions/core-hello-part5/modules/local/cowpy/environment.yml b/hello-nf-core/solutions/core-hello-part5/modules/local/cowpy/environment.yml new file mode 100644 index 0000000000..32bc330d8f --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/modules/local/cowpy/environment.yml @@ -0,0 +1,10 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + # TODO nf-core: List required Conda package(s). + # Software MUST be pinned to channel (i.e. "bioconda"), version (i.e. "1.10"). + # For Conda, the build (i.e. "h9402c20_2") must be EXCLUDED to support installation on different operating systems. + - "YOUR-TOOL-HERE" diff --git a/hello-nf-core/solutions/core-hello-part5/modules/local/cowpy/main.nf b/hello-nf-core/solutions/core-hello-part5/modules/local/cowpy/main.nf new file mode 100644 index 0000000000..212821d599 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/modules/local/cowpy/main.nf @@ -0,0 +1,47 @@ + + +process COWPY { + tag "$meta.id" + label 'process_single' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/YOUR-TOOL-HERE': + 'biocontainers/YOUR-TOOL-HERE' }" + + input:tuple val(meta), path(input) + + output: + tuple val(meta), path("*"), emit: output + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + cowpy: \$(cowpy --version) + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + echo $args + + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + cowpy: \$(cowpy --version) + END_VERSIONS + """ +} diff --git a/hello-nf-core/solutions/core-hello-part5/modules/local/cowpy/meta.yml b/hello-nf-core/solutions/core-hello-part5/modules/local/cowpy/meta.yml new file mode 100644 index 0000000000..616fdd9422 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/modules/local/cowpy/meta.yml @@ -0,0 +1,51 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "cowpy" +description: write your description here +keywords: + - sort + - example + - genomics +tools: + - "cowpy": + description: "" + homepage: "" + documentation: "" + tool_dev_url: "" + doi: "" + licence: null + identifier: null + +input: + - - meta: + type: map + description: Groovy Map containing sample information. e.g. `[ + id:'sample1' ]` + - input: + type: file + description: "" + pattern: "" + ontologies: + - edam: "" +output: + output: + - - meta: + type: map + description: Groovy Map containing sample information. e.g. `[ + id:'sample1' ]` + - "*": + type: file + description: "" + pattern: "" + ontologies: + - edam: "" + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: versions.yml + ontologies: + - edam: http://edamontology.org/format_3750 # YAML +authors: + - "@example" +maintainers: + - "@example" diff --git a/hello-nf-core/solutions/core-hello-part5/modules/local/cowpy/tests/main.nf.test b/hello-nf-core/solutions/core-hello-part5/modules/local/cowpy/tests/main.nf.test new file mode 100644 index 0000000000..88f75f7b6a --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/modules/local/cowpy/tests/main.nf.test @@ -0,0 +1,78 @@ +// TODO nf-core: Once you have added the required tests, please run the following command to build this file: +// nf-core modules test cowpy +nextflow_process { + + name "Test Process COWPY" + script "../main.nf" + process "COWPY" + + tag "modules" + tag "modules_" + tag "cowpy" + + // TODO nf-core: Change the test name preferably indicating the test-data and file-format used + test("sarscov2 - bam") { + + // TODO nf-core: If you are created a test for a chained module + // (the module requires running more than one process to generate the required output) + // add the 'setup' method here. + // You can find more information about how to use a 'setup' method in the docs (https://nf-co.re/docs/contributing/modules#steps-for-creating-nf-test-for-chained-modules). + + when { + process { + """ + // TODO nf-core: define inputs of the process here. Example: + + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot( + process.out, + path(process.out.versions[0]).yaml + ).match() } + //TODO nf-core: Add all required assertions to verify the test output. + // See https://nf-co.re/docs/contributing/tutorials/nf-test_assertions for more information and examples. + ) + } + + } + + // TODO nf-core: Change the test name preferably indicating the test-data and file-format used but keep the " - stub" suffix. + test("sarscov2 - bam - stub") { + + options "-stub" + + when { + process { + """ + // TODO nf-core: define inputs of the process here. Example: + + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot( + process.out, + path(process.out.versions[0]).yaml + ).match() } + ) + } + + } + +} diff --git a/hello-nf-core/solutions/core-hello-part5/modules/local/sayHello.nf b/hello-nf-core/solutions/core-hello-part5/modules/local/sayHello.nf new file mode 100644 index 0000000000..6005ad54c9 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/modules/local/sayHello.nf @@ -0,0 +1,20 @@ +#!/usr/bin/env nextflow + +/* + * Use echo to print 'Hello World!' to a file + */ +process sayHello { + + publishDir 'results', mode: 'copy' + + input: + val greeting + + output: + path "${greeting}-output.txt" + + script: + """ + echo '$greeting' > '$greeting-output.txt' + """ +} diff --git a/hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/environment.yml b/hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/environment.yml new file mode 100644 index 0000000000..50c2059afb --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/environment.yml @@ -0,0 +1,7 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - conda-forge::pigz=2.3.4 diff --git a/hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/main.nf b/hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/main.nf new file mode 100644 index 0000000000..2862c64cd9 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/main.nf @@ -0,0 +1,78 @@ +process CAT_CAT { + tag "$meta.id" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/pigz:2.3.4' : + 'biocontainers/pigz:2.3.4' }" + + input: + tuple val(meta), path(files_in) + + output: + tuple val(meta), path("${prefix}"), emit: file_out + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def args2 = task.ext.args2 ?: '' + def file_list = files_in.collect { it.toString() } + + // choose appropriate concatenation tool depending on input and output format + + // | input | output | command1 | command2 | + // |-----------|------------|----------|----------| + // | gzipped | gzipped | cat | | + // | ungzipped | ungzipped | cat | | + // | gzipped | ungzipped | zcat | | + // | ungzipped | gzipped | cat | pigz | + + // Use input file ending as default + prefix = task.ext.prefix ?: "${meta.id}${getFileSuffix(file_list[0])}" + out_zip = prefix.endsWith('.gz') + in_zip = file_list[0].endsWith('.gz') + command1 = (in_zip && !out_zip) ? 'zcat' : 'cat' + command2 = (!in_zip && out_zip) ? "| pigz -c -p $task.cpus $args2" : '' + if(file_list.contains(prefix.trim())) { + error "The name of the input file can't be the same as for the output prefix in the " + + "module CAT_CAT (currently `$prefix`). Please choose a different one." + } + """ + $command1 \\ + $args \\ + ${file_list.join(' ')} \\ + $command2 \\ + > ${prefix} + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + pigz: \$( pigz --version 2>&1 | sed 's/pigz //g' ) + END_VERSIONS + """ + + stub: + def file_list = files_in.collect { it.toString() } + prefix = task.ext.prefix ?: "${meta.id}${file_list[0].substring(file_list[0].lastIndexOf('.'))}" + if(file_list.contains(prefix.trim())) { + error "The name of the input file can't be the same as for the output prefix in the " + + "module CAT_CAT (currently `$prefix`). Please choose a different one." + } + """ + touch $prefix + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + pigz: \$( pigz --version 2>&1 | sed 's/pigz //g' ) + END_VERSIONS + """ +} + +// for .gz files also include the second to last extension if it is present. E.g., .fasta.gz +def getFileSuffix(filename) { + def match = filename =~ /^.*?((\.\w{1,5})?(\.\w{1,5}\.gz$))/ + return match ? match[0][1] : filename.substring(filename.lastIndexOf('.')) +} diff --git a/hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/meta.yml b/hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/meta.yml new file mode 100644 index 0000000000..2a9284d7f1 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/meta.yml @@ -0,0 +1,46 @@ +name: cat_cat +description: A module for concatenation of gzipped or uncompressed files +keywords: + - concatenate + - gzip + - cat +tools: + - cat: + description: Just concatenation + documentation: https://man7.org/linux/man-pages/man1/cat.1.html + licence: ["GPL-3.0-or-later"] + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - files_in: + type: file + description: List of compressed / uncompressed files + pattern: "*" + ontologies: [] +output: + file_out: + - - meta: + type: map + description: Groovy Map containing sample information + - ${prefix}: + type: file + description: Concatenated file. Will be gzipped if file_out ends with ".gz" + pattern: "${file_out}" + ontologies: [] + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML +authors: + - "@erikrikarddaniel" + - "@FriederikeHanssen" +maintainers: + - "@erikrikarddaniel" + - "@FriederikeHanssen" diff --git a/hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/tests/main.nf.test b/hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/tests/main.nf.test new file mode 100644 index 0000000000..9cb1617883 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/tests/main.nf.test @@ -0,0 +1,191 @@ +nextflow_process { + + name "Test Process CAT_CAT" + script "../main.nf" + process "CAT_CAT" + tag "modules" + tag "modules_nfcore" + tag "cat" + tag "cat/cat" + + test("test_cat_name_conflict") { + when { + params { + outdir = "${outputDir}" + } + process { + """ + input[0] = + [ + [ id:'genome', single_end:true ], + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.sizes', checkIfExists: true) + ] + ] + """ + } + } + then { + assertAll( + { assert !process.success }, + { assert process.stdout.toString().contains("The name of the input file can't be the same as for the output prefix") }, + { assert snapshot(process.out.versions).match() } + ) + } + } + + test("test_cat_unzipped_unzipped") { + when { + params { + outdir = "${outputDir}" + } + process { + """ + input[0] = + [ + [ id:'test', single_end:true ], + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.sizes', checkIfExists: true) + ] + ] + """ + } + } + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + + test("test_cat_zipped_zipped") { + when { + params { + outdir = "${outputDir}" + } + process { + """ + input[0] = + [ + [ id:'test', single_end:true ], + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.gff3.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/alignment/last/contigs.genome.maf.gz', checkIfExists: true) + ] + ] + """ + } + } + then { + def lines = path(process.out.file_out.get(0).get(1)).linesGzip + assertAll( + { assert process.success }, + { assert snapshot( + lines[0..5], + lines.size(), + process.out.versions + ).match() + } + ) + } + } + + test("test_cat_zipped_unzipped") { + config './nextflow_zipped_unzipped.config' + + when { + params { + outdir = "${outputDir}" + } + process { + """ + input[0] = + [ + [ id:'test', single_end:true ], + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.gff3.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/alignment/last/contigs.genome.maf.gz', checkIfExists: true) + ] + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + + test("test_cat_unzipped_zipped") { + config './nextflow_unzipped_zipped.config' + when { + params { + outdir = "${outputDir}" + } + process { + """ + input[0] = + [ + [ id:'test', single_end:true ], + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.sizes', checkIfExists: true) + ] + ] + """ + } + } + then { + def lines = path(process.out.file_out.get(0).get(1)).linesGzip + assertAll( + { assert process.success }, + { assert snapshot( + lines[0..5], + lines.size(), + process.out.versions + ).match() + } + ) + } + } + + test("test_cat_one_file_unzipped_zipped") { + config './nextflow_unzipped_zipped.config' + when { + params { + outdir = "${outputDir}" + } + process { + """ + input[0] = + [ + [ id:'test', single_end:true ], + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + ] + """ + } + } + then { + def lines = path(process.out.file_out.get(0).get(1)).linesGzip + assertAll( + { assert process.success }, + { assert snapshot( + lines[0..5], + lines.size(), + process.out.versions + ).match() + } + ) + } + } +} diff --git a/hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/tests/main.nf.test.snap b/hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/tests/main.nf.test.snap new file mode 100644 index 0000000000..b7623ee650 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/tests/main.nf.test.snap @@ -0,0 +1,147 @@ +{ + "test_cat_unzipped_unzipped": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fasta:md5,f44b33a0e441ad58b2d3700270e2dbe2" + ] + ], + "1": [ + "versions.yml:md5,115ed6177ebcff24eb99d503fa5ef894" + ], + "file_out": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fasta:md5,f44b33a0e441ad58b2d3700270e2dbe2" + ] + ], + "versions": [ + "versions.yml:md5,115ed6177ebcff24eb99d503fa5ef894" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2023-10-16T14:32:18.500464399" + }, + "test_cat_zipped_unzipped": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": true + }, + "cat.txt:md5,c439d3b60e7bc03e8802a451a0d9a5d9" + ] + ], + "1": [ + "versions.yml:md5,115ed6177ebcff24eb99d503fa5ef894" + ], + "file_out": [ + [ + { + "id": "test", + "single_end": true + }, + "cat.txt:md5,c439d3b60e7bc03e8802a451a0d9a5d9" + ] + ], + "versions": [ + "versions.yml:md5,115ed6177ebcff24eb99d503fa5ef894" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2023-10-16T14:32:49.642741302" + }, + "test_cat_zipped_zipped": { + "content": [ + [ + "MT192765.1\tGenbank\ttranscript\t259\t29667\t.\t+\t.\tID=unknown_transcript_1;geneID=orf1ab;gene_name=orf1ab", + "MT192765.1\tGenbank\tgene\t259\t21548\t.\t+\t.\tParent=unknown_transcript_1", + "MT192765.1\tGenbank\tCDS\t259\t13461\t.\t+\t0\tParent=unknown_transcript_1;exception=\"ribosomal slippage\";gbkey=CDS;gene=orf1ab;note=\"pp1ab;translated=by -1 ribosomal frameshift\";product=\"orf1ab polyprotein\";protein_id=QIK50426.1", + "MT192765.1\tGenbank\tCDS\t13461\t21548\t.\t+\t0\tParent=unknown_transcript_1;exception=\"ribosomal slippage\";gbkey=CDS;gene=orf1ab;note=\"pp1ab;translated=by -1 ribosomal frameshift\";product=\"orf1ab polyprotein\";protein_id=QIK50426.1", + "MT192765.1\tGenbank\tCDS\t21556\t25377\t.\t+\t0\tParent=unknown_transcript_1;gbkey=CDS;gene=S;note=\"structural protein\";product=\"surface glycoprotein\";protein_id=QIK50427.1", + "MT192765.1\tGenbank\tgene\t21556\t25377\t.\t+\t.\tParent=unknown_transcript_1" + ], + 78, + [ + "versions.yml:md5,115ed6177ebcff24eb99d503fa5ef894" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-22T11:51:46.802978" + }, + "test_cat_name_conflict": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-22T11:51:29.45394" + }, + "test_cat_one_file_unzipped_zipped": { + "content": [ + [ + ">MT192765.1 Severe acute respiratory syndrome coronavirus 2 isolate SARS-CoV-2/human/USA/PC00101P/2020, complete genome", + "GTTTATACCTTCCCAGGTAACAAACCAACCAACTTTCGATCTCTTGTAGATCTGTTCTCTAAACGAACTTTAAAATCTGT", + "GTGGCTGTCACTCGGCTGCATGCTTAGTGCACTCACGCAGTATAATTAATAACTAATTACTGTCGTTGACAGGACACGAG", + "TAACTCGTCTATCTTCTGCAGGCTGCTTACGGTTTCGTCCGTGTTGCAGCCGATCATCAGCACATCTAGGTTTTGTCCGG", + "GTGTGACCGAAAGGTAAGATGGAGAGCCTTGTCCCTGGTTTCAACGAGAAAACACACGTCCAACTCAGTTTGCCTGTTTT", + "ACAGGTTCGCGACGTGCTCGTACGTGGCTTTGGAGACTCCGTGGAGGAGGTCTTATCAGAGGCACGTCAACATCTTAAAG" + ], + 374, + [ + "versions.yml:md5,115ed6177ebcff24eb99d503fa5ef894" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-22T11:52:02.774016" + }, + "test_cat_unzipped_zipped": { + "content": [ + [ + ">MT192765.1 Severe acute respiratory syndrome coronavirus 2 isolate SARS-CoV-2/human/USA/PC00101P/2020, complete genome", + "GTTTATACCTTCCCAGGTAACAAACCAACCAACTTTCGATCTCTTGTAGATCTGTTCTCTAAACGAACTTTAAAATCTGT", + "GTGGCTGTCACTCGGCTGCATGCTTAGTGCACTCACGCAGTATAATTAATAACTAATTACTGTCGTTGACAGGACACGAG", + "TAACTCGTCTATCTTCTGCAGGCTGCTTACGGTTTCGTCCGTGTTGCAGCCGATCATCAGCACATCTAGGTTTTGTCCGG", + "GTGTGACCGAAAGGTAAGATGGAGAGCCTTGTCCCTGGTTTCAACGAGAAAACACACGTCCAACTCAGTTTGCCTGTTTT", + "ACAGGTTCGCGACGTGCTCGTACGTGGCTTTGGAGACTCCGTGGAGGAGGTCTTATCAGAGGCACGTCAACATCTTAAAG" + ], + 375, + [ + "versions.yml:md5,115ed6177ebcff24eb99d503fa5ef894" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-22T11:51:57.581523" + } +} \ No newline at end of file diff --git a/hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/tests/nextflow_unzipped_zipped.config b/hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/tests/nextflow_unzipped_zipped.config new file mode 100644 index 0000000000..ec26b0fdc6 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/tests/nextflow_unzipped_zipped.config @@ -0,0 +1,6 @@ + +process { + withName: CAT_CAT { + ext.prefix = 'cat.txt.gz' + } +} diff --git a/hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/tests/nextflow_zipped_unzipped.config b/hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/tests/nextflow_zipped_unzipped.config new file mode 100644 index 0000000000..fbc79783d5 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/tests/nextflow_zipped_unzipped.config @@ -0,0 +1,8 @@ + +process { + + withName: CAT_CAT { + ext.prefix = 'cat.txt' + } + +} diff --git a/hello-nf-core/solutions/core-hello-part5/nextflow.config b/hello-nf-core/solutions/core-hello-part5/nextflow.config new file mode 100644 index 0000000000..b59ba2175d --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/nextflow.config @@ -0,0 +1,252 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + core/hello Nextflow config file +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Default config options for all compute environments +---------------------------------------------------------------------------------------- +*/ + +// Global default params, used in configs +params { + + // TODO nf-core: Specify your pipeline's command line flags + // Input options + input = null + + // Boilerplate options + outdir = null + publish_dir_mode = 'copy' + monochrome_logs = false + help = false + help_full = false + show_hidden = false + version = false + pipelines_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/' + trace_report_suffix = new java.util.Date().format( 'yyyy-MM-dd_HH-mm-ss') + + // Config options + config_profile_name = null + config_profile_description = null + + custom_config_version = 'master' + custom_config_base = "https://raw.githubusercontent.com/nf-core/configs/${params.custom_config_version}" + config_profile_contact = null + config_profile_url = null + + // Schema validation default options + validate_params = true +} + +// Load base.config by default for all pipelines +includeConfig 'conf/base.config' + +profiles { + debug { + dumpHashes = true + process.beforeScript = 'echo $HOSTNAME' + cleanup = false + nextflow.enable.configProcessNamesValidation = true + } + conda { + conda.enabled = true + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + conda.channels = ['conda-forge', 'bioconda'] + apptainer.enabled = false + } + mamba { + conda.enabled = true + conda.useMamba = true + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + } + docker { + docker.enabled = true + conda.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + docker.runOptions = '-u $(id -u):$(id -g)' + } + arm64 { + process.arch = 'arm64' + // TODO https://github.com/nf-core/modules/issues/6694 + // For now if you're using arm64 you have to use wave for the sake of the maintainers + // wave profile + apptainer.ociAutoPull = true + singularity.ociAutoPull = true + wave.enabled = true + wave.freeze = true + wave.strategy = 'conda,container' + } + emulate_amd64 { + docker.runOptions = '-u $(id -u):$(id -g) --platform=linux/amd64' + } + singularity { + singularity.enabled = true + singularity.autoMounts = true + conda.enabled = false + docker.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + } + podman { + podman.enabled = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + } + shifter { + shifter.enabled = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + podman.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + } + charliecloud { + charliecloud.enabled = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + apptainer.enabled = false + } + apptainer { + apptainer.enabled = true + apptainer.autoMounts = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + } + wave { + apptainer.ociAutoPull = true + singularity.ociAutoPull = true + wave.enabled = true + wave.freeze = true + wave.strategy = 'conda,container' + } + gpu { + docker.runOptions = '-u $(id -u):$(id -g) --gpus all' + apptainer.runOptions = '--nv' + singularity.runOptions = '--nv' + } + test { includeConfig 'conf/test.config' } + test_full { includeConfig 'conf/test_full.config' } +} +// Load nf-core custom profiles from different institutions + +// If params.custom_config_base is set AND either the NXF_OFFLINE environment variable is not set or params.custom_config_base is a local path, the nfcore_custom.config file from the specified base path is included. +// Load core/hello custom profiles from different institutions. +includeConfig params.custom_config_base && (!System.getenv('NXF_OFFLINE') || !params.custom_config_base.startsWith('http')) ? "${params.custom_config_base}/nfcore_custom.config" : "/dev/null" + + +// Load core/hello custom profiles from different institutions. +// TODO nf-core: Optionally, you can add a pipeline-specific nf-core config at https://github.com/nf-core/configs +// includeConfig params.custom_config_base && (!System.getenv('NXF_OFFLINE') || !params.custom_config_base.startsWith('http')) ? "${params.custom_config_base}/pipeline/hello.config" : "/dev/null" + +// Set default registry for Apptainer, Docker, Podman, Charliecloud and Singularity independent of -profile +// Will not be used unless Apptainer / Docker / Podman / Charliecloud / Singularity are enabled +// Set to your registry if you have a mirror of containers +apptainer.registry = 'quay.io' +docker.registry = 'quay.io' +podman.registry = 'quay.io' +singularity.registry = 'quay.io' +charliecloud.registry = 'quay.io' + + + +// Export these variables to prevent local Python/R libraries from conflicting with those in the container +// The JULIA depot path has been adjusted to a fixed path `/usr/local/share/julia` that needs to be used for packages in the container. +// See https://apeltzer.github.io/post/03-julia-lang-nextflow/ for details on that. Once we have a common agreement on where to keep Julia packages, this is adjustable. + +env { + PYTHONNOUSERSITE = 1 + R_PROFILE_USER = "/.Rprofile" + R_ENVIRON_USER = "/.Renviron" + JULIA_DEPOT_PATH = "/usr/local/share/julia" +} + +// Set bash options +process.shell = [ + "bash", + "-C", // No clobber - prevent output redirection from overwriting files. + "-e", // Exit if a tool returns a non-zero status/exit code + "-u", // Treat unset variables and parameters as an error + "-o", // Returns the status of the last command to exit.. + "pipefail" // ..with a non-zero status or zero if all successfully execute +] + +// Disable process selector warnings by default. Use debug profile to enable warnings. +nextflow.enable.configProcessNamesValidation = false + +timeline { + enabled = true + file = "${params.outdir}/pipeline_info/execution_timeline_${params.trace_report_suffix}.html" +} +report { + enabled = true + file = "${params.outdir}/pipeline_info/execution_report_${params.trace_report_suffix}.html" +} +trace { + enabled = true + file = "${params.outdir}/pipeline_info/execution_trace_${params.trace_report_suffix}.txt" +} +dag { + enabled = true + file = "${params.outdir}/pipeline_info/pipeline_dag_${params.trace_report_suffix}.html" +} + +manifest { + name = 'core/hello' + contributors = [ + // TODO nf-core: Update the field with the details of the contributors to your pipeline. New with Nextflow version 24.10.0 + [ + name: 'pinin4fjords', + affiliation: '', + email: '', + github: '', + contribution: [], // List of contribution types ('author', 'maintainer' or 'contributor') + orcid: '' + ], + ] + homePage = 'https://github.com/core/hello' + description = """A basic nf-core style version of Hello Nextflow""" + mainScript = 'main.nf' + defaultBranch = 'main' + nextflowVersion = '!>=25.04.0' + version = '1.0.0dev' + doi = '' +} + +// Nextflow plugins +plugins { + id 'nf-schema@2.5.1' // Validation of pipeline parameters and creation of an input channel from a sample sheet +} + +validation { + defaultIgnoreParams = ["genomes"] + monochromeLogs = params.monochrome_logs +} + +// Load modules.config for DSL2 module specific options +includeConfig 'conf/modules.config' diff --git a/hello-nf-core/solutions/core-hello-part5/nextflow_schema.json b/hello-nf-core/solutions/core-hello-part5/nextflow_schema.json new file mode 100644 index 0000000000..008c91eb19 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/nextflow_schema.json @@ -0,0 +1,168 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/core/hello/main/nextflow_schema.json", + "title": "core/hello pipeline parameters", + "description": "A basic nf-core style version of Hello Nextflow", + "type": "object", + "$defs": { + "input_output_options": { + "title": "Input/output options", + "type": "object", + "fa_icon": "fas fa-terminal", + "description": "Define where the pipeline should find input data and save output data.", + "required": ["input", "outdir", "batch"], + "properties": { + "input": { + "type": "string", + "format": "file-path", + "exists": true, + "schema": "assets/schema_input.json", + "mimetype": "text/csv", + "pattern": "^\\S+\\.csv$", + "description": "Path to comma-separated file containing information about the samples in the experiment.", + "help_text": "You will need to create a design file with information about the samples in your experiment before running the pipeline. Use this parameter to specify its location. It has to be a comma-separated file with 3 columns, and a header row.", + "fa_icon": "fas fa-file-csv" + }, + "batch": { + "type": "string", + "description": "Name for this batch of greetings", + "fa_icon": "fas fa-layer-group" + }, + "outdir": { + "type": "string", + "format": "directory-path", + "description": "The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure.", + "fa_icon": "fas fa-folder-open" + } + } + }, + "institutional_config_options": { + "title": "Institutional config options", + "type": "object", + "fa_icon": "fas fa-university", + "description": "Parameters used to describe centralised config profiles. These should not be edited.", + "help_text": "The centralised nf-core configuration profiles use a handful of pipeline parameters to describe themselves. This information is then printed to the Nextflow log when you run a pipeline. You should not need to change these values when you run a pipeline.", + "properties": { + "custom_config_version": { + "type": "string", + "description": "Git commit id for Institutional configs.", + "default": "master", + "hidden": true, + "fa_icon": "fas fa-users-cog" + }, + "custom_config_base": { + "type": "string", + "description": "Base directory for Institutional configs.", + "default": "https://raw.githubusercontent.com/nf-core/configs/master", + "hidden": true, + "help_text": "If you're running offline, Nextflow will not be able to fetch the institutional config files from the internet. If you don't need them, then this is not a problem. If you do need them, you should download the files from the repo and tell Nextflow where to find them with this parameter.", + "fa_icon": "fas fa-users-cog" + }, + "config_profile_name": { + "type": "string", + "description": "Institutional config name.", + "hidden": true, + "fa_icon": "fas fa-users-cog" + }, + "config_profile_description": { + "type": "string", + "description": "Institutional config description.", + "hidden": true, + "fa_icon": "fas fa-users-cog" + }, + "config_profile_contact": { + "type": "string", + "description": "Institutional config contact information.", + "hidden": true, + "fa_icon": "fas fa-users-cog" + }, + "config_profile_url": { + "type": "string", + "description": "Institutional config URL link.", + "hidden": true, + "fa_icon": "fas fa-users-cog" + } + } + }, + "generic_options": { + "title": "Generic options", + "type": "object", + "fa_icon": "fas fa-file-import", + "description": "Less common options for the pipeline, typically set in a config file.", + "help_text": "These options are common to all nf-core pipelines and allow you to customise some of the core preferences for how the pipeline runs.\n\nTypically these options would be set in a Nextflow config file loaded for all pipeline runs, such as `~/.nextflow/config`.", + "properties": { + "version": { + "type": "boolean", + "description": "Display version and exit.", + "fa_icon": "fas fa-question-circle", + "hidden": true + }, + "publish_dir_mode": { + "type": "string", + "default": "copy", + "description": "Method used to save pipeline results to output directory.", + "help_text": "The Nextflow `publishDir` option specifies which intermediate files should be saved to the output directory. This option tells the pipeline what method should be used to move these files. See [Nextflow docs](https://www.nextflow.io/docs/latest/process.html#publishdir) for details.", + "fa_icon": "fas fa-copy", + "enum": [ + "symlink", + "rellink", + "link", + "copy", + "copyNoFollow", + "move" + ], + "hidden": true + }, + "monochrome_logs": { + "type": "boolean", + "description": "Do not use coloured log outputs.", + "fa_icon": "fas fa-palette", + "hidden": true + }, + "validate_params": { + "type": "boolean", + "description": "Boolean whether to validate parameters against the schema at runtime", + "default": true, + "fa_icon": "fas fa-check-square", + "hidden": true + }, + "pipelines_testdata_base_path": { + "type": "string", + "fa_icon": "far fa-check-circle", + "description": "Base URL or local path to location of pipeline test dataset files", + "default": "https://raw.githubusercontent.com/nf-core/test-datasets/", + "hidden": true + }, + "trace_report_suffix": { + "type": "string", + "fa_icon": "far calendar", + "description": "Suffix to add to the trace report filename. Default is the date and time in the format yyyy-MM-dd_HH-mm-ss.", + "hidden": true + }, + "help": { + "type": ["boolean", "string"], + "description": "Display the help message." + }, + "help_full": { + "type": "boolean", + "description": "Display the full detailed help message." + }, + "show_hidden": { + "type": "boolean", + "description": "Display hidden parameters in the help message (only works when --help or --help_full are provided)." + } + } + } + }, + "allOf": [ + { + "$ref": "#/$defs/input_output_options" + }, + { + "$ref": "#/$defs/institutional_config_options" + }, + { + "$ref": "#/$defs/generic_options" + } + ] +} diff --git a/hello-nf-core/solutions/core-hello-part5/subworkflows/local/utils_nfcore_hello_pipeline/main.nf b/hello-nf-core/solutions/core-hello-part5/subworkflows/local/utils_nfcore_hello_pipeline/main.nf new file mode 100644 index 0000000000..93c9f874cc --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/subworkflows/local/utils_nfcore_hello_pipeline/main.nf @@ -0,0 +1,136 @@ +// +// Subworkflow with functionality specific to the core/hello pipeline +// + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + IMPORT FUNCTIONS / MODULES / SUBWORKFLOWS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +include { UTILS_NFSCHEMA_PLUGIN } from '../../nf-core/utils_nfschema_plugin' +include { paramsSummaryMap } from 'plugin/nf-schema' +include { samplesheetToList } from 'plugin/nf-schema' +include { paramsHelp } from 'plugin/nf-schema' +include { completionSummary } from '../../nf-core/utils_nfcore_pipeline' +include { UTILS_NFCORE_PIPELINE } from '../../nf-core/utils_nfcore_pipeline' +include { UTILS_NEXTFLOW_PIPELINE } from '../../nf-core/utils_nextflow_pipeline' + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SUBWORKFLOW TO INITIALISE PIPELINE +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow PIPELINE_INITIALISATION { + + take: + version // boolean: Display version and exit + validate_params // boolean: Boolean whether to validate parameters against the schema at runtime + monochrome_logs // boolean: Do not use coloured log outputs + nextflow_cli_args // array: List of positional nextflow CLI args + outdir // string: The output directory where the results will be saved + input // string: Path to input samplesheet + help // boolean: Display help message and exit + help_full // boolean: Show the full help message + show_hidden // boolean: Show hidden parameters in the help message + + main: + + ch_versions = Channel.empty() + + // + // Print version and exit if required and dump pipeline parameters to JSON file + // + UTILS_NEXTFLOW_PIPELINE ( + version, + true, + outdir, + workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1 + ) + + // + // Validate parameters and generate parameter summary to stdout + // + command = "nextflow run ${workflow.manifest.name} -profile --input samplesheet.csv --outdir " + + UTILS_NFSCHEMA_PLUGIN ( + workflow, + validate_params, + null, + help, + help_full, + show_hidden, + "", + "", + command + ) + + // + // Check config provided to the pipeline + // + UTILS_NFCORE_PIPELINE ( + nextflow_cli_args + ) + + // + // Create channel from input file provided through params.input + // + + ch_samplesheet = channel.fromPath(params.input) + .splitCsv() + .map { line -> line[0] } + + emit: + samplesheet = ch_samplesheet + versions = ch_versions +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SUBWORKFLOW FOR PIPELINE COMPLETION +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow PIPELINE_COMPLETION { + + take: + outdir // path: Path to output directory where results will be published + monochrome_logs // boolean: Disable ANSI colour codes in log output + + main: + summary_params = paramsSummaryMap(workflow, parameters_schema: "nextflow_schema.json") + + // + // Completion email and summary + // + workflow.onComplete { + + completionSummary(monochrome_logs) + } + + workflow.onError { + log.error "Pipeline failed. Please refer to troubleshooting docs: https://nf-co.re/docs/usage/troubleshooting" + } +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + FUNCTIONS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +// +// Validate channels from input samplesheet +// +def validateInputSamplesheet(input) { + def (metas, fastqs) = input[1..2] + + // Check that multiple runs of the same sample are of the same datatype i.e. single-end / paired-end + def endedness_ok = metas.collect{ meta -> meta.single_end }.unique().size == 1 + if (!endedness_ok) { + error("Please check input samplesheet -> Multiple runs of a sample must be of the same datatype i.e. single-end or paired-end: ${metas[0].id}") + } + + return [ metas[0], fastqs ] +} diff --git a/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nextflow_pipeline/main.nf b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nextflow_pipeline/main.nf new file mode 100644 index 0000000000..d6e593e852 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nextflow_pipeline/main.nf @@ -0,0 +1,126 @@ +// +// Subworkflow with functionality that may be useful for any Nextflow pipeline +// + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SUBWORKFLOW DEFINITION +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow UTILS_NEXTFLOW_PIPELINE { + take: + print_version // boolean: print version + dump_parameters // boolean: dump parameters + outdir // path: base directory used to publish pipeline results + check_conda_channels // boolean: check conda channels + + main: + + // + // Print workflow version and exit on --version + // + if (print_version) { + log.info("${workflow.manifest.name} ${getWorkflowVersion()}") + System.exit(0) + } + + // + // Dump pipeline parameters to a JSON file + // + if (dump_parameters && outdir) { + dumpParametersToJSON(outdir) + } + + // + // When running with Conda, warn if channels have not been set-up appropriately + // + if (check_conda_channels) { + checkCondaChannels() + } + + emit: + dummy_emit = true +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + FUNCTIONS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +// +// Generate version string +// +def getWorkflowVersion() { + def version_string = "" as String + if (workflow.manifest.version) { + def prefix_v = workflow.manifest.version[0] != 'v' ? 'v' : '' + version_string += "${prefix_v}${workflow.manifest.version}" + } + + if (workflow.commitId) { + def git_shortsha = workflow.commitId.substring(0, 7) + version_string += "-g${git_shortsha}" + } + + return version_string +} + +// +// Dump pipeline parameters to a JSON file +// +def dumpParametersToJSON(outdir) { + def timestamp = new java.util.Date().format('yyyy-MM-dd_HH-mm-ss') + def filename = "params_${timestamp}.json" + def temp_pf = new File(workflow.launchDir.toString(), ".${filename}") + def jsonStr = groovy.json.JsonOutput.toJson(params) + temp_pf.text = groovy.json.JsonOutput.prettyPrint(jsonStr) + + nextflow.extension.FilesEx.copyTo(temp_pf.toPath(), "${outdir}/pipeline_info/params_${timestamp}.json") + temp_pf.delete() +} + +// +// When running with -profile conda, warn if channels have not been set-up appropriately +// +def checkCondaChannels() { + def parser = new org.yaml.snakeyaml.Yaml() + def channels = [] + try { + def config = parser.load("conda config --show channels".execute().text) + channels = config.channels + } + catch (NullPointerException e) { + log.debug(e) + log.warn("Could not verify conda channel configuration.") + return null + } + catch (IOException e) { + log.debug(e) + log.warn("Could not verify conda channel configuration.") + return null + } + + // Check that all channels are present + // This channel list is ordered by required channel priority. + def required_channels_in_order = ['conda-forge', 'bioconda'] + def channels_missing = ((required_channels_in_order as Set) - (channels as Set)) as Boolean + + // Check that they are in the right order + def channel_priority_violation = required_channels_in_order != channels.findAll { ch -> ch in required_channels_in_order } + + if (channels_missing | channel_priority_violation) { + log.warn """\ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + There is a problem with your Conda configuration! + You will need to set-up the conda-forge and bioconda channels correctly. + Please refer to https://bioconda.github.io/ + The observed channel order is + ${channels} + but the following channel order is required: + ${required_channels_in_order} + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + """.stripIndent(true) + } +} diff --git a/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nextflow_pipeline/meta.yml b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nextflow_pipeline/meta.yml new file mode 100644 index 0000000000..e5c3a0a828 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nextflow_pipeline/meta.yml @@ -0,0 +1,38 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "UTILS_NEXTFLOW_PIPELINE" +description: Subworkflow with functionality that may be useful for any Nextflow pipeline +keywords: + - utility + - pipeline + - initialise + - version +components: [] +input: + - print_version: + type: boolean + description: | + Print the version of the pipeline and exit + - dump_parameters: + type: boolean + description: | + Dump the parameters of the pipeline to a JSON file + - output_directory: + type: directory + description: Path to output dir to write JSON file to. + pattern: "results/" + - check_conda_channel: + type: boolean + description: | + Check if the conda channel priority is correct. +output: + - dummy_emit: + type: boolean + description: | + Dummy emit to make nf-core subworkflows lint happy +authors: + - "@adamrtalbot" + - "@drpatelh" +maintainers: + - "@adamrtalbot" + - "@drpatelh" + - "@maxulysse" diff --git a/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test new file mode 100644 index 0000000000..68718e4f59 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test @@ -0,0 +1,54 @@ + +nextflow_function { + + name "Test Functions" + script "subworkflows/nf-core/utils_nextflow_pipeline/main.nf" + config "subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config" + tag 'subworkflows' + tag 'utils_nextflow_pipeline' + tag 'subworkflows/utils_nextflow_pipeline' + + test("Test Function getWorkflowVersion") { + + function "getWorkflowVersion" + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function dumpParametersToJSON") { + + function "dumpParametersToJSON" + + when { + function { + """ + // define inputs of the function here. Example: + input[0] = "$outputDir" + """.stripIndent() + } + } + + then { + assertAll( + { assert function.success } + ) + } + } + + test("Test Function checkCondaChannels") { + + function "checkCondaChannels" + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } +} diff --git a/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap new file mode 100644 index 0000000000..e3f0baf473 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap @@ -0,0 +1,20 @@ +{ + "Test Function getWorkflowVersion": { + "content": [ + "v9.9.9" + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:02:05.308243" + }, + "Test Function checkCondaChannels": { + "content": null, + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:02:12.425833" + } +} \ No newline at end of file diff --git a/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test new file mode 100644 index 0000000000..02dbf094cd --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test @@ -0,0 +1,113 @@ +nextflow_workflow { + + name "Test Workflow UTILS_NEXTFLOW_PIPELINE" + script "../main.nf" + config "subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config" + workflow "UTILS_NEXTFLOW_PIPELINE" + tag 'subworkflows' + tag 'utils_nextflow_pipeline' + tag 'subworkflows/utils_nextflow_pipeline' + + test("Should run no inputs") { + + when { + workflow { + """ + print_version = false + dump_parameters = false + outdir = null + check_conda_channels = false + + input[0] = print_version + input[1] = dump_parameters + input[2] = outdir + input[3] = check_conda_channels + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } + + test("Should print version") { + + when { + workflow { + """ + print_version = true + dump_parameters = false + outdir = null + check_conda_channels = false + + input[0] = print_version + input[1] = dump_parameters + input[2] = outdir + input[3] = check_conda_channels + """ + } + } + + then { + expect { + with(workflow) { + assert success + assert "nextflow_workflow v9.9.9" in stdout + } + } + } + } + + test("Should dump params") { + + when { + workflow { + """ + print_version = false + dump_parameters = true + outdir = 'results' + check_conda_channels = false + + input[0] = false + input[1] = true + input[2] = outdir + input[3] = false + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } + + test("Should not create params JSON if no output directory") { + + when { + workflow { + """ + print_version = false + dump_parameters = true + outdir = null + check_conda_channels = false + + input[0] = false + input[1] = true + input[2] = outdir + input[3] = false + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } +} diff --git a/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config new file mode 100644 index 0000000000..a09572e5bb --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config @@ -0,0 +1,9 @@ +manifest { + name = 'nextflow_workflow' + author = """nf-core""" + homePage = 'https://127.0.0.1' + description = """Dummy pipeline""" + nextflowVersion = '!>=23.04.0' + version = '9.9.9' + doi = 'https://doi.org/10.5281/zenodo.5070524' +} diff --git a/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/main.nf b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/main.nf new file mode 100644 index 0000000000..bfd258760d --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/main.nf @@ -0,0 +1,419 @@ +// +// Subworkflow with utility functions specific to the nf-core pipeline template +// + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SUBWORKFLOW DEFINITION +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow UTILS_NFCORE_PIPELINE { + take: + nextflow_cli_args + + main: + valid_config = checkConfigProvided() + checkProfileProvided(nextflow_cli_args) + + emit: + valid_config +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + FUNCTIONS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +// +// Warn if a -profile or Nextflow config has not been provided to run the pipeline +// +def checkConfigProvided() { + def valid_config = true as Boolean + if (workflow.profile == 'standard' && workflow.configFiles.size() <= 1) { + log.warn( + "[${workflow.manifest.name}] You are attempting to run the pipeline without any custom configuration!\n\n" + "This will be dependent on your local compute environment but can be achieved via one or more of the following:\n" + " (1) Using an existing pipeline profile e.g. `-profile docker` or `-profile singularity`\n" + " (2) Using an existing nf-core/configs for your Institution e.g. `-profile crick` or `-profile uppmax`\n" + " (3) Using your own local custom config e.g. `-c /path/to/your/custom.config`\n\n" + "Please refer to the quick start section and usage docs for the pipeline.\n " + ) + valid_config = false + } + return valid_config +} + +// +// Exit pipeline if --profile contains spaces +// +def checkProfileProvided(nextflow_cli_args) { + if (workflow.profile.endsWith(',')) { + error( + "The `-profile` option cannot end with a trailing comma, please remove it and re-run the pipeline!\n" + "HINT: A common mistake is to provide multiple values separated by spaces e.g. `-profile test, docker`.\n" + ) + } + if (nextflow_cli_args[0]) { + log.warn( + "nf-core pipelines do not accept positional arguments. The positional argument `${nextflow_cli_args[0]}` has been detected.\n" + "HINT: A common mistake is to provide multiple values separated by spaces e.g. `-profile test, docker`.\n" + ) + } +} + +// +// Generate workflow version string +// +def getWorkflowVersion() { + def version_string = "" as String + if (workflow.manifest.version) { + def prefix_v = workflow.manifest.version[0] != 'v' ? 'v' : '' + version_string += "${prefix_v}${workflow.manifest.version}" + } + + if (workflow.commitId) { + def git_shortsha = workflow.commitId.substring(0, 7) + version_string += "-g${git_shortsha}" + } + + return version_string +} + +// +// Get software versions for pipeline +// +def processVersionsFromYAML(yaml_file) { + def yaml = new org.yaml.snakeyaml.Yaml() + def versions = yaml.load(yaml_file).collectEntries { k, v -> [k.tokenize(':')[-1], v] } + return yaml.dumpAsMap(versions).trim() +} + +// +// Get workflow version for pipeline +// +def workflowVersionToYAML() { + return """ + Workflow: + ${workflow.manifest.name}: ${getWorkflowVersion()} + Nextflow: ${workflow.nextflow.version} + """.stripIndent().trim() +} + +// +// Get channel of software versions used in pipeline in YAML format +// +def softwareVersionsToYAML(ch_versions) { + return ch_versions.unique().map { version -> processVersionsFromYAML(version) }.unique().mix(Channel.of(workflowVersionToYAML())) +} + +// +// Get workflow summary for MultiQC +// +def paramsSummaryMultiqc(summary_params) { + def summary_section = '' + summary_params + .keySet() + .each { group -> + def group_params = summary_params.get(group) + // This gets the parameters of that particular group + if (group_params) { + summary_section += "

${group}

\n" + summary_section += "
\n" + group_params + .keySet() + .sort() + .each { param -> + summary_section += "
${param}
${group_params.get(param) ?: 'N/A'}
\n" + } + summary_section += "
\n" + } + } + + def yaml_file_text = "id: '${workflow.manifest.name.replace('/', '-')}-summary'\n" as String + yaml_file_text += "description: ' - this information is collected when the pipeline is started.'\n" + yaml_file_text += "section_name: '${workflow.manifest.name} Workflow Summary'\n" + yaml_file_text += "section_href: 'https://github.com/${workflow.manifest.name}'\n" + yaml_file_text += "plot_type: 'html'\n" + yaml_file_text += "data: |\n" + yaml_file_text += "${summary_section}" + + return yaml_file_text +} + +// +// ANSII colours used for terminal logging +// +def logColours(monochrome_logs=true) { + def colorcodes = [:] as Map + + // Reset / Meta + colorcodes['reset'] = monochrome_logs ? '' : "\033[0m" + colorcodes['bold'] = monochrome_logs ? '' : "\033[1m" + colorcodes['dim'] = monochrome_logs ? '' : "\033[2m" + colorcodes['underlined'] = monochrome_logs ? '' : "\033[4m" + colorcodes['blink'] = monochrome_logs ? '' : "\033[5m" + colorcodes['reverse'] = monochrome_logs ? '' : "\033[7m" + colorcodes['hidden'] = monochrome_logs ? '' : "\033[8m" + + // Regular Colors + colorcodes['black'] = monochrome_logs ? '' : "\033[0;30m" + colorcodes['red'] = monochrome_logs ? '' : "\033[0;31m" + colorcodes['green'] = monochrome_logs ? '' : "\033[0;32m" + colorcodes['yellow'] = monochrome_logs ? '' : "\033[0;33m" + colorcodes['blue'] = monochrome_logs ? '' : "\033[0;34m" + colorcodes['purple'] = monochrome_logs ? '' : "\033[0;35m" + colorcodes['cyan'] = monochrome_logs ? '' : "\033[0;36m" + colorcodes['white'] = monochrome_logs ? '' : "\033[0;37m" + + // Bold + colorcodes['bblack'] = monochrome_logs ? '' : "\033[1;30m" + colorcodes['bred'] = monochrome_logs ? '' : "\033[1;31m" + colorcodes['bgreen'] = monochrome_logs ? '' : "\033[1;32m" + colorcodes['byellow'] = monochrome_logs ? '' : "\033[1;33m" + colorcodes['bblue'] = monochrome_logs ? '' : "\033[1;34m" + colorcodes['bpurple'] = monochrome_logs ? '' : "\033[1;35m" + colorcodes['bcyan'] = monochrome_logs ? '' : "\033[1;36m" + colorcodes['bwhite'] = monochrome_logs ? '' : "\033[1;37m" + + // Underline + colorcodes['ublack'] = monochrome_logs ? '' : "\033[4;30m" + colorcodes['ured'] = monochrome_logs ? '' : "\033[4;31m" + colorcodes['ugreen'] = monochrome_logs ? '' : "\033[4;32m" + colorcodes['uyellow'] = monochrome_logs ? '' : "\033[4;33m" + colorcodes['ublue'] = monochrome_logs ? '' : "\033[4;34m" + colorcodes['upurple'] = monochrome_logs ? '' : "\033[4;35m" + colorcodes['ucyan'] = monochrome_logs ? '' : "\033[4;36m" + colorcodes['uwhite'] = monochrome_logs ? '' : "\033[4;37m" + + // High Intensity + colorcodes['iblack'] = monochrome_logs ? '' : "\033[0;90m" + colorcodes['ired'] = monochrome_logs ? '' : "\033[0;91m" + colorcodes['igreen'] = monochrome_logs ? '' : "\033[0;92m" + colorcodes['iyellow'] = monochrome_logs ? '' : "\033[0;93m" + colorcodes['iblue'] = monochrome_logs ? '' : "\033[0;94m" + colorcodes['ipurple'] = monochrome_logs ? '' : "\033[0;95m" + colorcodes['icyan'] = monochrome_logs ? '' : "\033[0;96m" + colorcodes['iwhite'] = monochrome_logs ? '' : "\033[0;97m" + + // Bold High Intensity + colorcodes['biblack'] = monochrome_logs ? '' : "\033[1;90m" + colorcodes['bired'] = monochrome_logs ? '' : "\033[1;91m" + colorcodes['bigreen'] = monochrome_logs ? '' : "\033[1;92m" + colorcodes['biyellow'] = monochrome_logs ? '' : "\033[1;93m" + colorcodes['biblue'] = monochrome_logs ? '' : "\033[1;94m" + colorcodes['bipurple'] = monochrome_logs ? '' : "\033[1;95m" + colorcodes['bicyan'] = monochrome_logs ? '' : "\033[1;96m" + colorcodes['biwhite'] = monochrome_logs ? '' : "\033[1;97m" + + return colorcodes +} + +// Return a single report from an object that may be a Path or List +// +def getSingleReport(multiqc_reports) { + if (multiqc_reports instanceof Path) { + return multiqc_reports + } else if (multiqc_reports instanceof List) { + if (multiqc_reports.size() == 0) { + log.warn("[${workflow.manifest.name}] No reports found from process 'MULTIQC'") + return null + } else if (multiqc_reports.size() == 1) { + return multiqc_reports.first() + } else { + log.warn("[${workflow.manifest.name}] Found multiple reports from process 'MULTIQC', will use only one") + return multiqc_reports.first() + } + } else { + return null + } +} + +// +// Construct and send completion email +// +def completionEmail(summary_params, email, email_on_fail, plaintext_email, outdir, monochrome_logs=true, multiqc_report=null) { + + // Set up the e-mail variables + def subject = "[${workflow.manifest.name}] Successful: ${workflow.runName}" + if (!workflow.success) { + subject = "[${workflow.manifest.name}] FAILED: ${workflow.runName}" + } + + def summary = [:] + summary_params + .keySet() + .sort() + .each { group -> + summary << summary_params[group] + } + + def misc_fields = [:] + misc_fields['Date Started'] = workflow.start + misc_fields['Date Completed'] = workflow.complete + misc_fields['Pipeline script file path'] = workflow.scriptFile + misc_fields['Pipeline script hash ID'] = workflow.scriptId + if (workflow.repository) { + misc_fields['Pipeline repository Git URL'] = workflow.repository + } + if (workflow.commitId) { + misc_fields['Pipeline repository Git Commit'] = workflow.commitId + } + if (workflow.revision) { + misc_fields['Pipeline Git branch/tag'] = workflow.revision + } + misc_fields['Nextflow Version'] = workflow.nextflow.version + misc_fields['Nextflow Build'] = workflow.nextflow.build + misc_fields['Nextflow Compile Timestamp'] = workflow.nextflow.timestamp + + def email_fields = [:] + email_fields['version'] = getWorkflowVersion() + email_fields['runName'] = workflow.runName + email_fields['success'] = workflow.success + email_fields['dateComplete'] = workflow.complete + email_fields['duration'] = workflow.duration + email_fields['exitStatus'] = workflow.exitStatus + email_fields['errorMessage'] = (workflow.errorMessage ?: 'None') + email_fields['errorReport'] = (workflow.errorReport ?: 'None') + email_fields['commandLine'] = workflow.commandLine + email_fields['projectDir'] = workflow.projectDir + email_fields['summary'] = summary << misc_fields + + // On success try attach the multiqc report + def mqc_report = getSingleReport(multiqc_report) + + // Check if we are only sending emails on failure + def email_address = email + if (!email && email_on_fail && !workflow.success) { + email_address = email_on_fail + } + + // Render the TXT template + def engine = new groovy.text.GStringTemplateEngine() + def tf = new File("${workflow.projectDir}/assets/email_template.txt") + def txt_template = engine.createTemplate(tf).make(email_fields) + def email_txt = txt_template.toString() + + // Render the HTML template + def hf = new File("${workflow.projectDir}/assets/email_template.html") + def html_template = engine.createTemplate(hf).make(email_fields) + def email_html = html_template.toString() + + // Render the sendmail template + def max_multiqc_email_size = (params.containsKey('max_multiqc_email_size') ? params.max_multiqc_email_size : 0) as MemoryUnit + def smail_fields = [email: email_address, subject: subject, email_txt: email_txt, email_html: email_html, projectDir: "${workflow.projectDir}", mqcFile: mqc_report, mqcMaxSize: max_multiqc_email_size.toBytes()] + def sf = new File("${workflow.projectDir}/assets/sendmail_template.txt") + def sendmail_template = engine.createTemplate(sf).make(smail_fields) + def sendmail_html = sendmail_template.toString() + + // Send the HTML e-mail + def colors = logColours(monochrome_logs) as Map + if (email_address) { + try { + if (plaintext_email) { + new org.codehaus.groovy.GroovyException('Send plaintext e-mail, not HTML') + } + // Try to send HTML e-mail using sendmail + def sendmail_tf = new File(workflow.launchDir.toString(), ".sendmail_tmp.html") + sendmail_tf.withWriter { w -> w << sendmail_html } + ['sendmail', '-t'].execute() << sendmail_html + log.info("-${colors.purple}[${workflow.manifest.name}]${colors.green} Sent summary e-mail to ${email_address} (sendmail)-") + } + catch (Exception msg) { + log.debug(msg.toString()) + log.debug("Trying with mail instead of sendmail") + // Catch failures and try with plaintext + def mail_cmd = ['mail', '-s', subject, '--content-type=text/html', email_address] + mail_cmd.execute() << email_html + log.info("-${colors.purple}[${workflow.manifest.name}]${colors.green} Sent summary e-mail to ${email_address} (mail)-") + } + } + + // Write summary e-mail HTML to a file + def output_hf = new File(workflow.launchDir.toString(), ".pipeline_report.html") + output_hf.withWriter { w -> w << email_html } + nextflow.extension.FilesEx.copyTo(output_hf.toPath(), "${outdir}/pipeline_info/pipeline_report.html") + output_hf.delete() + + // Write summary e-mail TXT to a file + def output_tf = new File(workflow.launchDir.toString(), ".pipeline_report.txt") + output_tf.withWriter { w -> w << email_txt } + nextflow.extension.FilesEx.copyTo(output_tf.toPath(), "${outdir}/pipeline_info/pipeline_report.txt") + output_tf.delete() +} + +// +// Print pipeline summary on completion +// +def completionSummary(monochrome_logs=true) { + def colors = logColours(monochrome_logs) as Map + if (workflow.success) { + if (workflow.stats.ignoredCount == 0) { + log.info("-${colors.purple}[${workflow.manifest.name}]${colors.green} Pipeline completed successfully${colors.reset}-") + } + else { + log.info("-${colors.purple}[${workflow.manifest.name}]${colors.yellow} Pipeline completed successfully, but with errored process(es) ${colors.reset}-") + } + } + else { + log.info("-${colors.purple}[${workflow.manifest.name}]${colors.red} Pipeline completed with errors${colors.reset}-") + } +} + +// +// Construct and send a notification to a web server as JSON e.g. Microsoft Teams and Slack +// +def imNotification(summary_params, hook_url) { + def summary = [:] + summary_params + .keySet() + .sort() + .each { group -> + summary << summary_params[group] + } + + def misc_fields = [:] + misc_fields['start'] = workflow.start + misc_fields['complete'] = workflow.complete + misc_fields['scriptfile'] = workflow.scriptFile + misc_fields['scriptid'] = workflow.scriptId + if (workflow.repository) { + misc_fields['repository'] = workflow.repository + } + if (workflow.commitId) { + misc_fields['commitid'] = workflow.commitId + } + if (workflow.revision) { + misc_fields['revision'] = workflow.revision + } + misc_fields['nxf_version'] = workflow.nextflow.version + misc_fields['nxf_build'] = workflow.nextflow.build + misc_fields['nxf_timestamp'] = workflow.nextflow.timestamp + + def msg_fields = [:] + msg_fields['version'] = getWorkflowVersion() + msg_fields['runName'] = workflow.runName + msg_fields['success'] = workflow.success + msg_fields['dateComplete'] = workflow.complete + msg_fields['duration'] = workflow.duration + msg_fields['exitStatus'] = workflow.exitStatus + msg_fields['errorMessage'] = (workflow.errorMessage ?: 'None') + msg_fields['errorReport'] = (workflow.errorReport ?: 'None') + msg_fields['commandLine'] = workflow.commandLine.replaceFirst(/ +--hook_url +[^ ]+/, "") + msg_fields['projectDir'] = workflow.projectDir + msg_fields['summary'] = summary << misc_fields + + // Render the JSON template + def engine = new groovy.text.GStringTemplateEngine() + // Different JSON depending on the service provider + // Defaults to "Adaptive Cards" (https://adaptivecards.io), except Slack which has its own format + def json_path = hook_url.contains("hooks.slack.com") ? "slackreport.json" : "adaptivecard.json" + def hf = new File("${workflow.projectDir}/assets/${json_path}") + def json_template = engine.createTemplate(hf).make(msg_fields) + def json_message = json_template.toString() + + // POST + def post = new URL(hook_url).openConnection() + post.setRequestMethod("POST") + post.setDoOutput(true) + post.setRequestProperty("Content-Type", "application/json") + post.getOutputStream().write(json_message.getBytes("UTF-8")) + def postRC = post.getResponseCode() + if (!postRC.equals(200)) { + log.warn(post.getErrorStream().getText()) + } +} diff --git a/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/meta.yml b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/meta.yml new file mode 100644 index 0000000000..d08d24342d --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/meta.yml @@ -0,0 +1,24 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "UTILS_NFCORE_PIPELINE" +description: Subworkflow with utility functions specific to the nf-core pipeline template +keywords: + - utility + - pipeline + - initialise + - version +components: [] +input: + - nextflow_cli_args: + type: list + description: | + Nextflow CLI positional arguments +output: + - success: + type: boolean + description: | + Dummy output to indicate success +authors: + - "@adamrtalbot" +maintainers: + - "@adamrtalbot" + - "@maxulysse" diff --git a/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test new file mode 100644 index 0000000000..f117040cbd --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test @@ -0,0 +1,126 @@ + +nextflow_function { + + name "Test Functions" + script "../main.nf" + config "subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config" + tag "subworkflows" + tag "subworkflows_nfcore" + tag "utils_nfcore_pipeline" + tag "subworkflows/utils_nfcore_pipeline" + + test("Test Function checkConfigProvided") { + + function "checkConfigProvided" + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function checkProfileProvided") { + + function "checkProfileProvided" + + when { + function { + """ + input[0] = [] + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function without logColours") { + + function "logColours" + + when { + function { + """ + input[0] = true + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function with logColours") { + function "logColours" + + when { + function { + """ + input[0] = false + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function getSingleReport with a single file") { + function "getSingleReport" + + when { + function { + """ + input[0] = file(params.modules_testdata_base_path + '/generic/tsv/test.tsv', checkIfExists: true) + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert function.result.contains("test.tsv") } + ) + } + } + + test("Test Function getSingleReport with multiple files") { + function "getSingleReport" + + when { + function { + """ + input[0] = [ + file(params.modules_testdata_base_path + '/generic/tsv/test.tsv', checkIfExists: true), + file(params.modules_testdata_base_path + '/generic/tsv/network.tsv', checkIfExists: true), + file(params.modules_testdata_base_path + '/generic/tsv/expression.tsv', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert function.result.contains("test.tsv") }, + { assert !function.result.contains("network.tsv") }, + { assert !function.result.contains("expression.tsv") } + ) + } + } +} diff --git a/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap new file mode 100644 index 0000000000..02c6701413 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap @@ -0,0 +1,136 @@ +{ + "Test Function checkProfileProvided": { + "content": null, + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:03:03.360873" + }, + "Test Function checkConfigProvided": { + "content": [ + true + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:02:59.729647" + }, + "Test Function without logColours": { + "content": [ + { + "reset": "", + "bold": "", + "dim": "", + "underlined": "", + "blink": "", + "reverse": "", + "hidden": "", + "black": "", + "red": "", + "green": "", + "yellow": "", + "blue": "", + "purple": "", + "cyan": "", + "white": "", + "bblack": "", + "bred": "", + "bgreen": "", + "byellow": "", + "bblue": "", + "bpurple": "", + "bcyan": "", + "bwhite": "", + "ublack": "", + "ured": "", + "ugreen": "", + "uyellow": "", + "ublue": "", + "upurple": "", + "ucyan": "", + "uwhite": "", + "iblack": "", + "ired": "", + "igreen": "", + "iyellow": "", + "iblue": "", + "ipurple": "", + "icyan": "", + "iwhite": "", + "biblack": "", + "bired": "", + "bigreen": "", + "biyellow": "", + "biblue": "", + "bipurple": "", + "bicyan": "", + "biwhite": "" + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:03:17.969323" + }, + "Test Function with logColours": { + "content": [ + { + "reset": "\u001b[0m", + "bold": "\u001b[1m", + "dim": "\u001b[2m", + "underlined": "\u001b[4m", + "blink": "\u001b[5m", + "reverse": "\u001b[7m", + "hidden": "\u001b[8m", + "black": "\u001b[0;30m", + "red": "\u001b[0;31m", + "green": "\u001b[0;32m", + "yellow": "\u001b[0;33m", + "blue": "\u001b[0;34m", + "purple": "\u001b[0;35m", + "cyan": "\u001b[0;36m", + "white": "\u001b[0;37m", + "bblack": "\u001b[1;30m", + "bred": "\u001b[1;31m", + "bgreen": "\u001b[1;32m", + "byellow": "\u001b[1;33m", + "bblue": "\u001b[1;34m", + "bpurple": "\u001b[1;35m", + "bcyan": "\u001b[1;36m", + "bwhite": "\u001b[1;37m", + "ublack": "\u001b[4;30m", + "ured": "\u001b[4;31m", + "ugreen": "\u001b[4;32m", + "uyellow": "\u001b[4;33m", + "ublue": "\u001b[4;34m", + "upurple": "\u001b[4;35m", + "ucyan": "\u001b[4;36m", + "uwhite": "\u001b[4;37m", + "iblack": "\u001b[0;90m", + "ired": "\u001b[0;91m", + "igreen": "\u001b[0;92m", + "iyellow": "\u001b[0;93m", + "iblue": "\u001b[0;94m", + "ipurple": "\u001b[0;95m", + "icyan": "\u001b[0;96m", + "iwhite": "\u001b[0;97m", + "biblack": "\u001b[1;90m", + "bired": "\u001b[1;91m", + "bigreen": "\u001b[1;92m", + "biyellow": "\u001b[1;93m", + "biblue": "\u001b[1;94m", + "bipurple": "\u001b[1;95m", + "bicyan": "\u001b[1;96m", + "biwhite": "\u001b[1;97m" + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:03:21.714424" + } +} \ No newline at end of file diff --git a/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test new file mode 100644 index 0000000000..8940d32d1e --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test @@ -0,0 +1,29 @@ +nextflow_workflow { + + name "Test Workflow UTILS_NFCORE_PIPELINE" + script "../main.nf" + config "subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config" + workflow "UTILS_NFCORE_PIPELINE" + tag "subworkflows" + tag "subworkflows_nfcore" + tag "utils_nfcore_pipeline" + tag "subworkflows/utils_nfcore_pipeline" + + test("Should run without failures") { + + when { + workflow { + """ + input[0] = [] + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot(workflow.out).match() } + ) + } + } +} diff --git a/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap new file mode 100644 index 0000000000..859d1030fb --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap @@ -0,0 +1,19 @@ +{ + "Should run without failures": { + "content": [ + { + "0": [ + true + ], + "valid_config": [ + true + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:03:25.726491" + } +} \ No newline at end of file diff --git a/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config new file mode 100644 index 0000000000..d0a926bf6d --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config @@ -0,0 +1,9 @@ +manifest { + name = 'nextflow_workflow' + author = """nf-core""" + homePage = 'https://127.0.0.1' + description = """Dummy pipeline""" + nextflowVersion = '!>=23.04.0' + version = '9.9.9' + doi = 'https://doi.org/10.5281/zenodo.5070524' +} diff --git a/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfschema_plugin/main.nf b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfschema_plugin/main.nf new file mode 100644 index 0000000000..ee4738c8d1 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfschema_plugin/main.nf @@ -0,0 +1,74 @@ +// +// Subworkflow that uses the nf-schema plugin to validate parameters and render the parameter summary +// + +include { paramsSummaryLog } from 'plugin/nf-schema' +include { validateParameters } from 'plugin/nf-schema' +include { paramsHelp } from 'plugin/nf-schema' + +workflow UTILS_NFSCHEMA_PLUGIN { + + take: + input_workflow // workflow: the workflow object used by nf-schema to get metadata from the workflow + validate_params // boolean: validate the parameters + parameters_schema // string: path to the parameters JSON schema. + // this has to be the same as the schema given to `validation.parametersSchema` + // when this input is empty it will automatically use the configured schema or + // "${projectDir}/nextflow_schema.json" as default. This input should not be empty + // for meta pipelines + help // boolean: show help message + help_full // boolean: show full help message + show_hidden // boolean: show hidden parameters in help message + before_text // string: text to show before the help message and parameters summary + after_text // string: text to show after the help message and parameters summary + command // string: an example command of the pipeline + + main: + + if(help || help_full) { + help_options = [ + beforeText: before_text, + afterText: after_text, + command: command, + showHidden: show_hidden, + fullHelp: help_full, + ] + if(parameters_schema) { + help_options << [parametersSchema: parameters_schema] + } + log.info paramsHelp( + help_options, + params.help instanceof String ? params.help : "", + ) + exit 0 + } + + // + // Print parameter summary to stdout. This will display the parameters + // that differ from the default given in the JSON schema + // + + summary_options = [:] + if(parameters_schema) { + summary_options << [parametersSchema: parameters_schema] + } + log.info before_text + log.info paramsSummaryLog(summary_options, input_workflow) + log.info after_text + + // + // Validate the parameters using nextflow_schema.json or the schema + // given via the validation.parametersSchema configuration option + // + if(validate_params) { + validateOptions = [:] + if(parameters_schema) { + validateOptions << [parametersSchema: parameters_schema] + } + validateParameters(validateOptions) + } + + emit: + dummy_emit = true +} + diff --git a/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfschema_plugin/meta.yml b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfschema_plugin/meta.yml new file mode 100644 index 0000000000..f7d9f02885 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfschema_plugin/meta.yml @@ -0,0 +1,35 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "utils_nfschema_plugin" +description: Run nf-schema to validate parameters and create a summary of changed parameters +keywords: + - validation + - JSON schema + - plugin + - parameters + - summary +components: [] +input: + - input_workflow: + type: object + description: | + The workflow object of the used pipeline. + This object contains meta data used to create the params summary log + - validate_params: + type: boolean + description: Validate the parameters and error if invalid. + - parameters_schema: + type: string + description: | + Path to the parameters JSON schema. + This has to be the same as the schema given to the `validation.parametersSchema` config + option. When this input is empty it will automatically use the configured schema or + "${projectDir}/nextflow_schema.json" as default. The schema should not be given in this way + for meta pipelines. +output: + - dummy_emit: + type: boolean + description: Dummy emit to make nf-core subworkflows lint happy +authors: + - "@nvnieuwk" +maintainers: + - "@nvnieuwk" diff --git a/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test new file mode 100644 index 0000000000..c977917aac --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test @@ -0,0 +1,173 @@ +nextflow_workflow { + + name "Test Subworkflow UTILS_NFSCHEMA_PLUGIN" + script "../main.nf" + workflow "UTILS_NFSCHEMA_PLUGIN" + + tag "subworkflows" + tag "subworkflows_nfcore" + tag "subworkflows/utils_nfschema_plugin" + tag "plugin/nf-schema" + + config "./nextflow.config" + + test("Should run nothing") { + + when { + + params { + test_data = '' + } + + workflow { + """ + validate_params = false + input[0] = workflow + input[1] = validate_params + input[2] = "" + input[3] = false + input[4] = false + input[5] = false + input[6] = "" + input[7] = "" + input[8] = "" + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } + + test("Should validate params") { + + when { + + params { + test_data = '' + outdir = null + } + + workflow { + """ + validate_params = true + input[0] = workflow + input[1] = validate_params + input[2] = "" + input[3] = false + input[4] = false + input[5] = false + input[6] = "" + input[7] = "" + input[8] = "" + """ + } + } + + then { + assertAll( + { assert workflow.failed }, + { assert workflow.stdout.any { it.contains('ERROR ~ Validation of pipeline parameters failed!') } } + ) + } + } + + test("Should run nothing - custom schema") { + + when { + + params { + test_data = '' + } + + workflow { + """ + validate_params = false + input[0] = workflow + input[1] = validate_params + input[2] = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + input[3] = false + input[4] = false + input[5] = false + input[6] = "" + input[7] = "" + input[8] = "" + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } + + test("Should validate params - custom schema") { + + when { + + params { + test_data = '' + outdir = null + } + + workflow { + """ + validate_params = true + input[0] = workflow + input[1] = validate_params + input[2] = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + input[3] = false + input[4] = false + input[5] = false + input[6] = "" + input[7] = "" + input[8] = "" + """ + } + } + + then { + assertAll( + { assert workflow.failed }, + { assert workflow.stdout.any { it.contains('ERROR ~ Validation of pipeline parameters failed!') } } + ) + } + } + + test("Should create a help message") { + + when { + + params { + test_data = '' + outdir = null + } + + workflow { + """ + validate_params = true + input[0] = workflow + input[1] = validate_params + input[2] = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + input[3] = true + input[4] = false + input[5] = false + input[6] = "Before" + input[7] = "After" + input[8] = "nextflow run test/test" + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } +} diff --git a/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config new file mode 100644 index 0000000000..8d8c73718a --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config @@ -0,0 +1,8 @@ +plugins { + id "nf-schema@2.5.1" +} + +validation { + parametersSchema = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + monochromeLogs = true +} diff --git a/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json new file mode 100644 index 0000000000..91e26fc4a7 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json @@ -0,0 +1,103 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/./master/nextflow_schema.json", + "title": ". pipeline parameters", + "description": "", + "type": "object", + "$defs": { + "input_output_options": { + "title": "Input/output options", + "type": "object", + "fa_icon": "fas fa-terminal", + "description": "Define where the pipeline should find input data and save output data.", + "required": ["outdir"], + "properties": { + "validate_params": { + "type": "boolean", + "description": "Validate parameters?", + "default": true, + "hidden": true + }, + "outdir": { + "type": "string", + "format": "directory-path", + "description": "The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure.", + "fa_icon": "fas fa-folder-open" + }, + "test_data_base": { + "type": "string", + "default": "https://raw.githubusercontent.com/nf-core/test-datasets/modules", + "description": "Base for test data directory", + "hidden": true + }, + "test_data": { + "type": "string", + "description": "Fake test data param", + "hidden": true + } + } + }, + "generic_options": { + "title": "Generic options", + "type": "object", + "fa_icon": "fas fa-file-import", + "description": "Less common options for the pipeline, typically set in a config file.", + "help_text": "These options are common to all nf-core pipelines and allow you to customise some of the core preferences for how the pipeline runs.\n\nTypically these options would be set in a Nextflow config file loaded for all pipeline runs, such as `~/.nextflow/config`.", + "properties": { + "help": { + "type": "boolean", + "description": "Display help text.", + "fa_icon": "fas fa-question-circle", + "hidden": true + }, + "version": { + "type": "boolean", + "description": "Display version and exit.", + "fa_icon": "fas fa-question-circle", + "hidden": true + }, + "logo": { + "type": "boolean", + "default": true, + "description": "Display nf-core logo in console output.", + "fa_icon": "fas fa-image", + "hidden": true + }, + "singularity_pull_docker_container": { + "type": "boolean", + "description": "Pull Singularity container from Docker?", + "hidden": true + }, + "publish_dir_mode": { + "type": "string", + "default": "copy", + "description": "Method used to save pipeline results to output directory.", + "help_text": "The Nextflow `publishDir` option specifies which intermediate files should be saved to the output directory. This option tells the pipeline what method should be used to move these files. See [Nextflow docs](https://www.nextflow.io/docs/latest/process.html#publishdir) for details.", + "fa_icon": "fas fa-copy", + "enum": [ + "symlink", + "rellink", + "link", + "copy", + "copyNoFollow", + "move" + ], + "hidden": true + }, + "monochrome_logs": { + "type": "boolean", + "description": "Use monochrome_logs", + "hidden": true + } + } + } + }, + "allOf": [ + { + "$ref": "#/$defs/input_output_options" + }, + { + "$ref": "#/$defs/generic_options" + } + ] +} diff --git a/hello-nf-core/solutions/core-hello-part5/workflows/hello.nf b/hello-nf-core/solutions/core-hello-part5/workflows/hello.nf new file mode 100644 index 0000000000..1993686608 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-part5/workflows/hello.nf @@ -0,0 +1,67 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ +include { paramsSummaryMap } from 'plugin/nf-schema' +include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' +include { sayHello } from '../modules/local/sayHello.nf' +include { convertToUpper } from '../modules/local/convertToUpper.nf' +include { cowpy } from '../modules/local/cowpy.nf' +include { CAT_CAT } from '../modules/nf-core/cat/cat/main' + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + RUN MAIN WORKFLOW +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow HELLO { + + take: + ch_samplesheet // channel: samplesheet read in from --input + + main: + + ch_versions = Channel.empty() + + // emit a greeting + sayHello(ch_samplesheet) + + // convert the greeting to uppercase + convertToUpper(sayHello.out) + + // create metadata map with batch name as the ID + def cat_meta = [ id: params.batch ] + // create a channel with metadata and files in tuple format + ch_for_cat = convertToUpper.out.collect().map { files -> tuple(cat_meta, files) } + + // concatenate files using the nf-core cat/cat module + CAT_CAT(ch_for_cat) + + // generate ASCII art of the greetings with cowpy + cowpy(CAT_CAT.out.file_out) + + // + // Collate and save software versions + // + softwareVersionsToYAML(ch_versions) + .collectFile( + storeDir: "${params.outdir}/pipeline_info", + name: 'hello_software_' + 'versions.yml', + sort: true, + newLine: true + ).set { ch_collated_versions } + + + emit: + cowpy_hellos = cowpy.out.cowpy_output + versions = ch_versions // channel: [ path(versions.yml) ] + +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + THE END +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ From 8d37b375d56b8b2f50ab20af2e5c42991d58e5c5 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 31 Oct 2025 11:22:11 +0000 Subject: [PATCH 078/113] Some linting fixes --- docs/hello_nf-core/05_input_validation.md | 10 +- .../solutions/core-hello-part5/modules.json | 64 ++-- .../core-hello-part5/nextflow_schema.json | 318 +++++++++--------- 3 files changed, 196 insertions(+), 196 deletions(-) diff --git a/docs/hello_nf-core/05_input_validation.md b/docs/hello_nf-core/05_input_validation.md index 5994b50059..45a08ea0c9 100644 --- a/docs/hello_nf-core/05_input_validation.md +++ b/docs/hello_nf-core/05_input_validation.md @@ -258,11 +258,11 @@ To add the `batch` parameter: 1. Click the **"Add parameter"** button at the top 2. Use the drag handle (⋮⋮) to move the new parameter up into the "Input/output options" group, below the `input` parameter 3. Fill in the parameter details: - - **ID**: `batch` - - **Description**: `Name for this batch of greetings` - - **Type**: `string` - - Check the **Required** checkbox - - Optionally, select an icon from the icon picker (e.g., `fas fa-layer-group`) + - **ID**: `batch` + - **Description**: `Name for this batch of greetings` + - **Type**: `string` + - Check the **Required** checkbox + - Optionally, select an icon from the icon picker (e.g., `fas fa-layer-group`) ![Adding the batch parameter](./img/schema_add.png) diff --git a/hello-nf-core/solutions/core-hello-part5/modules.json b/hello-nf-core/solutions/core-hello-part5/modules.json index 3e65b38609..71a7815a6b 100644 --- a/hello-nf-core/solutions/core-hello-part5/modules.json +++ b/hello-nf-core/solutions/core-hello-part5/modules.json @@ -1,36 +1,36 @@ { - "name": "core/hello", - "homePage": "https://github.com/core/hello", - "repos": { - "https://github.com/nf-core/modules.git": { - "modules": { - "nf-core": { - "cat/cat": { - "branch": "master", - "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", - "installed_by": ["modules"] - } + "name": "core/hello", + "homePage": "https://github.com/core/hello", + "repos": { + "https://github.com/nf-core/modules.git": { + "modules": { + "nf-core": { + "cat/cat": { + "branch": "master", + "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", + "installed_by": ["modules"] + } + } + }, + "subworkflows": { + "nf-core": { + "utils_nextflow_pipeline": { + "branch": "master", + "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "installed_by": ["subworkflows"] + }, + "utils_nfcore_pipeline": { + "branch": "master", + "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "installed_by": ["subworkflows"] + }, + "utils_nfschema_plugin": { + "branch": "master", + "git_sha": "4b406a74dc0449c0401ed87d5bfff4252fd277fd", + "installed_by": ["subworkflows"] + } + } + } } - }, - "subworkflows": { - "nf-core": { - "utils_nextflow_pipeline": { - "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", - "installed_by": ["subworkflows"] - }, - "utils_nfcore_pipeline": { - "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", - "installed_by": ["subworkflows"] - }, - "utils_nfschema_plugin": { - "branch": "master", - "git_sha": "4b406a74dc0449c0401ed87d5bfff4252fd277fd", - "installed_by": ["subworkflows"] - } - } - } } - } } diff --git a/hello-nf-core/solutions/core-hello-part5/nextflow_schema.json b/hello-nf-core/solutions/core-hello-part5/nextflow_schema.json index 008c91eb19..5dcce69565 100644 --- a/hello-nf-core/solutions/core-hello-part5/nextflow_schema.json +++ b/hello-nf-core/solutions/core-hello-part5/nextflow_schema.json @@ -1,168 +1,168 @@ { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://raw.githubusercontent.com/core/hello/main/nextflow_schema.json", - "title": "core/hello pipeline parameters", - "description": "A basic nf-core style version of Hello Nextflow", - "type": "object", - "$defs": { - "input_output_options": { - "title": "Input/output options", - "type": "object", - "fa_icon": "fas fa-terminal", - "description": "Define where the pipeline should find input data and save output data.", - "required": ["input", "outdir", "batch"], - "properties": { - "input": { - "type": "string", - "format": "file-path", - "exists": true, - "schema": "assets/schema_input.json", - "mimetype": "text/csv", - "pattern": "^\\S+\\.csv$", - "description": "Path to comma-separated file containing information about the samples in the experiment.", - "help_text": "You will need to create a design file with information about the samples in your experiment before running the pipeline. Use this parameter to specify its location. It has to be a comma-separated file with 3 columns, and a header row.", - "fa_icon": "fas fa-file-csv" + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/core/hello/main/nextflow_schema.json", + "title": "core/hello pipeline parameters", + "description": "A basic nf-core style version of Hello Nextflow", + "type": "object", + "$defs": { + "input_output_options": { + "title": "Input/output options", + "type": "object", + "fa_icon": "fas fa-terminal", + "description": "Define where the pipeline should find input data and save output data.", + "required": ["input", "outdir", "batch"], + "properties": { + "input": { + "type": "string", + "format": "file-path", + "exists": true, + "schema": "assets/schema_input.json", + "mimetype": "text/csv", + "pattern": "^\\S+\\.csv$", + "description": "Path to comma-separated file containing information about the samples in the experiment.", + "help_text": "You will need to create a design file with information about the samples in your experiment before running the pipeline. Use this parameter to specify its location. It has to be a comma-separated file with 3 columns, and a header row.", + "fa_icon": "fas fa-file-csv" + }, + "batch": { + "type": "string", + "description": "Name for this batch of greetings", + "fa_icon": "fas fa-layer-group" + }, + "outdir": { + "type": "string", + "format": "directory-path", + "description": "The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure.", + "fa_icon": "fas fa-folder-open" + } + } }, - "batch": { - "type": "string", - "description": "Name for this batch of greetings", - "fa_icon": "fas fa-layer-group" + "institutional_config_options": { + "title": "Institutional config options", + "type": "object", + "fa_icon": "fas fa-university", + "description": "Parameters used to describe centralised config profiles. These should not be edited.", + "help_text": "The centralised nf-core configuration profiles use a handful of pipeline parameters to describe themselves. This information is then printed to the Nextflow log when you run a pipeline. You should not need to change these values when you run a pipeline.", + "properties": { + "custom_config_version": { + "type": "string", + "description": "Git commit id for Institutional configs.", + "default": "master", + "hidden": true, + "fa_icon": "fas fa-users-cog" + }, + "custom_config_base": { + "type": "string", + "description": "Base directory for Institutional configs.", + "default": "https://raw.githubusercontent.com/nf-core/configs/master", + "hidden": true, + "help_text": "If you're running offline, Nextflow will not be able to fetch the institutional config files from the internet. If you don't need them, then this is not a problem. If you do need them, you should download the files from the repo and tell Nextflow where to find them with this parameter.", + "fa_icon": "fas fa-users-cog" + }, + "config_profile_name": { + "type": "string", + "description": "Institutional config name.", + "hidden": true, + "fa_icon": "fas fa-users-cog" + }, + "config_profile_description": { + "type": "string", + "description": "Institutional config description.", + "hidden": true, + "fa_icon": "fas fa-users-cog" + }, + "config_profile_contact": { + "type": "string", + "description": "Institutional config contact information.", + "hidden": true, + "fa_icon": "fas fa-users-cog" + }, + "config_profile_url": { + "type": "string", + "description": "Institutional config URL link.", + "hidden": true, + "fa_icon": "fas fa-users-cog" + } + } }, - "outdir": { - "type": "string", - "format": "directory-path", - "description": "The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure.", - "fa_icon": "fas fa-folder-open" + "generic_options": { + "title": "Generic options", + "type": "object", + "fa_icon": "fas fa-file-import", + "description": "Less common options for the pipeline, typically set in a config file.", + "help_text": "These options are common to all nf-core pipelines and allow you to customise some of the core preferences for how the pipeline runs.\n\nTypically these options would be set in a Nextflow config file loaded for all pipeline runs, such as `~/.nextflow/config`.", + "properties": { + "version": { + "type": "boolean", + "description": "Display version and exit.", + "fa_icon": "fas fa-question-circle", + "hidden": true + }, + "publish_dir_mode": { + "type": "string", + "default": "copy", + "description": "Method used to save pipeline results to output directory.", + "help_text": "The Nextflow `publishDir` option specifies which intermediate files should be saved to the output directory. This option tells the pipeline what method should be used to move these files. See [Nextflow docs](https://www.nextflow.io/docs/latest/process.html#publishdir) for details.", + "fa_icon": "fas fa-copy", + "enum": [ + "symlink", + "rellink", + "link", + "copy", + "copyNoFollow", + "move" + ], + "hidden": true + }, + "monochrome_logs": { + "type": "boolean", + "description": "Do not use coloured log outputs.", + "fa_icon": "fas fa-palette", + "hidden": true + }, + "validate_params": { + "type": "boolean", + "description": "Boolean whether to validate parameters against the schema at runtime", + "default": true, + "fa_icon": "fas fa-check-square", + "hidden": true + }, + "pipelines_testdata_base_path": { + "type": "string", + "fa_icon": "far fa-check-circle", + "description": "Base URL or local path to location of pipeline test dataset files", + "default": "https://raw.githubusercontent.com/nf-core/test-datasets/", + "hidden": true + }, + "trace_report_suffix": { + "type": "string", + "fa_icon": "far calendar", + "description": "Suffix to add to the trace report filename. Default is the date and time in the format yyyy-MM-dd_HH-mm-ss.", + "hidden": true + }, + "help": { + "type": ["boolean", "string"], + "description": "Display the help message." + }, + "help_full": { + "type": "boolean", + "description": "Display the full detailed help message." + }, + "show_hidden": { + "type": "boolean", + "description": "Display hidden parameters in the help message (only works when --help or --help_full are provided)." + } + } } - } }, - "institutional_config_options": { - "title": "Institutional config options", - "type": "object", - "fa_icon": "fas fa-university", - "description": "Parameters used to describe centralised config profiles. These should not be edited.", - "help_text": "The centralised nf-core configuration profiles use a handful of pipeline parameters to describe themselves. This information is then printed to the Nextflow log when you run a pipeline. You should not need to change these values when you run a pipeline.", - "properties": { - "custom_config_version": { - "type": "string", - "description": "Git commit id for Institutional configs.", - "default": "master", - "hidden": true, - "fa_icon": "fas fa-users-cog" + "allOf": [ + { + "$ref": "#/$defs/input_output_options" }, - "custom_config_base": { - "type": "string", - "description": "Base directory for Institutional configs.", - "default": "https://raw.githubusercontent.com/nf-core/configs/master", - "hidden": true, - "help_text": "If you're running offline, Nextflow will not be able to fetch the institutional config files from the internet. If you don't need them, then this is not a problem. If you do need them, you should download the files from the repo and tell Nextflow where to find them with this parameter.", - "fa_icon": "fas fa-users-cog" + { + "$ref": "#/$defs/institutional_config_options" }, - "config_profile_name": { - "type": "string", - "description": "Institutional config name.", - "hidden": true, - "fa_icon": "fas fa-users-cog" - }, - "config_profile_description": { - "type": "string", - "description": "Institutional config description.", - "hidden": true, - "fa_icon": "fas fa-users-cog" - }, - "config_profile_contact": { - "type": "string", - "description": "Institutional config contact information.", - "hidden": true, - "fa_icon": "fas fa-users-cog" - }, - "config_profile_url": { - "type": "string", - "description": "Institutional config URL link.", - "hidden": true, - "fa_icon": "fas fa-users-cog" + { + "$ref": "#/$defs/generic_options" } - } - }, - "generic_options": { - "title": "Generic options", - "type": "object", - "fa_icon": "fas fa-file-import", - "description": "Less common options for the pipeline, typically set in a config file.", - "help_text": "These options are common to all nf-core pipelines and allow you to customise some of the core preferences for how the pipeline runs.\n\nTypically these options would be set in a Nextflow config file loaded for all pipeline runs, such as `~/.nextflow/config`.", - "properties": { - "version": { - "type": "boolean", - "description": "Display version and exit.", - "fa_icon": "fas fa-question-circle", - "hidden": true - }, - "publish_dir_mode": { - "type": "string", - "default": "copy", - "description": "Method used to save pipeline results to output directory.", - "help_text": "The Nextflow `publishDir` option specifies which intermediate files should be saved to the output directory. This option tells the pipeline what method should be used to move these files. See [Nextflow docs](https://www.nextflow.io/docs/latest/process.html#publishdir) for details.", - "fa_icon": "fas fa-copy", - "enum": [ - "symlink", - "rellink", - "link", - "copy", - "copyNoFollow", - "move" - ], - "hidden": true - }, - "monochrome_logs": { - "type": "boolean", - "description": "Do not use coloured log outputs.", - "fa_icon": "fas fa-palette", - "hidden": true - }, - "validate_params": { - "type": "boolean", - "description": "Boolean whether to validate parameters against the schema at runtime", - "default": true, - "fa_icon": "fas fa-check-square", - "hidden": true - }, - "pipelines_testdata_base_path": { - "type": "string", - "fa_icon": "far fa-check-circle", - "description": "Base URL or local path to location of pipeline test dataset files", - "default": "https://raw.githubusercontent.com/nf-core/test-datasets/", - "hidden": true - }, - "trace_report_suffix": { - "type": "string", - "fa_icon": "far calendar", - "description": "Suffix to add to the trace report filename. Default is the date and time in the format yyyy-MM-dd_HH-mm-ss.", - "hidden": true - }, - "help": { - "type": ["boolean", "string"], - "description": "Display the help message." - }, - "help_full": { - "type": "boolean", - "description": "Display the full detailed help message." - }, - "show_hidden": { - "type": "boolean", - "description": "Display hidden parameters in the help message (only works when --help or --help_full are provided)." - } - } - } - }, - "allOf": [ - { - "$ref": "#/$defs/input_output_options" - }, - { - "$ref": "#/$defs/institutional_config_options" - }, - { - "$ref": "#/$defs/generic_options" - } - ] + ] } From f3cdbea21b9b1feafdd0454135849b578c112442 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 31 Oct 2025 11:27:30 +0000 Subject: [PATCH 079/113] Fix linting errors in core-hello-part5 solution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove trailing whitespace from cowpy module files to pass editorconfig checks. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../core-hello-part5/modules/local/cowpy/main.nf | 8 ++++---- .../modules/local/cowpy/tests/main.nf.test | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/hello-nf-core/solutions/core-hello-part5/modules/local/cowpy/main.nf b/hello-nf-core/solutions/core-hello-part5/modules/local/cowpy/main.nf index 212821d599..1b30633471 100644 --- a/hello-nf-core/solutions/core-hello-part5/modules/local/cowpy/main.nf +++ b/hello-nf-core/solutions/core-hello-part5/modules/local/cowpy/main.nf @@ -21,9 +21,9 @@ process COWPY { script: def args = task.ext.args ?: '' def prefix = task.ext.prefix ?: "${meta.id}" - + """ - + cat <<-END_VERSIONS > versions.yml "${task.process}": @@ -34,10 +34,10 @@ process COWPY { stub: def args = task.ext.args ?: '' def prefix = task.ext.prefix ?: "${meta.id}" - + """ echo $args - + cat <<-END_VERSIONS > versions.yml "${task.process}": diff --git a/hello-nf-core/solutions/core-hello-part5/modules/local/cowpy/tests/main.nf.test b/hello-nf-core/solutions/core-hello-part5/modules/local/cowpy/tests/main.nf.test index 88f75f7b6a..e3fcf90a5d 100644 --- a/hello-nf-core/solutions/core-hello-part5/modules/local/cowpy/tests/main.nf.test +++ b/hello-nf-core/solutions/core-hello-part5/modules/local/cowpy/tests/main.nf.test @@ -22,7 +22,7 @@ nextflow_process { process { """ // TODO nf-core: define inputs of the process here. Example: - + input[0] = [ [ id:'test' ], file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), @@ -54,7 +54,7 @@ nextflow_process { process { """ // TODO nf-core: define inputs of the process here. Example: - + input[0] = [ [ id:'test' ], file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), From 16d2f3fe3aa88308e29397128e36a4fc2d98c5e8 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 31 Oct 2025 11:29:46 +0000 Subject: [PATCH 080/113] Fix remaining linting issues in core-hello-part5 solution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove trailing whitespace from snap files - Add newlines at end of snap files - Remove extra blank line from utils_nfschema_plugin/main.nf 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../modules/nf-core/cat/cat/tests/main.nf.test.snap | 4 ++-- .../utils_nextflow_pipeline/tests/main.function.nf.test.snap | 2 +- .../utils_nfcore_pipeline/tests/main.function.nf.test.snap | 2 +- .../utils_nfcore_pipeline/tests/main.workflow.nf.test.snap | 2 +- .../subworkflows/nf-core/utils_nfschema_plugin/main.nf | 1 - 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/tests/main.nf.test.snap b/hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/tests/main.nf.test.snap index b7623ee650..e2381ca20b 100644 --- a/hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/tests/main.nf.test.snap +++ b/hello-nf-core/solutions/core-hello-part5/modules/nf-core/cat/cat/tests/main.nf.test.snap @@ -93,7 +93,7 @@ "test_cat_name_conflict": { "content": [ [ - + ] ], "meta": { @@ -144,4 +144,4 @@ }, "timestamp": "2024-07-22T11:51:57.581523" } -} \ No newline at end of file +} diff --git a/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap index e3f0baf473..846287c417 100644 --- a/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap +++ b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap @@ -17,4 +17,4 @@ }, "timestamp": "2024-02-28T12:02:12.425833" } -} \ No newline at end of file +} diff --git a/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap index 02c6701413..b13b311213 100644 --- a/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap +++ b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap @@ -133,4 +133,4 @@ }, "timestamp": "2024-02-28T12:03:21.714424" } -} \ No newline at end of file +} diff --git a/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap index 859d1030fb..84ee1e1d1e 100644 --- a/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap +++ b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap @@ -16,4 +16,4 @@ }, "timestamp": "2024-02-28T12:03:25.726491" } -} \ No newline at end of file +} diff --git a/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfschema_plugin/main.nf b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfschema_plugin/main.nf index ee4738c8d1..acb3972419 100644 --- a/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfschema_plugin/main.nf +++ b/hello-nf-core/solutions/core-hello-part5/subworkflows/nf-core/utils_nfschema_plugin/main.nf @@ -71,4 +71,3 @@ workflow UTILS_NFSCHEMA_PLUGIN { emit: dummy_emit = true } - From 43d222dbfc9af9617f6f895f19f10fa811de8435 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 31 Oct 2025 12:21:47 +0000 Subject: [PATCH 081/113] Strip nf-core side quest in favour of 'hello nf-core' --- docs/side_quests/index.md | 1 - docs/side_quests/nf-core.md | 1441 ----------------- mkdocs.yml | 1 - .../nf-core/data/sequencer_samplesheet.csv | 5 - 4 files changed, 1448 deletions(-) delete mode 100644 docs/side_quests/nf-core.md delete mode 100644 side-quests/nf-core/data/sequencer_samplesheet.csv diff --git a/docs/side_quests/index.md b/docs/side_quests/index.md index 2eb905de89..60e52180d2 100644 --- a/docs/side_quests/index.md +++ b/docs/side_quests/index.md @@ -32,7 +32,6 @@ Otherwise, select a side quest from the table below. | -------------------------------------------------------------------------- | -------------------------- | | [Nextflow development environment walkthrough](./ide_features.md) | 45 mins | | [Essential Nextflow Scripting Patterns](./essential_scripting_patterns.md) | 90 mins | -| [Introduction to nf-core](./nf-core.md) | - | | [Metadata in workflows](./metadata.md) | 45 mins | | [Splitting and Grouping](./splitting_and_grouping.md) | 45 mins | | [Testing with nf-test](./nf-test.md) | 1 hour | diff --git a/docs/side_quests/nf-core.md b/docs/side_quests/nf-core.md deleted file mode 100644 index 6dceda9361..0000000000 --- a/docs/side_quests/nf-core.md +++ /dev/null @@ -1,1441 +0,0 @@ -# Introduction to nf-core - -nf-core is a community effort to develop and maintain a curated set of analysis pipelines built using Nextflow. It was created by several core facilities wanting to consolidate their analysis development and is governed by community members from academia and industry. It is an open community that anyone can join and contribute to. - -![nf-core logo](./img/nf-core/nf-core-logo.png) - -nf-core provides a standardized set of best practices, guidelines, and templates for building and sharing scientific pipelines. -These pipelines are designed to be modular, scalable, and portable, allowing researchers to easily adapt and execute them using their own data and compute resources. - -One of the key benefits of nf-core is that it promotes open development, testing, and peer review, ensuring that the pipelines are robust, well-documented, and validated against real-world datasets. -This helps to increase the reliability and reproducibility of scientific analyses and ultimately enables researchers to accelerate their scientific discoveries. - -nf-core is published in Nature Biotechnology: [Nat Biotechnol 38, 276–278 (2020). Nature Biotechnology](https://www.nature.com/articles/s41587-020-0439-x). An updated preprint is available at [bioRxiv](https://www.biorxiv.org/content/10.1101/2024.05.10.592912v1). - -In this tutorial you will explore using and writing nf-core pipelines: - -- Section 1: Run nf-core pipeline - In the first section, you will learn where you can find information about a particular nf-core pipeline and how to run one with provided test data. -- Section 2: Develop an nf-core-like pipeline - In second section, you will use a simplified version of the nf-core template to write a nf-core-style pipeline. The pipeline consists of two modules to process FastQ data: `fastqe` and `seqtk`. It uses an input from a sample sheet, validates it, and produces a multiqc report. - ---- - -## 0. Warmup - -Let's move into the project directory. - -```bash -cd side-quests/nf-core -``` - -The `nf-core` directory has the file content like: - -```console title="Directory contents" -nf-core -└── data - └── sequencer_samplesheet.csv -``` - -We will first run a pipeline in this directory and then build our own. We need the `sequencer_samplesheet.csv` for part 2. For now you can ignore it. - -## 1. Run nf-core pipelines - -nf-core uses their website [nf-co.re](https://nf-co.re) to centrally display all information such as: general documentation and help articles, documentation for each of its pipelines, blog posts, event annoucenments, etc.. - -### 1.1 nf-core website - -Each released pipeline has a dedicated page that includes 6 documentation sections: - -- **Introduction:** An introduction and overview of the pipeline -- **Usage:** Descriptions of how to execute the pipeline -- **Parameters:** Grouped pipeline parameters with descriptions -- **Output:** Descriptions and examples of the expected output files -- **Results:** Example output files generated from the full test dataset -- **Releases & Statistics:** Pipeline version history and statistics - -You should read the pipeline documentation carefully to understand what a given pipeline does and how it can be configured before attempting to run it. - -Go to the nf-core website and find the documentation for the [nf-core/demo pipeline](https://nf-co.re/demo/). - -Find out: - -- which tools the pipeline will run (Check the tab: `Introduction`) -- which parameters the pipeline has (Check the tab: `Parameters`) -- what the output files (Check the tab: `Output`) - -#### Takeaway - -You know where to find information about a particular nf-core pipeline: where to find general information, where the parameters are described, and where you can find a description on the output that the pipelines produce. - -#### What's next? - -Next, we'll show you how to run your first nf-core pipeline. - -### 1.2 Running an nf-core pipeline - -Let's start by creating a new subdirectory to run the pipeline in: - -```bash -mkdir nf-core-demo -cd nf-core-demo -``` - -!!!tip - - You can run this from anywhere, but by creating a new folder all logs and output files that will be generated are bundled in one place. - -Whenever you're ready, run the command: - -```bash -nextflow pull nf-core/demo -``` - -Nextflow will `pull` the pipeline code. - -```console title="Output" -Checking nf-core/demo ... - downloaded from https://github.com/nf-core/demo.git - revision: 04060b4644 [master] -``` - -To be clear, you can do this with any Nextflow pipeline that is appropriately set up in GitHub, not just nf-core pipelines. -However nf-core is the largest open curated collection of Nextflow pipelines. - -Now that we've got the pipeline pulled, we can try running it! - -#### 1.2.1 Trying out an nf-core pipeline with the test profile - -Conveniently, every nf-core pipeline comes with a `test` profile. -This is a minimal set of configuration settings for the pipeline to run using a small test dataset that is hosted on the [nf-core/test-datasets](https://github.com/nf-core/test-datasets) repository. It's a great way to try out a pipeline at small scale. - -The `test` profile for `nf-core/demo` is shown below: - -```groovy title="conf/test.config" linenums="1" hl_lines="26" -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Nextflow config file for running minimal tests -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Defines input files and everything required to run a fast and simple pipeline test. - - Use as follows: - nextflow run nf-core/demo -profile test, --outdir - ----------------------------------------------------------------------------------------- -*/ - -process { - resourceLimits = [ - cpus: 4, - memory: '15.GB', - time: '1.h' - ] -} - -params { - config_profile_name = 'Test profile' - config_profile_description = 'Minimal test dataset to check pipeline function' - - // Input data - input = 'https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/samplesheet/samplesheet_test_illumina_amplicon.csv' - -} -``` - -This tells us that the `nf-core/demo` `test` profile already specifies the input parameter, so you don't have to provide any input yourself. -However, the `outdir` parameter is not included in the `test` profile, so you have to add it to the execution command using the `--outdir` flag. - -Here, we're also going to specify `-profile docker`, which by nf-core convention enables the use of Docker. - -Lets' try it! - -```bash -nextflow run nf-core/demo -profile docker,test --outdir results -``` - -Here's the console output from the pipeline: - -```console title="Output" - N E X T F L O W ~ version 24.10.0 - -Launching `https://github.com/nf-core/demo` [maniac_jones] DSL2 - revision: 04060b4644 [master] - - ------------------------------------------------------- - ,--./,-. - ___ __ __ __ ___ /,-._.--~' - |\ | |__ __ / ` / \ |__) |__ } { - | \| | \__, \__/ | \ |___ \`-._,-`-, - `._,._,' - nf-core/demo 1.0.1 ------------------------------------------------------- -Input/output options - input : https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/samplesheet/samplesheet_test_illumina_amplicon.csv - outdir : results - -Institutional config options - config_profile_name : Test profile - config_profile_description: Minimal test dataset to check pipeline function - -Core Nextflow options - revision : master - runName : maniac_jones - containerEngine : docker - launchDir : /workspaces/training/side-quests/nf-core/nf-core-demo - workDir : /workspaces/training/side-quests/nf-core/nf-core-demo/work - projectDir : /workspaces/.nextflow/assets/nf-core/demo - userName : gitpod - profile : docker,test - configFiles : - -!! Only displaying parameters that differ from the pipeline defaults !! -------------------------------------------------------* The pipeline - https://doi.org/10.5281/zenodo.12192442 - -* The nf-core framework - https://doi.org/10.1038/s41587-020-0439-x - -* Software dependencies - https://github.com/nf-core/demo/blob/master/CITATIONS.md - -executor > local (7) -[3c/a00024] NFC…_DEMO:DEMO:FASTQC (SAMPLE2_PE) | 3 of 3 ✔ -[94/d1d602] NFC…O:DEMO:SEQTK_TRIM (SAMPLE2_PE) | 3 of 3 ✔ -[ab/460670] NFCORE_DEMO:DEMO:MULTIQC | 1 of 1 ✔ --[nf-core/demo] Pipeline completed successfully- -Completed at: 05-Mar-2025 09:46:21 -Duration : 1m 54s -CPU hours : (a few seconds) -Succeeded : 7 -``` - -Isn't that neat? - -You can also explore the `results` directory produced by the pipeline. - -```console title="Output" -results/ -├── fastqc -│ ├── SAMPLE1_PE -│ ├── SAMPLE2_PE -│ └── SAMPLE3_SE -├── fq -│ ├── SAMPLE1_PE -│ ├── SAMPLE2_PE -│ └── SAMPLE3_SE -├── multiqc -│ ├── multiqc_data -│ ├── multiqc_plots -│ └── multiqc_report.html -└── pipeline_info - ├── execution_report_2025-03-05_09-44-26.html - ├── execution_timeline_2025-03-05_09-44-26.html - ├── execution_trace_2025-03-05_09-44-26.txt - ├── nf_core_pipeline_software_mqc_versions.yml - ├── params_2025-03-05_09-44-29.json - └── pipeline_dag_2025-03-05_09-44-26.html -``` - -If you're curious about what that all means, check out [the nf-core/demo pipeline documentation page](https://nf-co.re/demo/1.0.1/)! - -And that's all you need to know for now. -Congratulations! You have now run your first nf-core pipeline. - -#### Takeaway - -You know how to run an nf-core pipeline using its built-in test profile. - -#### What's next? - -Celebrate and take a break! Next, we'll show you how to use nf-core tooling to build your own pipeline. - -## 2. Create a basic pipeline from template - -We will now start developing our own nf-core style pipeline. -The nf-core collection currently offers, [72 subworkflows](https://nf-co.re/subworkflows/) and [over 1300 modules](https://nf-co.re/modules/) that you can use to build your own pipelines. Subworkflows are 'composable' workflows, such as those you may have encountered in the [Workflows of workflows side quest](./workflows_of_workflows.md), providing ready-made chunks of logic you can you can use in your own worklfows. - -The nf-core community provides a [command line tool](https://nf-co.re/docs/nf-core-tools) with helper functions to use and develop pipelines, including to install those components. - -We have pre-installed nf-core tools, and here, we will use them to create and develop a new pipeline. - -View all of the tooling using the `nf-core --help` argument. - -```bash -nf-core --help -``` - -### 2.1 Creating your pipeline - -Before we start, let's create a new subfolder in the current `nf-core` directory: - -``` -cd .. -mkdir nf-core-pipeline -cd nf-core-pipeline -``` - -!!! hint "Open a new window in VSCode" - - If you are working with VS Code you can open a new window to reduce visual clutter: - - ```bash - code . - ``` - -Let's start by creating a new pipeline with the `nf-core pipelines create` command: - -All nf-core pipelines are based on a common template, a standardized pipeline skeleton that can be used to streamline development with shared features and components. - -The `nf-core pipelines create` command creates a new pipeline using the nf-core base template with a pipeline name, description, and author. It is the first and most important step for creating a pipeline that will integrate with the wider Nextflow ecosystem. - -```bash -nf-core pipelines create -``` - -Running this command will open a Text User Interface (TUI) for pipeline creation. - -
- -
- -Template features can be flexibly included or excluded at the time of creation, follow these steps create your first pipeline using the `nf-core pipelines create` TUI: - -1. Run the `nf-core pipelines create` command -2. Select **Let's go!** on the welcome screen -3. Select **Custom** on the Choose pipeline type screen -4. Enter your pipeline details, replacing < YOUR NAME > with your own name, then select **Next** - -- **GitHub organisation:** myorg -- **Workflow name:** myfirstpipeline -- **A short description of your pipeline:** My first pipeline -- **Name of the main author / authors:** < YOUR NAME > - -5. On the Template features screen, set "Toggle all features" to **off**, then **enable**: - -- `Add configuration files` -- `Use multiqc` -- `Use nf-core components` -- `Use nf-schema` -- `Add documentation` -- `Add testing profiles` - -6. Select **Finish** on the Final details screen -7. Wait for the pipeline to be created, then select **Continue** -8. Select **Finish without creating a repo** on the Create GitHub repository screen -9. Select **Close** on the HowTo create a GitHub repository page - -If run successfully, you will see a new folder in your current directory named `myorg-myfirstpipeline`. - -#### 2.1.1 Testing your pipeline - -Let's try to run our new pipeline: - -```bash -cd myorg-myfirstpipeline -nextflow run . -profile docker,test --outdir results -``` - -The pipeline should run successfully! - -Here's the console output from the pipeline: - -```console title="Output" - N E X T F L O W ~ version 24.10.0 - -Launching `./main.nf` [infallible_kilby] DSL2 - revision: fee0bcf390 - -Downloading plugin nf-schema@2.3.0 -Input/output options - input : https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/samplesheet/samplesheet_test_illumina_amplicon.csv - outdir : results - -Institutional config options - config_profile_name : Test profile - config_profile_description: Minimal test dataset to check pipeline function - -Generic options - trace_report_suffix : 2025-03-05_10-17-59 - -Core Nextflow options - runName : infallible_kilby - containerEngine : docker - launchDir : /workspaces/training/side-quests/nf-core/nf-core-pipeline/myorg-myfirstpipeline - workDir : /workspaces/training/side-quests/nf-core/nf-core-pipeline/myorg-myfirstpipeline/work - projectDir : /workspaces/training/side-quests/nf-core/nf-core-pipeline/myorg-myfirstpipeline - userName : gitpod - profile : docker,test - configFiles : /workspaces/training/side-quests/nf-core/nf-core-pipeline/myorg-myfirstpipeline/nextflow.config - -!! Only displaying parameters that differ from the pipeline defaults !! ------------------------------------------------------- -executor > local (1) -[02/510003] MYO…PELINE:MYFIRSTPIPELINE:MULTIQC | 1 of 1 ✔ --[myorg/myfirstpipeline] Pipeline completed successfully- -``` - -Let's dissect what we are seeing. - -The nf-core pipeline template is a working pipeline and comes preconfigured with some modules. Here, we only run [MultiQC](https://multiqc.info/) - -At the top, you see all parameters displayed that differ from the pipeline defaults. Most of these are default or were set by applying the `test` profile. - -Additionally we used the `docker` profile to use docker for software packaging. nf-core provides this as a profile for convenience to enable the docker feature but we could do it with configuration as we did with the earlier module. - -#### 2.1.2 Template tour - -The nf-core pipeline template comes packed with a lot of files and folders. While creating the pipeline, we selected a subset of the nf-core features. The features we selected are now included as files and directories in our repository. - -While the template may feel overwhelming, a complete understanding isn't required to start developing your pipeline. Let's look at the important places that we need to touch during pipeline development. - -##### Workflows, subworkflows, and modules - -The nf-core pipeline template has a `main.nf` script that calls `myfirstpipeline.nf` from the `workflows` folder. The `myfirstpipeline.nf` file inside the workflows folder is the central pipeline file that is used to bring everything else together. - -Instead of having one large monolithic pipeline script, it's broken up into smaller script components, namely, modules and subworkflows: - -- **Modules:** Wrappers around a single process -- **Subworkflows:** Two or more modules that are packaged together as a mini workflow - -
- --8<-- "docs/side_quests/img/nf-core/nested.excalidraw.svg" -
- -Within your pipeline repository, `modules` and `subworkflows` are stored within `local` and `nf-core` folders. The `nf-core` folder is for components that have come from the nf-core GitHub repository while the `local` folder is for components that have been developed independently (usually things very specific to a pipeline): - -```console -modules/ -├── local -│ └── .nf -│ . -│ -└── nf-core - ├── - │ ├── environment.yml - │ ├── main.nf - │ ├── meta.yml - │ └── tests - │ ├── main.nf.test - │ ├── main.nf.test.snap - │ └── tags.yml - . -``` - -Modules from nf-core follow a similar structure and contain a small number of additional files for testing using [nf-test](https://www.nf-test.com/) and documentation about the module. - -!!!note - - Some nf-core modules are also split into command specific directories: - - ```console - │ - └── - └── - ├── environment.yml - ├── main.nf - ├── meta.yml - └── tests - ├── main.nf.test - ├── main.nf.test.snap - ├── nextflow.config - └── tags.yml - ``` - -!!!note - - The nf-core template does not come with a local modules folder by default. - -##### Configuration files - -The nf-core pipeline template utilizes Nextflow's flexible customization options and has a series of configuration files throughout the template. - -In the template, the `nextflow.config` file is a central configuration file and is used to set default values for parameters and other configuration options. The majority of these configuration options are applied by default while others (e.g., software dependency profiles) are included as optional profiles. - -There are several configuration files that are stored in the `conf` folder and are added to the configuration by default or optionally as profiles: - -- `base.config`: A 'blank slate' config file, appropriate for general use on most high-performance computing environments. This defines broad bins of resource usage, for example, which are convenient to apply to modules. -- `modules.config`: Additional module directives and arguments. -- `test.config`: A profile to run the pipeline with minimal test data. -- `test_full.config`: A profile to run the pipeline with a full-sized test dataset. - -##### `nextflow_schema.json` - -The `nextflow_schema.json` is a file used to store parameter related information including type, description and help text in a machine readable format. The schema is used for various purposes, including automated parameter validation, help text generation, and interactive parameter form rendering in UI interfaces. - -##### `assets/schema_input.json` - -The `schema_input.json` is a file used to define the input samplesheet structure. Each column can have a type, pattern, description and help text in a machine readable format. The schema is used for various purposes, including automated validation, and providing helpful error messages. - -#### Takeaway - -You used the nf-core tooling to create a template pipeline. You customized it with components you wanted to use for this pipeline focusing on a handful important ones. You also learned about each of the pieces you have installed and have a general idea of the locations of important files. Lastly, you checked that the template pipeline works by running it as is. - -#### What's next? - -Congratulations and take a break! In the next step, we will investigate the default input data, that the pipeline comes with. - ---- - -### 2.2 Check the input data - -Above, we said that the `test` profile comes with small test files that are stored in the nf-core. Let's check what type of files we are dealing with to plan our expansion. Remember that we can inspect any channel content using the `view` operator: - -```groovy title="workflows/myfirstpipeline.nf" linenums="27" -ch_samplesheet.view() -``` - -!!!note - - nf-core is making heavy use of more complex workflow encapsulation. The `main.nf` that you used in the hello-series imports and calls the workflow in the file `workflows/myfirstpipeline.nf`. This is the file we will work in today. - -and the run command: - -```bash -nextflow run . -profile docker,test --outdir results -``` - -The output should look like the below. We see that we have FASTQ files as input and each set of files is accompanied by some metadata: the `id` and whether or not they are single end: - -```console title="Output" -[['id':'SAMPLE1_PE', 'single_end':false], [/nf-core/test-datasets/viralrecon/illumina/amplicon/sample1_R1.fastq.gz, /nf-core/test-datasets/viralrecon/illumina/amplicon/sample1_R2.fastq.gz]] -[['id':'SAMPLE2_PE', 'single_end':false], [/nf-core/test-datasets/viralrecon/illumina/amplicon/sample2_R1.fastq.gz, /nf-core/test-datasets/viralrecon/illumina/amplicon/sample2_R2.fastq.gz]] -[['id':'SAMPLE3_SE', 'single_end':true], [/nf-core/test-datasets/viralrecon/illumina/amplicon/sample1_R1.fastq.gz, /nf-core/test-datasets/viralrecon/illumina/amplicon/sample2_R1.fastq.gz]] -``` - -You can comment the `view` statement for now. We will use it later during this training to inspect the channel content again. - -#### Takeaway - -The pipeline template comes with a default samplesheet. You learned what is part of this samplesheet so you can use it in the next steps when we want to add and run modules in the pipeline. - -#### What's next? - -In the next step you will start adding your first nf-core module to the pipeline: seqtk. - ---- - -### 2.3 Add an nf-core module - -nf-core provides a large library of modules and subworkflows: pre-made nextflow wrappers around tools that can be installed into nextflow pipelines. They are designed to be flexible but may require additional configuration to suit different use cases. - -Currently, there are more than [1400 nf-core modules](https://nf-co.re/modules) and [70 nf-core subworkflows](https://nf-co.re/subworkflows) (March 2025) available. Modules and subworkflows can be listed, installed, updated, removed, and patched using nf-core tooling. - -While you could develop a module for this tool independently, you can save a lot of time and effort by leveraging nf-core modules and subworkflows. - -Let's see which modules are available: - -```console -nf-core modules list remote -``` - -This command lists all currently available modules, > 1400. An easier way to find them is to go to the nf-core website and visit the modules subpage [https://nf-co.re/modules](https://nf-co.re/modules). Here you can search for modules by name or tags, find documentation for each module, and see which nf-core pipeline are using the module: - -![nf-core/modules](./img/nf-core/nf-core-modules.png) - -#### 2.3.1 Install an nf-core module - -Now let's add another tool to the pipeline. - -`Seqtk` is a fast and lightweight tool for processing sequences in the FASTA or FASTQ format. Here, you will use the [`seqtk trim`](https://github.com/lh3/seqtk) command to trim FASTQ files. - -In your pipeline, you will add a new step that will take FASTQ files from the sample sheet as inputs and will produce trimmed fastq files that can be used as an input for other tools and version information about the seqtk tools to mix into the inputs for the MultiQC process. - -
- --8<-- "docs/side_quests/img/nf-core/pipeline.excalidraw.svg" -
- -The `nf-core modules install` command can be used to install the `seqtk/trim` module directly from the nf-core repository: - -``` -nf-core modules install -``` - -!!!warning - - You need to be in the myorg-myfirstpipeline directory when executing `nf-core modules install` - -You can follow the prompts to find and install the module you are interested in: - -```console -? Tool name: seqtk/trim -``` - -Once selected, the tooling will install the module in the `modules/nf-core/` folder and suggest code that you can add to your main workflow file (`workflows/myfirstpipeline.nf`). - -```console -INFO Installing 'seqtk/trim' -INFO Use the following statement to include this module: - -include { SEQTK_TRIM } from '../modules/nf-core/seqtk/trim/main' -``` - -To enable reporting and reproducibility, modules and subworkflows from the nf-core repository are tracked using hashes in the `modules.json` file. When modules are installed or removed using the nf-core tooling the `modules.json` file will be automatically updated. - -When you open the `modules.json`, you will see an entry for each module that is currently installed from the nf-core modules repository. You can open the file with the VS Code user interface by clicking on it in `myorg-myfirstpipeline/modules.json`: - -```console -"nf-core": { - "multiqc": { - "branch": "master", - "git_sha": "cf17ca47590cc578dfb47db1c2a44ef86f89976d", - "installed_by": ["modules"] - }, - "seqtk/trim": { - "branch": "master", - "git_sha": "666652151335353eef2fcd58880bcef5bc2928e1", - "installed_by": ["modules"] - } -} -``` - -#### 2.3.2 Add the module to your pipeline - -Although the module has been installed in your local pipeline repository, it is not yet added to your pipeline. - -The suggested `include` statement needs to be added to your `workflows/myfirstpipeline.nf` file and the process call (with inputs) needs to be added to the workflow block. - -```groovy title="workflows/myfirstpipeline.nf" linenums="6" -include { SEQTK_TRIM } from '../modules/nf-core/seqtk/trim/main' -include { MULTIQC } from '../modules/nf-core/multiqc/main' -``` - -To add the `SEQTK_TRIM` module to your workflow you will need to check what inputs are required. - -You can view the input channels for the module by opening the `./modules/nf-core/seqtk/trim/main.nf` file. - -```groovy title="modules/nf-core/seqtk/trim/main.nf" linenums="11" -input: -tuple val(meta), path(reads) -``` - -Each nf-core module also has a `meta.yml` file which describes the inputs and outputs. This meta file is rendered on the [nf-core website](https://nf-co.re/modules/seqtk_trim), or can be viewed using the `nf-core modules info` command: - -```console -nf-core modules info seqtk/trim -``` - -It outputs a table with all defined inputs and outputs of the module: - -```console title="Output" - -╭─ Module: seqtk/trim ─────────────────────────────────────────────────────────────────────────────╮ -│ Location: modules/nf-core/seqtk/trim │ -│ 🔧 Tools: seqtk │ -│ 📖 Description: Trim low quality bases from FastQ files │ -╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ - ╷ ╷ - 📥 Inputs │Description │ Pattern -╺━━━━━━━━━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┿━━━━━━━━━━━━╸ - input[0] │ │ -╶──────────────┼───────────────────────────────────────────────────────────────────────┼────────────╴ - meta (map) │Groovy Map containing sample information e.g. [ id:'test', │ - │single_end:false ] │ -╶──────────────┼───────────────────────────────────────────────────────────────────────┼────────────╴ - reads (file)│List of input FastQ files │*.{fastq.gz} - ╵ ╵ - ╷ ╷ - 📥 Outputs │Description │ Pattern -╺━━━━━━━━━━━━━━━━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┿━━━━━━━━━━━━╸ - reads │ │ -╶─────────────────────┼────────────────────────────────────────────────────────────────┼────────────╴ - meta (map) │Groovy Map containing sample information e.g. [ id:'test', │ - │single_end:false ] │ -╶─────────────────────┼────────────────────────────────────────────────────────────────┼────────────╴ - *.fastq.gz (file) │Filtered FastQ files │*.{fastq.gz} -╶─────────────────────┼────────────────────────────────────────────────────────────────┼────────────╴ - versions │ │ -╶─────────────────────┼────────────────────────────────────────────────────────────────┼────────────╴ - versions.yml (file)│File containing software versions │versions.yml - ╵ ╵ - - Use the following statement to include this module: - - include { SEQTK_TRIM } from '../modules/nf-core/seqtk/trim/main' -``` - -Using this module information you can work out what inputs are required for the `SEQTK_TRIM` process: - -1. `tuple val(meta), path(reads)` - - - A tuple (basically a fixed-length list) with a meta _map_ (we will talk about meta maps more in the next section) and a list of FASTQ _files_ - - The channel `ch_samplesheet` used by the `FASTQC` process can be used as the reads input. - -Only one input channel is required, and it already exists, so it can be added to your `firstpipeline.nf` file without any additional channel creation or modifications. - -_Before:_ - -```groovy title="workflows/myfirstpipeline.nf" linenums="30" -// -// Collate and save software versions -// -``` - -_After:_ - -```groovy title="workflows/myfirstpipeline.nf" linenums="29" -// -// MODULE: Run SEQTK_TRIM -// -SEQTK_TRIM ( - ch_samplesheet -) -// -// Collate and save software versions -// -``` - -Let's test it: - -```bash -nextflow run . -profile docker,test --outdir results -``` - -```console title="Output" - N E X T F L O W ~ version 24.10.0 - -Launching `./main.nf` [admiring_davinci] DSL2 - revision: fee0bcf390 - -Input/output options - input : https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/samplesheet/samplesheet_test_illumina_amplicon.csv - outdir : results - -Institutional config options - config_profile_name : Test profile - config_profile_description: Minimal test dataset to check pipeline function - -Generic options - trace_report_suffix : 2025-03-05_10-40-35 - -Core Nextflow options - runName : admiring_davinci - containerEngine : docker - launchDir : /workspaces/training/side-quests/nf-core/nf-core-pipeline/myorg-myfirstpipeline - workDir : /workspaces/training/side-quests/nf-core/nf-core-pipeline/myorg-myfirstpipeline/work - projectDir : /workspaces/training/side-quests/nf-core/nf-core-pipeline/myorg-myfirstpipeline - userName : gitpod - profile : docker,test - configFiles : /workspaces/training/side-quests/nf-core/nf-core-pipeline/myorg-myfirstpipeline/nextflow.config - -!! Only displaying parameters that differ from the pipeline defaults !! ------------------------------------------------------- -executor > local (4) -[a8/d4ccea] MYO…PELINE:SEQTK_TRIM (SAMPLE1_PE) | 3 of 3 ✔ -[fb/d907c3] MYO…PELINE:MYFIRSTPIPELINE:MULTIQC | 1 of 1 ✔ --[myorg/myfirstpipeline] Pipeline completed successfully- -``` - -#### 2.3.4 Inspect results folder - -Default nf-core configuration directs the output of each process into the `/`. After running the previous command, you -should have a `results` folder that looks something like this: - -```console -results/ -├── multiqc -│ ├── multiqc_data -│ └── multiqc_report.html -├── pipeline_info -│ ├── execution_report_2025-03-05_10-17-59.html -│ ├── execution_report_2025-03-05_10-28-16.html -│ ├── execution_report_2025-03-05_10-40-35.html -│ ├── execution_timeline_2025-03-05_10-17-59.html -│ ├── execution_timeline_2025-03-05_10-28-16.html -│ ├── execution_timeline_2025-03-05_10-40-35.html -│ ├── execution_trace_2025-03-05_10-17-59.txt -│ ├── execution_trace_2025-03-05_10-28-16.txt -│ ├── execution_trace_2025-03-05_10-40-35.txt -│ ├── myfirstpipeline_software_mqc_versions.yml -│ ├── params_2025-03-05_10-18-03.json -│ ├── params_2025-03-05_10-28-19.json -│ ├── params_2025-03-05_10-40-37.json -│ ├── pipeline_dag_2025-03-05_10-17-59.html -│ ├── pipeline_dag_2025-03-05_10-28-16.html -│ └── pipeline_dag_2025-03-05_10-40-35.html -└── seqtk - ├── SAMPLE1_PE_sample1_R1.fastq.gz - ├── SAMPLE1_PE_sample1_R2.fastq.gz - ├── SAMPLE2_PE_sample2_R1.fastq.gz - ├── SAMPLE2_PE_sample2_R2.fastq.gz - ├── SAMPLE3_SE_sample1_R1.fastq.gz - └── SAMPLE3_SE_sample2_R1.fastq.gz -``` - -The outputs from the `multiqc` and `seqtk` modules are published in their respective subdirectories. In addition, by default, nf-core pipelines generate a set of reports. These files are stored in the`pipeline_info` subdirectory and time-stamped so that runs don't overwrite each other. - -#### 2.3.5 Handle modules output - -As with the inputs, you can view the outputs for the module by opening the `/modules/nf-core/seqtk/trim/main.nf` file, use the `nf-core modules info seqtk/trim`, or check the `meta.yml`. - -```groovy title="modules/nf-core/seqtk/trim/main.nf" linenums="13" -output: -tuple val(meta), path("*.fastq.gz"), emit: reads -path "versions.yml" , emit: versions -``` - -To help with organization and readability it is beneficial to create named output channels. - -For `SEQTK_TRIM`, the `reads` output could be put into a channel named `ch_trimmed`. - -```groovy title="workflows/myfirstpipeline.nf" linenums="32" -ch_trimmed = SEQTK_TRIM.out.reads -``` - -All nf-core modules have a common output channel: `versions`. The channel contains a file that lists the tool version used in the module. MultiQC can collect all tool versions and print them out in a table in the results folder. This is useful to later track which version was actually run. - -It is beneficial to immediately mix the tool versions into the `ch_versions` channel so they can be used as input for the `MULTIQC` process and passed to the final report. - -```groovy title="workflows/myfirstpipeline.nf" linenums="33" -ch_versions = ch_versions.mix(SEQTK_TRIM.out.versions.first()) -``` - -!!! note - - The `first` operator is used to emit the first item from `SEQTK_TRIM.out.versions` to avoid duplication. - -#### 2.3.6 Add a parameter to the `seqtk/trim` tool - -nf-core modules should be flexible and usable across many different pipelines. Therefore, optional tool parameters are typically not set in an nf-core/module. Instead, additional configuration options on how to run the tool, like its parameters or filename, can be applied to a module using the `conf/modules.config` file on the pipeline level. Process selectors (e.g., `withName`) are used to apply configuration options to modules selectively. Process selectors must be used within the `process` scope. - -The parameters or arguments of a tool can be changed using the directive `args`. You can find many examples of how arguments are added to modules in nf-core pipelines, for example, the nf-core/demo [modules.config](https://github.com/nf-core/demo/blob/master/conf/modules.config) file. - -Add this snippet to your `conf/modules.config` file (using the `process` scope) to call the `seqtk/trim` tool with the argument `-b 5` to trim 5 bp from the left end of each read: - -```console title="conf/modules.config" linenums="21" -withName: 'SEQTK_TRIM' { - ext.args = "-b 5" -} -``` - -Run the pipeline again and check if the new parameter is applied: - -```bash -nextflow run . -profile docker,test --outdir results -``` - -```console title="Output" -[67/cc3d2f] process > MYORG_MYFIRSTPIPELINE:MYFIRSTPIPELINE:SEQTK_TRIM (SAMPLE1_PE) [100%] 3 of 3 ✔ -[b4/a1b41b] process > MYORG_MYFIRSTPIPELINE:MYFIRSTPIPELINE:MULTIQC [100%] 1 of 1 ✔ -``` - -Copy the hash you see in your console output (here `6c/34e549`; it is different for _each_ run). You can `ls` using tab-completion in your `work` directory to expand the complete hash. -In this folder you will find various log files. The `.command.sh` file contains the resolved command: - -```bash -less work/6c/34e549912696b6757f551603d135bb/.command.sh -``` - -We can see, that the parameter `-b 5`, that we set in the `modules.config` is applied to the task: - -```console title="Output" -#!/usr/bin/env bash -C -e -u -o pipefail -printf "%s\n" sample2_R1.fastq.gz sample2_R2.fastq.gz | while read f; -do - seqtk \ - trimfq \ - -b 5 \ - $f \ - | gzip --no-name > SAMPLE2_PE_$(basename $f) -done - -cat <<-END_VERSIONS > versions.yml -"MYORG_MYFIRSTPIPELINE:MYFIRSTPIPELINE:SEQTK_TRIM": - seqtk: $(echo $(seqtk 2>&1) | sed 's/^.*Version: //; s/ .*$//') -END_VERSIONS -``` - -#### Takeaway - -You changed the pipeline template and added the nf-core/module `seqtk` to your pipeline. You then changed the default tool command by editing the `modules.config` for this tool. You also made the output available in the workflow so it can be used by other modules in the pipeline. - -#### What's next? - -In the next step we will add a pipeline parameter to allow users to skip the trimming step run by `seqtk`. - ---- - -### 2.4 Adding parameters to your pipeline - -Any option that a pipeline user may want to configure regularly, whether in the specific modules used or the options passed to them, should be made into a pipeline-level parameter so it can easily be overridden. nf-core defines some standards for providing parameters. - -Here, as a simple example, you will add a new parameter to your pipeline that will skip the `SEQTK_TRIM` process. -That parameter will be accessible in the pipeline script, and we can use it to control how the pipeline runs. - -#### 2.4.1 Default values - -In the nf-core template the default values for parameters are set in the `nextflow.config` in the base repository. - -Any new parameters should be added to the `nextflow.config` with a default value within the `params` scope. - -Parameter names should be unique and easily identifiable. - -We can add a new parameter `skip_trim` to your `nextflow.config` file and set it to `false`. - -```groovy title="nextflow.config" linenums="15" -// Trimming -skip_trim = false -``` - -#### 2.4.2 Using the parameter - -Let's add an `if` statement that is depended on the `skip_trim` parameter to control the execution of the `SEQTK_TRIM` process: - -```groovy title="workflows/myfirstpipeline.nf" linenums="29" - // - // MODULE: Run SEQTK_TRIM - // - if (!params.skip_trim) { - SEQTK_TRIM ( - ch_samplesheet - ) - ch_trimmed = SEQTK_TRIM.out.reads - ch_versions = ch_versions.mix(SEQTK_TRIM.out.versions.first()) - } -``` - -Here, an `if` statement that is depended on the `skip_trim` parameter is used to control the execution of the `SEQTK_TRIM` process. An `!` can be used to imply the logical "not". - -Thus, if the `skip_trim` parameter is **not** `true`, the `SEQTK_TRIM` will be be executed. - -Now your if statement has been added to your main workflow file and has a default setting in your `nextflow.config` file, you will be able to flexibly skip the new trimming step using the `skip_trim` parameter. - -We can now run the pipeline with the new `skip_trim` parameter to check it is working: - -```console -nextflow run . -profile test,docker --outdir results --skip_trim -``` - -You should see that the `SEQTK_TRIM` process has been skipped in your execution: - -```console title="Output" -!! Only displaying parameters that differ from the pipeline defaults !! ------------------------------------------------------- -WARN: The following invalid input values have been detected: - -* --skip_trim: true - - -executor > local (1) -[7b/8b60a0] process > MYORG_MYFIRSTPIPELINE:MYFIRSTPIPELINE:MULTIQC [100%] 1 of 1 ✔ --[myorg/myfirstpipeline] Pipeline completed successfully- -``` - -#### 2.4.3 Validate input parameters - -When we ran the pipeline, we saw a warning message: - -```console -WARN: The following invalid input values have been detected: - -* --skip_trim: true -``` - -Parameters are validated through the `nextflow_schema.json` file. This file is also used by the nf-core website (for example, in [nf-core/mag](https://nf-co.re/mag/3.2.1/parameters/)) to render the parameter documentation and print the pipeline help message (`nextflow run . --help`). If you have added parameters and they have not been documented in the `nextflow_schema.json` file, then the input validation does not recognize the parameter. - -The `nextflow_schema.json` file can get very big and very complicated very quickly, and is hard to manually edit. Fortunately, the `nf-core pipelines schema build` command is designed to support developers write, check, validate, and propose additions to your `nextflow_schema.json` file. - -```console -nf-core pipelines schema build -``` - -This will enable you to launch a web builder to edit this file in your web browser rather than trying to edit this file manually. - -```console -INFO [✓] Default parameters match schema validation -INFO [✓] Pipeline schema looks valid (found 18 params) -✨ Found 'params.skip_trim' in the pipeline config, but not in the schema. Add to pipeline schema? [y/n]: y -INFO Writing schema with 19 params: 'nextflow_schema.json' -🚀 Launch web builder for customization and editing? [y/n]: y -``` - -Using the web builder you can add add details about your new parameters. - -The parameters that you have added to your pipeline will be added to the bottom of the `nf-core pipelines schema build` file. Some information about these parameters will be automatically filled based on the default value from your `nextflow.config`. You will be able to categorize your new parameters into a group, add icons, and add descriptions for each. - -![Pipeline parameters](./img/nf-core/pipeline_schema.png) - -!!!note - - Ungrouped parameters in schema will cause a warning. - -Once you have made your edits you can click `Finished` and all changes will be automatically added to your `nextflow_schema.json` file. - -If you rerun the previous command, the warning should disappear: - -```console -nextflow run . -profile test,docker --outdir results --skip_trim -``` - -```console title="Output" -!! Only displaying parameters that differ from the pipeline defaults !! ------------------------------------------------------- -executor > local (1) -[6c/c78d0c] process > MYORG_MYFIRSTPIPELINE:MYFIRSTPIPELINE:MULTIQC [100%] 1 of 1 ✔ --[myorg/myfirstpipeline] Pipeline completed successfully- -``` - -#### Takeaway - -You added a new parameter to the pipeline. Your pipeline can now run `seqtk` or the user can decide to skip it. You learned how parameters are handeled in nf-core using the JSON schema and how this gives you additional features, such as help text and validation. - -#### What's next? - -In the next step we will take a look at how we track metadata related to an input file. - ---- - -### 2.5 Meta maps - -Datasets often contain additional information relevant to the analysis, such as a sample name, information about sequencing protocols, or other conditions needed in the pipeline to process certain samples together, determine their output name, or adjust parameters. - -By convention, nf-core tracks this information as `meta` maps. These are `key`-`value` pairs that are passed into modules together with the files. We already saw this briefly when inspecting the `input` for `seqtk`: - -```groovy title="modules/nf-core/seqtk/trim/main.nf" linenums="11" -input: -tuple val(meta), path(reads) -``` - -If we uncomment our earlier `view` statement: - -```groovy title="workflows/myfirstpipeline.nf" linenums="28" -ch_samplesheet.view() -``` - -and run the pipeline again, we can see the current content of the `meta` maps: - -```console title="meta map" -[[id:SAMPLE1_PE, single_end:false], ....] -``` - -You can add any field that you require to the `meta` map. By default, nf-core modules expect an `id` field. - -#### Takeaway - -In this section you learned, that a `meta` map is used to pass along additional information for a sample in nf-core. It is a `map` (or dictionary) that allows you to assign arbitray keys to track any information you require in the workflow. - -#### What's next? - -In the next step we will take a look how we can add a new key to the `meta` map using the samplesheet. - ---- - -### 2.6 Simple Samplesheet adaptations - -nf-core pipelines typically use samplesheets as inputs to the pipelines. This allows us to: - -- validate each entry and print specific error messages. -- attach information to each input file. -- track which datasets are processed. - -Samplesheets are comma-separated text files with a header row specifying the column names, followed by one entry per row. For example, the samplesheet ([link](https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/samplesheet/samplesheet_test_illumina_amplicon.csv)) that we have been using during this teaching module looks like this: - -```csv title="samplesheet_test_illumina_amplicon.csv" -sample,fastq_1,fastq_2 -SAMPLE1_PE,https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/illumina/amplicon/sample1_R1.fastq.gz,https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/illumina/amplicon/sample1_R2.fastq.gz -SAMPLE2_PE,https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/illumina/amplicon/sample2_R1.fastq.gz,https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/illumina/amplicon/sample2_R2.fastq.gz -SAMPLE3_SE,https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/illumina/amplicon/sample1_R1.fastq.gz, -SAMPLE3_SE,https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/illumina/amplicon/sample2_R1.fastq.gz, -``` - -The structure of the samplesheet is specified in its own schema file in `assets/schema_input.json`. Each column has its own entry together with information about the column: - -```json title="assets/schema_input.json" -"properties": { - "sample": { - "type": "string", - "pattern": "^\\S+$", - "errorMessage": "Sample name must be provided and cannot contain spaces", - "meta": ["id"] - }, - "fastq_1": { - "type": "string", - "format": "file-path", - "exists": true, - "pattern": "^\\S+\\.f(ast)?q\\.gz$", - "errorMessage": "FastQ file for reads 1 must be provided, cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" - }, - "fastq_2": { - "type": "string", - "format": "file-path", - "exists": true, - "pattern": "^\\S+\\.f(ast)?q\\.gz$", - "errorMessage": "FastQ file for reads 2 cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" - } -}, -"required": ["sample", "fastq_1"] -``` - -This validates that the samplesheet has at least two columns: `sample` and `fastq1` (`"required": ["sample", "fastq_1"]`). It also checks that `fastq1` and `fastq2` are files, and that the file endings match a particular pattern. -Lastly, `sample` is information about the files that we want to attach and pass along the pipeline. nf-core uses `meta` maps for this: objects that have a key and a value. We can indicate this in the schema file directly by using the meta field: - -```json title="Sample column" - "sample": { - "type": "string", - "pattern": "^\\S+$", - "errorMessage": "Sample name must be provided and cannot contain spaces", - "meta": ["id"] - }, -``` - -This sets the key name as `id` and the value that is in the `sample` column, for example `SAMPLE1_PE`: - -```console title="meta map" -[id: SAMPLE1_PE] -``` - -By adding a new entry into the JSON schema, we can attach additional meta information that we want to track. This will automatically validate it for us and add it to the meta map. - -Let's add some new meta information, like the `sequencer` as an optional column: - -```json title="assets/schema_input.json" -"properties": { - "sample": { - "type": "string", - "pattern": "^\\S+$", - "errorMessage": "Sample name must be provided and cannot contain spaces", - "meta": ["id"] - }, - "sequencer": { - "type": "string", - "pattern": "^\\S+$", - "meta": ["sequencer"] - }, - "fastq_1": { - "type": "string", - "format": "file-path", - "exists": true, - "pattern": "^\\S+\\.f(ast)?q\\.gz$", - "errorMessage": "FastQ file for reads 1 must be provided, cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" - }, - "fastq_2": { - "type": "string", - "format": "file-path", - "exists": true, - "pattern": "^\\S+\\.f(ast)?q\\.gz$", - "errorMessage": "FastQ file for reads 2 cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" - } -}, -"required": ["sample", "fastq_1"] -``` - -We can now run our normal tests with the old samplesheet: - -```console -nextflow run . -profile docker,test --outdir results -``` - -The meta map now has a new key `sequencer`, that is empty because we did not specify a value yet: - -```console title="output" -[['id':'SAMPLE1_PE', 'sequencer':[], 'single_end':false], ... ] -[['id':'SAMPLE2_PE', 'sequencer':[], 'single_end':false], ... ] -[['id':'SAMPLE3_SE', 'sequencer':[], 'single_end':true], ... ] -``` - -We have also prepared a new samplesheet, that has the `sequencer` column. You can overwrite the existing input with this command: - -```console -nextflow run . -profile docker,test --outdir results --input ../../data/sequencer_samplesheet.csv -``` - -This populates the `sequencer` and we can see it in the pipeline, when `view`ing the samplesheet channel: - -```console title="output" -[['id':'SAMPLE1_PE', 'sequencer':'sequencer1', 'single_end':false], ... ] -[['id':'SAMPLE2_PE', 'sequencer':'sequencer2', 'single_end':false], ... ] -[['id':'SAMPLE3_SE', 'sequencer':'sequencer3', 'single_end':true], ... ] -``` - -We can comment the `ch_samplesheet.view()` line or remove it. We are not going to use it anymore in this training section. - -#### 2.6.1 Use the new meta key in the pipeline - -We can access this new meta value in the pipeline and use it to, for example, only enable trimming for samples from a particular sequencer. The [branch operator](https://www.nextflow.io/docs/stable/reference/operator.html#branch) let's us split -an input channel into several new output channels based on a selection criteria. Let's add this within the `if` block: - -```groovy title="workflows/myfirstpipeline.nf" linenums="31" - if (!params.skip_trim) { - - ch_seqtk_in = ch_samplesheet.branch { meta, reads -> - to_trim: meta["sequencer"] == "sequencer2" - other: true - } - - SEQTK_TRIM ( - ch_seqtk_in.to_trim - ) - ch_trimmed = SEQTK_TRIM.out.reads - ch_versions = ch_versions.mix(SEQTK_TRIM.out.versions.first()) - } -``` - -If we now rerun our default test, no reads are being trimmed (even though we did not specify `--skip_trim`): - -```console title="Output" -nextflow run . -profile docker,test --outdir results - -[- ] process > MYORG_MYFIRSTPIPELINE:MYFIRSTPIPELINE:SEQTK_TRIM - -[5a/f580bc] process > MYORG_MYFIRSTPIPELINE:MYFIRSTPIPELINE:MULTIQC [100%] 1 of 1 ✔ -``` - -If we use the samplesheet with the `sequencer` set, only one sample will be trimmed: - -```console -nextflow run . -profile docker,test --outdir results --input ../../data/sequencer_samplesheet.csv -resume -``` - -```console title="Output" -[47/fdf9de] process > MYORG_MYFIRSTPIPELINE:MYFIRSTPIPELINE:SEQTK_TRIM (SAMPLE2_PE) [100%] 1 of 1 ✔ -[2a/a742ae] process > MYORG_MYFIRSTPIPELINE:MYFIRSTPIPELINE:MULTIQC [100%] 1 of 1 ✔ -``` - -If you want to learn more about how to fine tune and develop the samplesheet schema further, visit [nf-schema](https://nextflow-io.github.io/nf-schema/2.2/nextflow_schema/sample_sheet_schema_specification/). - -#### Takeaway - -You explored how different samplesheets can provide different sets of additional information to your data files. You know how to adapt the samplesheet validation and how this is reflected in the pipeline in the `,meta` map. - -#### What's next? - -In the next step we will add a module that is not yet in nf-core. - ---- - -### 2.7 Create a custom module for your pipeline - -nf-core offers a comprehensive set of modules that have been created and curated by the community. However, as a developer, you may be interested in bespoke pieces of software that are not apart of the nf-core repository or customizing a module that already exists. - -In this instance, we will write a local module for the QC Tool [FastQE](https://fastqe.com/), which computes stats for FASTQ files and print those stats as emoji. - -This section should feel familiar to the `hello_modules` section. - -#### 2.7.1 Create the module - -!!! note "New module contributions are always welcome and encouraged!" - - If you have a module that you would like to contribute back to the community, reach out on the nf-core slack or open a pull request to the modules repository. - -Start by using the nf-core tooling to create a sceleton local module: - -```console -nf-core modules create -``` - -It will ask you to enter the tool name and some configurations for the module. We will use the defaults here: - -- Specify the tool name: `Name of tool/subtool: fastqe` -- Add the author name: `GitHub Username: (@):` -- Accept the defaults for the remaining prompts by typing `enter` - -This will create a new file in `modules/local/fastqe/main.nf` that already contains the container and conda definitions, the general structure of the process, and a number of TODO statements to guide you through the adaptation. - -!!! warning - - If the module already exists locally, the command will fail to prevent you from accidentally overwriting existing work: - - ```console - INFO Repository type: pipeline - INFO Press enter to use default values (shown in brackets) or type your own responses. ctrl+click underlined text to open links. - CRITICAL Module directory exists: 'modules/local/fastqe'. Use '--force' to overwrite - ``` - -Let's open the modules file: `modules/local/fastqe/main.nf`. - -You will notice, that it still calls `samtools` and the input are `bam`. - -From our sample sheet, we know we have fastq files instead, so let's change the input definition accordingly: - -```groovy title="modules/local/fastqe/main.nf" linenums="38" -tuple val(meta), path(reads) -``` - -The output of this tool is a tsv file with the emoji annotation, let's adapt the output as well: - -```groovy title="modules/local/fastqe/main.nf" linenums="42" -tuple val(meta), path("*.tsv"), emit: tsv -``` - -The script section still calls `samtools`. Let's change this to the proper call of the tool: - -```groovy title="modules/local/fastqe/main.nf" linenums="62" - fastqe \\ - $args \\ - $reads \\ - --output ${prefix}.tsv -``` - -And at last, we need to adapt the version retrieval. This tool does not have a version command, so we will add the release number manually: - -```groovy title="modules/local/fastqe/main.nf" linenums="52" - def VERSION = '0.3.3' -``` - -and write it to a file in the script section: - -```groovy title="modules/local/fastqe/main.nf" linenums="70" - fastqe: $VERSION -``` - -We will not cover [`stubs`](https://www.nextflow.io/docs/latest/process.html#stub) in this training. They are not necessary to run a module, so let's remove them for now: - -```groovy title="modules/local/fastqe/main.nf" linenums="74" -stub: - def args = task.ext.args ?: '' - def prefix = task.ext.prefix ?: "${meta.id}" - // TODO nf-core: A stub section should mimic the execution of the original module as best as possible - // Have a look at the following examples: - // Simple example: https://github.com/nf-core/modules/blob/818474a292b4860ae8ff88e149fbcda68814114d/modules/nf-core/bcftools/annotate/main.nf#L47-L63 - // Complex example: https://github.com/nf-core/modules/blob/818474a292b4860ae8ff88e149fbcda68814114d/modules/nf-core/bedtools/split/main.nf#L38-L54 - """ - touch ${prefix}.bam - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - fastqe: \$(samtools --version |& sed '1!d ; s/samtools //') - END_VERSIONS - """ -``` - -If you think this looks a bit messy and just want to add a complete final version, here's one we made earlier and we've removed all the commented out instructions: - -```groovy title="modules/local/fastqe/main.nf" linenums="1" -process FASTQE { - tag "$meta.id" - label 'process_single' - - conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/fastqe:0.3.3--pyhdfd78af_0': - 'biocontainers/fastqe:0.3.3--pyhdfd78af_0' }" - - input: - tuple val(meta), path(reads) - - output: - tuple val(meta), path("*.tsv"), emit: tsv - path "versions.yml" , emit: versions - - when: - task.ext.when == null || task.ext.when - - script: - def args = task.ext.args ?: '' - def prefix = task.ext.prefix ?: "${meta.id}" - def VERSION = '0.3.3' - """ - fastqe \\ - $args \\ - $reads \\ - --output ${prefix}.tsv - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - fastqe: $VERSION - END_VERSIONS - """ -} -``` - -#### 2.7.2 Include the module into the pipeline - -The module is now ready in your `modules/local` folder, but not yet included in your pipeline. Similar to `seqtk/trim` we need to add it to `workflows/myfirstpipeline.nf`: - -_Before:_ - -```groovy title="workflows/myfirstpipeline.nf" linenums="1" -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ -include { SEQTK_TRIM } from '../modules/nf-core/seqtk/trim/main' -include { MULTIQC } from '../modules/nf-core/multiqc/main' -``` - -_After:_ - -```groovy title="workflows/myfirstpipeline.nf" linenums="1" -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ -include { FASTQE } from '../modules/local/fastqe' -include { SEQTK_TRIM } from '../modules/nf-core/seqtk/trim/main' -include { MULTIQC } from '../modules/nf-core/multiqc/main' -``` - -and call it on our input data: - -```groovy title="workflows/myfirstpipeline.nf" linenums="45" - FASTQE(ch_samplesheet) - ch_versions = ch_versions.mix(FASTQE.out.versions.first()) -``` - -Let's run the pipeline again: - -```console -nextflow run . -profile docker,test --outdir results -``` - -In the results folder, you should now see a new subdirectory `fastqe/`, with the mean read qualities: - -```console title="SAMPLE1_PE.tsv" -Filename Statistic Qualities -sample1_R1.fastq.gz mean 😝 😝 😝 😝 😝 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😉 😉 😜 😜 😜 😉 😉 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😁 😉 😛 😜 😉 😉 😉 😉 😜 😜 😉 😉 😉 😉 😉 😁 😁 😁 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😜 😉 😉 😉 😉 😉 😜 😜 😜 😜 😜 😜 😜 😜 😜 😜 😜 😜 😜 😜 😛 😜 😜 😛 😛 😛 😚 -sample1_R2.fastq.gz mean 😌 😌 😌 😝 😝 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😜 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😉 😜 😉 😉 😜 😜 😉 😜 😜 😜 😜 😜 😜 😜 😜 😜 😜 😜 😜 😛 😜 😜 😜 😛 😜 😜 😜 😜 😛 😜 😛 😛 😛 😛 😛 😛 😛 😛 😛 😛 😛 😛 😝 😛 😝 😝 😝 😝 😝 😝 😝 😝 😝 😝 😝 😝 😝 😝 😌 😌 😌 😌 😌 😌 😌 😌 😌 😌 😌 😌 😌 😌 😌 😌 😌 😌 😌 😋 😋 😋 😋 😋 😋 😋 😋 😀 -``` - -#### Takeaway - -You added a new local module to the pipeline. We touched on how the module template files in nf-core look like and which aspects you need to adapt to add your own tool. - ---- - -## Summary - -In this side-quest you got an introduction to nf-core. You've learned: - -- Section 1: How to run nf-core pipelines - - 1. Where to find information about nf-core pipelines - 2. How to run a nf-core pipelines - -- Section 2: How to create an nf-core pipelines: - - 1. About nf-core tooling - 2. About the nf-core template: - - - How to create a basic nf-core pipeline - - What files are in the template - - 3. About nf-core/modules: - - - How to find one - - How to install it - - How to configure it in the `modules.config` - - 4. About parameters: - - - Where to add it in the workflow code - - How to set a default in the `nextflow.config` - - How to validated the parameter using the `nextflow_schema.json` - - 5. About `meta` maps: - - - What a `meta` map is - - How to access information from it - - How to add new fields in the `assets/schema_input.json` - - How to add a column in the samplesheet to track additional `meta` information - - 6. About developping a local module: - - - How to create a module sceleton file using nf-core tooling - - How to adapt the sceleton file - - How to include the module in the pipeline - -### What's next? - -Check out the [nf-core documentation](https://nf-co.re) to learn more. You can join the [nf-core community slack](https://nf-co.re/join#slack) where most of the exchange happens. You might want to: - -- Get involved in the development of an nf-core pipeline -- Contribute nf-core components -- Contribute a pipeline to nf-core (before you do, check their [guidelines](https://nf-co.re/docs/guidelines/pipelines/overview#ask-the-community)) -- Start developping your own nf-core style pipeline diff --git a/mkdocs.yml b/mkdocs.yml index 0478c3f16e..4ea78ffc5d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -80,7 +80,6 @@ nav: - side_quests/debugging.md - side_quests/essential_scripting_patterns.md - side_quests/nf-test.md - - side_quests/nf-core.md - Archive: - Fundamentals Training: - archive/basic_training/index.md diff --git a/side-quests/nf-core/data/sequencer_samplesheet.csv b/side-quests/nf-core/data/sequencer_samplesheet.csv deleted file mode 100644 index f2c3b78051..0000000000 --- a/side-quests/nf-core/data/sequencer_samplesheet.csv +++ /dev/null @@ -1,5 +0,0 @@ -sample,sequencer,fastq_1,fastq_2 -SAMPLE1_PE,sequencer1,https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/illumina/amplicon/sample1_R1.fastq.gz,https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/illumina/amplicon/sample1_R2.fastq.gz -SAMPLE2_PE,sequencer2,https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/illumina/amplicon/sample2_R1.fastq.gz,https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/illumina/amplicon/sample2_R2.fastq.gz -SAMPLE3_SE,sequencer3,https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/illumina/amplicon/sample1_R1.fastq.gz, -SAMPLE3_SE,sequencer3,https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/illumina/amplicon/sample2_R1.fastq.gz, From a35789db74b85f8b5623b964f345c265daadf354 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 31 Oct 2025 15:13:42 +0000 Subject: [PATCH 082/113] Fix workflow description to match actual behavior MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Corrected the Hello Nextflow workflow description: - sayHello writes greetings to files (doesn't add "world!") - convertToUpper operates on the greeting text - Added process names for clarity 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/02_rewrite_hello.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index 67d28c4553..0e77e78e18 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -285,12 +285,12 @@ A composable workflow must be called from a parent workflow—it cannot run on i If you haven't completed the [Hello Nextflow](../hello_nextflow/index.md) training, here's a quick overview of what this simple workflow does: 1. **Reads greetings** from a CSV file (e.g., "Hello", "Bonjour", "Holà") -2. **Says hello** by adding "world!" to each greeting -3. **Converts to uppercase** (e.g., "HELLO WORLD!") -4. **Collects results** into a single file -5. **Adds ASCII art** using cowpy to display the final output with a fun character +2. **Writes each greeting** to its own output file (e.g., "Hello-output.txt") +3. **Converts to uppercase** (e.g., "HELLO") +4. **Collects all uppercase greetings** into a single batch file +5. **Adds ASCII art** using cowpy to display the collected greetings with a fun character -The workflow uses four Nextflow processes organized into separate module files, takes an input CSV file of greetings, and produces a whimsical output file. +The workflow uses four Nextflow processes (`sayHello`, `convertToUpper`, `collectGreetings`, and `cowpy`) organized into separate module files, takes an input CSV file of greetings, and produces a whimsical ASCII art output file. We provide you with a clean, fully functional copy of the completed Hello Nextflow workflow in the directory `original-hello` along with its modules and the default CSV file it expects to use as input. From 49b69452fbdbed0f6ed9c982d171101896e40c64 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 31 Oct 2025 16:00:24 +0000 Subject: [PATCH 083/113] Try some folding directory listings --- docs/hello_nf-core/00_orientation.md | 34 ++-- docs/hello_nf-core/01_run_demo.md | 202 ++++++++++++----------- docs/hello_nf-core/02_rewrite_hello.md | 212 +++++++++++++------------ docs/hello_nf-core/03_use_module.md | 30 ++-- docs/hello_nf-core/04_make_module.md | 18 ++- 5 files changed, 263 insertions(+), 233 deletions(-) diff --git a/docs/hello_nf-core/00_orientation.md b/docs/hello_nf-core/00_orientation.md index aa825af334..f642a27890 100644 --- a/docs/hello_nf-core/00_orientation.md +++ b/docs/hello_nf-core/00_orientation.md @@ -71,22 +71,24 @@ tree . -L 2 If you run this inside `hello-nf-core`, you should see the following output: -```console title="Directory contents" -. -├── greetings.csv -├── original-hello -│ ├── hello.nf -│ ├── modules -│ └── nextflow.config -└── solutions - ├── composable-hello - ├── core-hello-part2 - ├── core-hello-part3 - ├── core-hello-part4 - └── core-hello-start - -8 directories, 3 files -``` +??? example "Directory contents" + + ```console + . + ├── greetings.csv + ├── original-hello + │ ├── hello.nf + │ ├── modules + │ └── nextflow.config + └── solutions + ├── composable-hello + ├── core-hello-part2 + ├── core-hello-part3 + ├── core-hello-part4 + └── core-hello-start + + 8 directories, 3 files + ``` **Here's a summary of what you should know to get started:** diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index a56240c353..8dd15254b7 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -97,11 +97,13 @@ By default, Nextflow saves them to `$NXF_HOME/assets`. tree -L 2 $NXF_HOME/assets/ ``` -```console title="Output" -/workspaces/.nextflow/assets/ -└── nf-core - └── demo -``` +??? example "Directory contents" + + ```console + /workspaces/.nextflow/assets/ + └── nf-core + └── demo + ``` !!! note @@ -122,11 +124,13 @@ This creates a shortcut that makes it easier to explore the code we just downloa tree -L 2 pipelines ``` -```console title="Output" -pipelines -└── nf-core - └── demo -``` +??? example "Directory contents" + + ```console + pipelines + └── nf-core + └── demo + ``` Now we can more easily peek into the source code as needed. @@ -294,28 +298,30 @@ Finally, let's have a look at the `demo-results` directory produced by the pipel tree -L 2 demo-results ``` -```console title="Output" -demo-results/ -├── fastqc -│ ├── SAMPLE1_PE -│ ├── SAMPLE2_PE -│ └── SAMPLE3_SE -├── fq -│ ├── SAMPLE1_PE -│ ├── SAMPLE2_PE -│ └── SAMPLE3_SE -├── multiqc -│ ├── multiqc_data -│ ├── multiqc_plots -│ └── multiqc_report.html -└── pipeline_info - ├── execution_report_2025-03-05_09-44-26.html - ├── execution_timeline_2025-03-05_09-44-26.html - ├── execution_trace_2025-03-05_09-44-26.txt - ├── nf_core_pipeline_software_mqc_versions.yml - ├── params_2025-03-05_09-44-29.json - └── pipeline_dag_2025-03-05_09-44-26.html -``` +??? example "Directory contents" + + ```console + demo-results/ + ├── fastqc + │ ├── SAMPLE1_PE + │ ├── SAMPLE2_PE + │ └── SAMPLE3_SE + ├── fq + │ ├── SAMPLE1_PE + │ ├── SAMPLE2_PE + │ └── SAMPLE3_SE + ├── multiqc + │ ├── multiqc_data + │ ├── multiqc_plots + │ └── multiqc_report.html + └── pipeline_info + ├── execution_report_2025-03-05_09-44-26.html + ├── execution_timeline_2025-03-05_09-44-26.html + ├── execution_trace_2025-03-05_09-44-26.txt + ├── nf_core_pipeline_software_mqc_versions.yml + ├── params_2025-03-05_09-44-29.json + └── pipeline_dag_2025-03-05_09-44-26.html + ``` If you're curious about the specifics of what that all means, check out [the nf-core/demo pipeline documentation page](https://nf-co.re/demo/1.0.2/). @@ -348,28 +354,30 @@ You can either use `tree` or use the file explorer in your IDE. tree -L 1 pipelines/nf-core/demo ``` -```console title="Output (top-level only)" -pipelines/nf-core/demo -├── assets -├── CHANGELOG.md -├── CITATIONS.md -├── CODE_OF_CONDUCT.md -├── conf -├── docs -├── LICENSE -├── main.nf -├── modules -├── modules.json -├── nextflow.config -├── nextflow_schema.json -├── nf-test.config -├── README.md -├── ro-crate-metadata.json -├── subworkflows -├── tests -├── tower.yml -└── workflows -``` +??? example "Directory contents" + + ```console + pipelines/nf-core/demo + ├── assets + ├── CHANGELOG.md + ├── CITATIONS.md + ├── CODE_OF_CONDUCT.md + ├── conf + ├── docs + ├── LICENSE + ├── main.nf + ├── modules + ├── modules.json + ├── nextflow.config + ├── nextflow_schema.json + ├── nf-test.config + ├── README.md + ├── ro-crate-metadata.json + ├── subworkflows + ├── tests + ├── tower.yml + └── workflows + ``` There's a lot going on in there, so we'll tackle this in stages. We're going to look at the following categories: @@ -399,10 +407,12 @@ The central logic of the pipeline is stored inside the `workflows` folder, in a tree pipelines/nf-core/demo/workflows ``` -```console title="Output" -pipelines/nf-core/demo/workflows -└── demo.nf -``` +??? example "Directory contents" + + ```console + pipelines/nf-core/demo/workflows + └── demo.nf + ``` `main.nf` also calls a few 'housekeeping' subworkflows that we're going to ignore for now. @@ -436,26 +446,28 @@ The module code file describing the process is always called `main.nf`, and is a tree -L 3 pipelines/nf-core/demo/modules ``` -```console title="Output" -pipelines/nf-core/demo/modules -└── nf-core - ├── fastqc - │   ├── environment.yml - │   ├── main.nf - │   ├── meta.yml - │   └── tests - ├── multiqc - │   ├── environment.yml - │   ├── main.nf - │   ├── meta.yml - │   └── tests - └── seqtk - └── trim - ├── environment.yml - ├── main.nf +??? example "Directory contents" + + ```console + pipelines/nf-core/demo/modules + └── nf-core + ├── fastqc + │   ├── environment.yml + │   ├── main.nf + │   ├── meta.yml + │   └── tests + ├── multiqc + │   ├── environment.yml + │   ├── main.nf + │   ├── meta.yml + │   └── tests + └── seqtk + └── trim + ├── environment.yml + ├── main.nf ├── meta.yml └── tests -``` + ``` Here you see that the `fastqc` and `multiqc` modules sit at the top level within the `nf-core` modules, whereas the `trim` module sits under the toolkit that it belongs to, `seqtk`. In this case there are no `local` modules. @@ -470,25 +482,27 @@ In an nf-core pipeline, the subworkflows are divided into `local` and `nf-core` tree -L 3 pipelines/nf-core/demo/subworkflows ``` -```console title="Output" -pipelines/nf-core/demo/subworkflows -├── local -│   └── utils_nfcore_demo_pipeline -│   └── main.nf -└── nf-core - ├── utils_nextflow_pipeline - │   ├── main.nf - │   ├── meta.yml - │   └── tests - ├── utils_nfcore_pipeline - │   ├── main.nf - │   ├── meta.yml - │   └── tests - └── utils_nfschema_plugin - ├── main.nf - ├── meta.yml +??? example "Directory contents" + + ```console + pipelines/nf-core/demo/subworkflows + ├── local + │   └── utils_nfcore_demo_pipeline + │   └── main.nf + └── nf-core + ├── utils_nextflow_pipeline + │   ├── main.nf + │   ├── meta.yml + │   └── tests + ├── utils_nfcore_pipeline + │   ├── main.nf + │   ├── meta.yml + │   └── tests + └── utils_nfschema_plugin + ├── main.nf + ├── meta.yml └── tests -``` + ``` In the case of the `nf-core/demo` pipeline, the subworkflows involved are all 'utility' or housekeeping subworkflows, as denoted by the `utils_` prefix in their names. These subworkflows are what produces the fancy nf-core header in the console output, among other accessory functions. diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index 0e77e78e18..e1d2c104ea 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -86,59 +86,61 @@ View the contents of the new directory to see how much work you saved yourself b tree core-hello ``` -```console title="Output" -core-hello/ -├── assets -│ ├── samplesheet.csv -│ └── schema_input.json -├── conf -│ ├── base.config -│ ├── modules.config -│ ├── test.config -│ └── test_full.config -├── docs -│ ├── output.md -│ ├── README.md -│ └── usage.md -├── main.nf -├── modules.json -├── nextflow.config -├── nextflow_schema.json -├── README.md -├── subworkflows -│ ├── local -│ │ └── utils_nfcore_hello_pipeline -│ │ └── main.nf -│ └── nf-core -│ ├── utils_nextflow_pipeline -│ │ ├── main.nf -│ │ ├── meta.yml -│ │ └── tests -│ │ ├── main.function.nf.test -│ │ ├── main.function.nf.test.snap -│ │ ├── main.workflow.nf.test -│ │ └── nextflow.config -│ ├── utils_nfcore_pipeline -│ │ ├── main.nf -│ │ ├── meta.yml -│ │ └── tests -│ │ ├── main.function.nf.test -│ │ ├── main.function.nf.test.snap -│ │ ├── main.workflow.nf.test -│ │ ├── main.workflow.nf.test.snap -│ │ └── nextflow.config -│ └── utils_nfschema_plugin -│ ├── main.nf -│ ├── meta.yml -│ └── tests -│ ├── main.nf.test -│ ├── nextflow.config -│ └── nextflow_schema.json -└── workflows - └── hello.nf - -14 directories, 34 files -``` +??? example "Directory contents" + + ```console + core-hello/ + ├── assets + │ ├── samplesheet.csv + │ └── schema_input.json + ├── conf + │ ├── base.config + │ ├── modules.config + │ ├── test.config + │ └── test_full.config + ├── docs + │ ├── output.md + │ ├── README.md + │ └── usage.md + ├── main.nf + ├── modules.json + ├── nextflow.config + ├── nextflow_schema.json + ├── README.md + ├── subworkflows + │ ├── local + │ │ └── utils_nfcore_hello_pipeline + │ │ └── main.nf + │ └── nf-core + │ ├── utils_nextflow_pipeline + │ │ ├── main.nf + │ │ ├── meta.yml + │ │ └── tests + │ │ ├── main.function.nf.test + │ │ ├── main.function.nf.test.snap + │ │ ├── main.workflow.nf.test + │ │ └── nextflow.config + │ ├── utils_nfcore_pipeline + │ │ ├── main.nf + │ │ ├── meta.yml + │ │ └── tests + │ │ ├── main.function.nf.test + │ │ ├── main.function.nf.test.snap + │ │ ├── main.workflow.nf.test + │ │ ├── main.workflow.nf.test.snap + │ │ └── nextflow.config + │ └── utils_nfschema_plugin + │ ├── main.nf + │ ├── meta.yml + │ └── tests + │ ├── main.nf.test + │ ├── nextflow.config + │ └── nextflow_schema.json + └── workflows + └── hello.nf + + 14 directories, 34 files + ``` That's a lot of files! @@ -298,16 +300,18 @@ We provide you with a clean, fully functional copy of the completed Hello Nextfl tree original-hello/ ``` -```console title="Output" -original-hello/ -├── hello.nf -├── modules -│ ├── collectGreetings.nf -│ ├── convertToUpper.nf -│ ├── cowpy.nf -│ └── sayHello.nf -└── nextflow.config -``` +??? example "Directory contents" + + ```console + original-hello/ + ├── hello.nf + ├── modules + │ ├── collectGreetings.nf + │ ├── convertToUpper.nf + │ ├── cowpy.nf + │ └── sayHello.nf + └── nextflow.config + ``` Feel free to run it to satisfy yourself that it works: @@ -701,14 +705,16 @@ You should now see the directory of modules listed under `core-hello/`. tree core-hello/modules ``` -```console title="Output" -core-hello/modules -└── local - ├── collectGreetings.nf - ├── convertToUpper.nf - ├── cowpy.nf - └── sayHello.nf -``` +??? example "Directory contents" + + ```console + core-hello/modules + └── local + ├── collectGreetings.nf + ├── convertToUpper.nf + ├── cowpy.nf + └── sayHello.nf + ``` Now let's set up the module import statements. @@ -1270,19 +1276,21 @@ We didn't change anything to the modules themselves, so the outputs handled by m tree results ``` -```console title="Output" -results -├── Bonjour-output.txt -├── COLLECTED-test-batch-output.txt -├── COLLECTED-test-output.txt -├── cowpy-COLLECTED-test-batch-output.txt -├── cowpy-COLLECTED-test-output.txt -├── Hello-output.txt -├── Holà-output.txt -├── UPPER-Bonjour-output.txt -├── UPPER-Hello-output.txt -└── UPPER-Holà-output.txt -``` +??? example "Directory contents" + + ```console + results + ├── Bonjour-output.txt + ├── COLLECTED-test-batch-output.txt + ├── COLLECTED-test-output.txt + ├── cowpy-COLLECTED-test-batch-output.txt + ├── cowpy-COLLECTED-test-output.txt + ├── Hello-output.txt + ├── Holà-output.txt + ├── UPPER-Bonjour-output.txt + ├── UPPER-Hello-output.txt + └── UPPER-Holà-output.txt + ``` Anything that is hooked up to the nf-core template code gets put into a directory generated automatically, called `core-hello-results/`. This includes various execution reports and metadata that you can find under `core-hello-results/pipeline_info`. @@ -1291,23 +1299,25 @@ This includes various execution reports and metadata that you can find under `co tree core-hello-results ``` -```console title="Output" -core-hello-results -└── pipeline_info - ├── execution_report_2025-06-03_18-22-28.html - ├── execution_report_2025-06-03_20-11-39.html - ├── execution_timeline_2025-06-03_18-22-28.html - ├── execution_timeline_2025-06-03_20-11-39.html - ├── execution_trace_2025-06-03_18-22-28.txt - ├── execution_trace_2025-06-03_20-10-11.txt - ├── execution_trace_2025-06-03_20-11-39.txt - ├── hello_software_versions.yml - ├── params_2025-06-03_18-22-32.json - ├── params_2025-06-03_20-10-15.json - ├── params_2025-06-03_20-11-43.json - ├── pipeline_dag_2025-06-03_18-22-28.html - └── pipeline_dag_2025-06-03_20-11-39.html -``` +??? example "Directory contents" + + ```console + core-hello-results + └── pipeline_info + ├── execution_report_2025-06-03_18-22-28.html + ├── execution_report_2025-06-03_20-11-39.html + ├── execution_timeline_2025-06-03_18-22-28.html + ├── execution_timeline_2025-06-03_20-11-39.html + ├── execution_trace_2025-06-03_18-22-28.txt + ├── execution_trace_2025-06-03_20-10-11.txt + ├── execution_trace_2025-06-03_20-11-39.txt + ├── hello_software_versions.yml + ├── params_2025-06-03_18-22-32.json + ├── params_2025-06-03_20-10-15.json + ├── params_2025-06-03_20-11-43.json + ├── pipeline_dag_2025-06-03_18-22-28.html + └── pipeline_dag_2025-06-03_20-11-39.html + ``` In our case, we didn't explicitly mark anything else as an output, so there's nothing else there. diff --git a/docs/hello_nf-core/03_use_module.md b/docs/hello_nf-core/03_use_module.md index 14df901813..b6334e4e1a 100644 --- a/docs/hello_nf-core/03_use_module.md +++ b/docs/hello_nf-core/03_use_module.md @@ -157,20 +157,22 @@ Let's check that the module was installed correctly: tree modules/nf-core/cat ``` -```console title="Output" -modules/nf-core/cat -└── cat - ├── environment.yml - ├── main.nf - ├── meta.yml - └── tests - ├── main.nf.test - ├── main.nf.test.snap - ├── nextflow_unzipped_zipped.config - └── nextflow_zipped_unzipped.config - -2 directories, 7 files -``` +??? example "Directory contents" + + ```console + modules/nf-core/cat + └── cat + ├── environment.yml + ├── main.nf + ├── meta.yml + └── tests + ├── main.nf.test + ├── main.nf.test.snap + ├── nextflow_unzipped_zipped.config + └── nextflow_zipped_unzipped.config + + 2 directories, 7 files + ``` You can also verify the installation by listing locally installed modules: diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index b20875e4c6..290b399435 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -599,14 +599,16 @@ The tool handles the complexity of finding package information and setting up th The tool creates a complete module structure in `modules/local/` (or `modules/nf-core/` if you're in the nf-core/modules repository): -```console -modules/local/cowpy -├── environment.yml -├── main.nf -├── meta.yml -└── tests - └── main.nf.test -``` +??? example "Directory contents" + + ```console + modules/local/cowpy + ├── environment.yml + ├── main.nf + ├── meta.yml + └── tests + └── main.nf.test + ``` Each file serves a specific purpose: From bcf14eb3237bdc7b237b9ead17b2d7cfc92a0159 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 31 Oct 2025 17:04:18 +0000 Subject: [PATCH 084/113] Replace step labels and bold text with numbered headings in hello_nf-core MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert all instances of step labels and bold text pseudo-headings to proper numbered headings for consistency and better navigation. Changes: - 03_use_module.md: Convert 5 "Step X:" labels to numbered headings (1.9.1-1.9.5) - 04_make_module.md: Number 15 subsection headings (1.2.1-1.2.6, 1.3.1-1.3.4, 2.1.1-2.1.2, 2.2.1-2.2.3) - 05_input_validation.md: Convert 3 bold labels to numbered headings (2.2.1, 3.7.1-3.7.2) and fix incorrectly numbered intro sections Benefits: - Consistent numbered heading structure throughout documentation - All subsections appear in table of contents and sidebar navigation - Sections can be directly linked with anchor links - Easier to maintain with automated heading validation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/03_use_module.md | 10 ++++---- docs/hello_nf-core/04_make_module.md | 30 +++++++++++------------ docs/hello_nf-core/05_input_validation.md | 14 +++++++---- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/docs/hello_nf-core/03_use_module.md b/docs/hello_nf-core/03_use_module.md index b6334e4e1a..e7410dac8f 100644 --- a/docs/hello_nf-core/03_use_module.md +++ b/docs/hello_nf-core/03_use_module.md @@ -336,7 +336,7 @@ Now we need to modify our workflow code to use `CAT_CAT` instead of `collectGree Open [core-hello/workflows/hello.nf](core-hello/workflows/hello.nf) and make the following changes to the workflow logic in the `main` block. -#### Step 1: Create a metadata map +#### 1.9.1. Create a metadata map First, we need to create a metadata map for `CAT_CAT`. Remember that nf-core modules require metadata with at least an `id` field. @@ -376,7 +376,7 @@ Add these lines after the `convertToUpper` call, removing the `collectGreetings` This creates a simple metadata map where the `id` is set to our batch name (which will be "test" when using the test profile). -#### Step 2: Create a channel with metadata tuples +#### 1.9.2. Create a channel with metadata tuples Next, transform the channel of files into a channel of tuples containing metadata and files: @@ -419,7 +419,7 @@ This line does two things: - `.collect()` gathers all files from the `convertToUpper` output into a single list - `.map { files -> tuple(cat_meta, files) }` creates a tuple of `[metadata, files]` in the format `CAT_CAT` expects -#### Step 3: Call CAT_CAT +#### 1.9.3. Call CAT_CAT Now call `CAT_CAT` with the properly formatted channel: @@ -460,7 +460,7 @@ Now call `CAT_CAT` with the properly formatted channel: cowpy(collectGreetings.out.outfile, params.character) ``` -#### Step 4: Remove the legacy collectGreetings import +#### 1.9.4. Remove the legacy collectGreetings import Since we're no longer using the `collectGreetings` module, remove its import statement from the top of the file: @@ -497,7 +497,7 @@ Since we're no longer using the `collectGreetings` module, remove its import sta include { CAT_CAT } from '../modules/nf-core/cat/cat/main' ``` -#### Step 5: Update cowpy to use CAT_CAT output +#### 1.9.5. Update cowpy to use CAT_CAT output Finally, update the `cowpy` call to use the output from `CAT_CAT`. Since `cowpy` doesn't accept metadata tuples yet (we'll fix this in the next section), we need to extract just the file: diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index 290b399435..d433ca07bd 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -166,7 +166,7 @@ Now let's address another nf-core pattern: simplifying module interfaces by usin Currently, our `cowpy` module requires the `character` parameter to be passed as a separate input. While this works, nf-core modules use a different approach for **tool configuration arguments**: instead of adding input parameters for every tool option, they use `ext.args` to pass these via configuration. This keeps the module interface focused on essential data (files, metadata, and any mandatory per-sample parameters), while tool configuration options are handled through `ext.args`. -#### Understanding ext.args +#### 1.2.1. Understanding ext.args The `task.ext.args` pattern is an nf-core convention for passing command-line arguments to tools through configuration rather than as process inputs. Instead of adding input parameters for tool options, nf-core modules accept arguments through the `ext.args` configuration directive. @@ -182,7 +182,7 @@ Benefits of this approach: - **Portability**: Modules can be reused without hardcoded tool options - **No workflow changes**: Adding or changing tool options doesn't require updating workflow code -#### Centralized publishing configuration +#### 1.2.2. Centralized publishing configuration Before we update the module to use `ext.args`, let's address an important nf-core convention: **modules should not contain hardcoded `publishDir` directives**. @@ -211,7 +211,7 @@ Benefits of this approach: - **Easy customization**: Override publishing behavior in config, not in module code - **Portable modules**: Modules don't hardcode output locations -#### Update the module +#### 1.2.3. Update the module Now let's update the cowpy module to use `ext.args` and remove the local `publishDir`. @@ -278,7 +278,7 @@ Key changes: The module interface is now simpler - it only accepts the essential metadata and file inputs. By removing the local `publishDir`, we follow the nf-core convention of centralizing all publishing configuration in `modules.config`. -#### Configure ext.args +#### 1.2.4. Configure ext.args Now we need to configure the `ext.args` to pass the character option. This allows us to keep the module interface simple while still providing the character option at the pipeline level. @@ -321,7 +321,7 @@ Key points: The `modules.config` file is where nf-core pipelines centralize per-module configuration. This separation of concerns makes modules more reusable across different pipelines. -#### Update the workflow +#### 1.2.5. Update the workflow Since the cowpy module no longer requires the `character` parameter as an input, we need to update the workflow call. @@ -343,7 +343,7 @@ Open `workflows/hello.nf` and update the cowpy call: The workflow code is now cleaner - we don't need to pass `params.character` directly to the process. The module interface is kept minimal, making it more portable, while the pipeline still provides the explicit option through configuration. -#### Test +#### 1.2.6. Test Test that the workflow still works with the ext.args configuration. Let's specify a different character to verify the configuration is working (using `kosh`, one of the more... enigmatic options): @@ -405,7 +405,7 @@ cat work/bd/0abaf8*/cowpy-test.txt There's one more nf-core pattern we can apply: using `ext.prefix` for configurable output file naming. -#### Understanding ext.prefix +#### 1.3.1. Understanding ext.prefix The `task.ext.prefix` pattern is another nf-core convention for standardizing output file naming across modules while keeping it configurable. @@ -416,7 +416,7 @@ Benefits: - **Consistent**: All nf-core modules follow this pattern - **Predictable**: Easy to know what output files will be called -#### Update the module +#### 1.3.2. Update the module Let's update the cowpy module to use `ext.prefix` for output file naming. @@ -481,7 +481,7 @@ Key changes: Note that the local `publishDir` has already been removed in the previous step, so we're continuing with the centralized configuration approach. -#### Configure ext.prefix +#### 1.3.3. Configure ext.prefix To maintain the same output file naming as before (`cowpy-.txt`), we can configure `ext.prefix` in modules.config. @@ -510,7 +510,7 @@ Note that we use a closure (`{ "cowpy-${meta.id}" }`) which has access to `meta` The `ext.prefix` closure has access to `meta` because the configuration is evaluated in the context of the process execution, where metadata is available. -#### Test and verify +#### 1.3.4. Test and verify Test the workflow once more: @@ -595,7 +595,7 @@ You'll be prompted for: The tool handles the complexity of finding package information and setting up the structure, allowing you to focus on implementing the tool's specific logic. -#### What gets generated +#### 2.1.1. What gets generated The tool creates a complete module structure in `modules/local/` (or `modules/nf-core/` if you're in the nf-core/modules repository): @@ -684,7 +684,7 @@ The template also includes several additional nf-core conventions that we didn't These additional conventions make modules more maintainable and provide better visibility into pipeline execution. -#### Completing the environment and container setup +#### 2.1.2. Completing the environment and container setup In the case of cowpy, the tool warned that it couldn't find the package in Bioconda (the primary channel for bioinformatics tools). However, cowpy is available in conda-forge, so you would complete the `environment.yml` like this: @@ -716,7 +716,7 @@ Once you've completed the environment setup and filled in the command logic, the The [nf-core/modules](https://github.com/nf-core/modules) repository welcomes contributions of well-tested, standardized modules. -#### Why contribute? +#### 2.2.1. Why contribute? Contributing your modules to nf-core: @@ -725,7 +725,7 @@ Contributing your modules to nf-core: - Provides quality assurance through code review and automated testing - Gives your work visibility and recognition -#### Contributing workflow +#### 2.2.2. Contributing workflow To contribute a module to nf-core: @@ -739,7 +739,7 @@ To contribute a module to nf-core: For detailed instructions, see the [nf-core components tutorial](https://nf-co.re/docs/tutorials/nf-core_components/components). -#### Resources +#### 2.2.3. Resources - **Components tutorial**: [Complete guide to creating and contributing modules](https://nf-co.re/docs/tutorials/nf-core_components/components) - **Module specifications**: [Technical requirements and guidelines](https://nf-co.re/docs/guidelines/components/modules) diff --git a/docs/hello_nf-core/05_input_validation.md b/docs/hello_nf-core/05_input_validation.md index 45a08ea0c9..8f9480954f 100644 --- a/docs/hello_nf-core/05_input_validation.md +++ b/docs/hello_nf-core/05_input_validation.md @@ -36,14 +36,18 @@ The pipeline fails immediately with clear, actionable error messages. This saves nf-core pipelines validate two different kinds of input: -**Parameter validation**: Validates command-line parameters (flags like `--outdir`, `--batch`, `--input`) +### Parameter validation + +This validates command-line parameters (flags like `--outdir`, `--batch`, `--input`): - Checks parameter types, ranges, and formats - Ensures required parameters are provided - Validates file paths exist - Defined in `nextflow_schema.json` -**Input data validation**: Validates the contents of input files (like sample sheets or CSV files) +### Input data validation + +This validates the contents of input files (like sample sheets or CSV files) - Checks column structure and data types - Validates file references within the input file @@ -279,7 +283,7 @@ Press `Ctrl+C` to exit the schema builder. The tool has now updated your `nextflow_schema.json` file with the new `batch` parameter, handling all the JSON Schema syntax correctly. -**Verify the changes:** +#### 2.2.1. Verify the changes ```bash grep -A 25 '"input_output_options"' nextflow_schema.json @@ -636,7 +640,7 @@ Now nf-schema will validate both parameter types AND the input file contents. Let's verify that our validation works by testing both valid and invalid inputs. -**Test with valid input:** +#### 3.7.1. Test with valid input First, confirm the pipeline runs successfully with valid input: @@ -663,7 +667,7 @@ executor > local (10) Great! The pipeline runs successfully and validation passes silently. The warning about `--character` is just informational since it's not defined in the schema. If you want, use what you've learned to add validation for that parameter too! -**Test with invalid input:** +#### 3.7.2. Test with invalid input Now let's test that validation catches errors. Create a test file with an invalid column name: From dd73bdfba846778a7895ff7b8aaf533df52b2cab Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Fri, 31 Oct 2025 17:10:05 +0000 Subject: [PATCH 085/113] Prettier --- docs/hello_nf-core/02_rewrite_hello.md | 2 +- docs/hello_nf-core/03_use_module.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index e1d2c104ea..7291a1a950 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -138,7 +138,7 @@ tree core-hello │ └── nextflow_schema.json └── workflows └── hello.nf - + 14 directories, 34 files ``` diff --git a/docs/hello_nf-core/03_use_module.md b/docs/hello_nf-core/03_use_module.md index e7410dac8f..8b89daa9ab 100644 --- a/docs/hello_nf-core/03_use_module.md +++ b/docs/hello_nf-core/03_use_module.md @@ -170,7 +170,7 @@ tree modules/nf-core/cat ├── main.nf.test.snap ├── nextflow_unzipped_zipped.config └── nextflow_zipped_unzipped.config - + 2 directories, 7 files ``` From 4f06fe94f5c76b21ae533a664621f0480b4c9bfb Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Tue, 4 Nov 2025 10:59:38 +0000 Subject: [PATCH 086/113] Address Geraldine's editorial comments on Hello nf-core MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit addresses the straightforward editorial feedback from Geraldine's review of PR #691, focusing on improving clarity and messaging for the research pipeline audience. Changes: - Strip em-dashes throughout (replace with periods/separate sentences) - Revise messaging to avoid implying production vs research dichotomy - Remove "production-ready" claims about the toy example - Improve explanations of workflow summaries and technical concepts - Clarify ext.args benefits (avoiding many optional input parameters) - Replace jargon (e.g., "test harness") with simpler terms - Make "learn by doing" messaging more specific and actionable 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/00_orientation.md | 10 +++++----- docs/hello_nf-core/01_run_demo.md | 2 +- docs/hello_nf-core/02_rewrite_hello.md | 4 ++-- docs/hello_nf-core/04_make_module.md | 21 +++++++++++++-------- 4 files changed, 21 insertions(+), 16 deletions(-) diff --git a/docs/hello_nf-core/00_orientation.md b/docs/hello_nf-core/00_orientation.md index f642a27890..80c8a68f01 100644 --- a/docs/hello_nf-core/00_orientation.md +++ b/docs/hello_nf-core/00_orientation.md @@ -36,13 +36,13 @@ Now let's have a look at the contents of this directory. ## What you'll learn This training course teaches you the core concepts for building nf-core-style pipelines. -We can't cover everything—nf-core pipelines have many features and conventions developed by the community over years—but we focus on the essential concepts that will help you get started and understand how nf-core works. +We can't cover everything. nf-core pipelines have many features and conventions developed by the community over years. However, we focus on the essential concepts that will help you get started and understand how nf-core works. -First, you'll **run an existing nf-core pipeline** to understand how they're structured and what makes them different from basic Nextflow workflows. +First, you'll **run an existing nf-core pipeline** to see what makes them different from basic Nextflow workflows. The extensive directory structure, configuration system, and standardized conventions might seem overwhelming at first, but understanding them is essential for working within the template. Next, you'll **adapt a simple workflow to the nf-core template scaffold**. -Most real pipelines start from existing code, so learning how to restructure workflows to work within nf-core's nested workflow system is a practical skill you'll use repeatedly. +Many pipeline development efforts start from existing code, so learning how to restructure workflows to work within nf-core's nested workflow system is a practical skill you'll use repeatedly. Then you'll discover one of nf-core's biggest advantages: the **community modules library**. Instead of writing every process from scratch, you'll learn to integrate pre-built, tested modules that wrap common bioinformatics tools. @@ -52,9 +52,9 @@ Of course, the modules library doesn't have everything, so you'll **create your You'll learn the specific structure, naming conventions, and metadata requirements that make modules shareable and maintainable by the community. Finally, you'll implement **comprehensive validation** for both command-line parameters and input data files using nf-schema. -This catches errors before pipelines run, providing fast feedback and clear error messages—a key differentiator between research scripts and production pipelines. +This catches errors before pipelines run, providing fast feedback and clear error messages. This type of upfront validation makes pipelines more robust and easier to use. -By the end, you'll have transformed a basic Nextflow workflow into a production-ready, nf-core-style pipeline with standardized structure, reusable components, and robust validation. +By the end, you'll have transformed a basic Nextflow workflow into an nf-core-style pipeline with standardized structure, reusable components, and robust validation. ## Materials provided diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index 8dd15254b7..27f8134e35 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -190,7 +190,7 @@ params { The test profile shows us what has been pre-configured for testing: most notably, the `input` parameter is already set to point to a test dataset, so we don't need to provide our own data. The comment block at the top also includes a usage example showing how to run with this test profile. -Notice that it includes `--outdir ` - this tells us we'll need to specify an output directory when we run the pipeline. +Notice that it includes `--outdir `. This tells us we'll need to specify an output directory when we run the pipeline. ### 2.2. Run the pipeline diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index 7291a1a950..0d5ede11e1 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -529,8 +529,8 @@ That is going to be defined in the parent workflow, also called the **entrypoint ### 2.6. Make a dummy entrypoint workflow -Before integrating our composable workflow into the complex nf-core scaffold, let's verify it works correctly with a simple test harness. -We can make a dummy entrypoint workflow to test the composable workflow in isolation. +Before integrating our composable workflow into the complex nf-core scaffold, let's verify it works correctly. +We can make a simple dummy entrypoint workflow to test the composable workflow in isolation. Create a blank file named `main.nf` in the same`original-hello` directory. diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index d433ca07bd..9f1203a2ff 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -1,15 +1,15 @@ # Part 4: Make an nf-core module -In this fourth part of the Hello nf-core training course, we show you how to create an nf-core module by learning the key conventions that make modules portable and maintainable. +In this fourth part of the Hello nf-core training course, we show you how to create an nf-core module by applying the key conventions that make modules portable and maintainable. The nf-core project provides a command (`nf-core modules create`) that generates properly structured module templates automatically. -However, for teaching purposes, we're going to **learn by doing**: transforming the local `cowpy` module in your `core-hello` pipeline into an nf-core-style module step-by-step. -This hands-on approach will help you understand the patterns deeply, making you better equipped to work with nf-core modules in practice. +However, for teaching purposes, we're going to start by doing it manually: transforming the local `cowpy` module in your `core-hello` pipeline into an nf-core-style module step-by-step. +After that, we'll show you how to use the template-based module creation to work more efficiently in the future. We'll apply three essential nf-core patterns incrementally: 1. **Metadata tuples**: Accept and propagate sample metadata through the workflow -2. **`ext.args`**: Simplify the module interface by handling optional arguments via configuration +2. **`ext.args`**: Keep the module interface minimal by handling optional tool arguments via configuration rather than as inputs 3. **`ext.prefix`**: Standardize output file naming with configurable prefixes Once you understand these patterns, we'll show you how to use the official nf-core tooling to create modules efficiently. @@ -164,15 +164,20 @@ executor > local (8) Now let's address another nf-core pattern: simplifying module interfaces by using `ext.args` for optional command-line arguments. -Currently, our `cowpy` module requires the `character` parameter to be passed as a separate input. While this works, nf-core modules use a different approach for **tool configuration arguments**: instead of adding input parameters for every tool option, they use `ext.args` to pass these via configuration. This keeps the module interface focused on essential data (files, metadata, and any mandatory per-sample parameters), while tool configuration options are handled through `ext.args`. +Currently, our `cowpy` module requires the `character` parameter to be passed as an input via the process `input:` block. +This works, but it forces us to provide a value for `character` every time we call the process, even if we're happy with a default. +For tools with many optional parameters, having to provide values for all of them gets annoying fast. + +nf-core modules use a different approach for **tool configuration arguments**: instead of declaring inputs for every tool option, they use `ext.args` to pass these via configuration. +This keeps the module interface focused on essential data (files, metadata, and any mandatory per-sample parameters), while tool configuration options are handled through `ext.args`. #### 1.2.1. Understanding ext.args -The `task.ext.args` pattern is an nf-core convention for passing command-line arguments to tools through configuration rather than as process inputs. Instead of adding input parameters for tool options, nf-core modules accept arguments through the `ext.args` configuration directive. +The `task.ext.args` pattern is an nf-core convention for passing command-line arguments to tools through configuration rather than as process inputs. -!!! note "ext.args can be dynamic" +!!! note "ext.args can do more" - While `ext.args` is configured outside the module, it can access metadata to provide sample-specific values using closures. For example: `ext.args = { meta.single_end ? '--single' : '--paired' }` + The `ext.args` system has powerful additional capabilities not covered here, including switching argument values dynamically based on metadata. See the [nf-core module specifications](https://nf-co.re/docs/guidelines/components/modules) for more details. Benefits of this approach: From 439f480099b9f39673a1763066bbc2f094b3cff7 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Tue, 4 Nov 2025 11:08:18 +0000 Subject: [PATCH 087/113] Remove remaining em-dashes from Hello nf-core MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Strip all remaining em-dashes from the hello nf-core documentation, replacing them with periods, commas, or colons as appropriate for cleaner, more straightforward technical writing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/02_rewrite_hello.md | 2 +- docs/hello_nf-core/04_make_module.md | 2 +- docs/hello_nf-core/summary.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index 0d5ede11e1..e29b93d53d 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -280,7 +280,7 @@ Learn how to make a simple workflow composable as a prelude to making it nf-core ## 2. Make the original Hello Nextflow workflow composable Before we can integrate our workflow into the nf-core scaffold, we need to make it **composable**. -A composable workflow must be called from a parent workflow—it cannot run on its own—which is exactly how the nf-core template is structured. +A composable workflow must be called from a parent workflow. It cannot run on its own, which is exactly how the nf-core template is structured. ### What does the Hello Nextflow workflow do? diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index 9f1203a2ff..9d7ee1a2d7 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -761,7 +761,7 @@ You now know how to create nf-core modules! You learned the three key patterns t - **`ext.prefix`** standardizes output file naming - **Centralized configuration** in `modules.config` keeps modules reusable -By transforming `cowpy` step-by-step, you developed a deep understanding of these patterns—making you equipped to work with, debug, and create nf-core modules. +By transforming `cowpy` step-by-step, you developed a deep understanding of these patterns, making you equipped to work with, debug, and create nf-core modules. In practice, you'll use `nf-core modules create` to generate properly structured modules with these patterns built in from the start. Finally, you learned how to contribute modules to the nf-core community, making tools available to researchers worldwide while benefiting from ongoing community maintenance. diff --git a/docs/hello_nf-core/summary.md b/docs/hello_nf-core/summary.md index 663c399399..1410029a56 100644 --- a/docs/hello_nf-core/summary.md +++ b/docs/hello_nf-core/summary.md @@ -4,7 +4,7 @@ Congratulations on completing the Hello nf-core training course! 🎉 ## Your journey -You started with a simple Nextflow workflow from the Hello Nextflow course—a straightforward pipeline that processed greetings through a few steps and added some ASCII art. +You started with a simple Nextflow workflow from the Hello Nextflow course: a straightforward pipeline that processed greetings through a few steps and added some ASCII art. Through five parts, you've transformed that basic workflow into a production-ready nf-core pipeline. ### What you built From 21936bfa9c75cb63048c6f6427f72682da06be17 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Tue, 4 Nov 2025 11:56:01 +0000 Subject: [PATCH 088/113] Address editorial comments from PR review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add link to Hello Nextflow configuration section in 01_run_demo.md - Rewrite workflow summary in 02_rewrite_hello.md to be more natural - Remove redundant sentence about nf-core tooling in 04_make_module.md - Restructure ext.args section with high-level overview first - Improve ext.args explanation to be more specific about what it is - Separate ext.args and publishDir discussions for better flow 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/01_run_demo.md | 2 ++ docs/hello_nf-core/02_rewrite_hello.md | 3 ++- docs/hello_nf-core/04_make_module.md | 36 ++++++++++++++++---------- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index 27f8134e35..e7a66c18c9 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -204,6 +204,8 @@ We're also going to specify `-profile docker,test`, which by nf-core convention nf-core pipelines are designed to work with containers (Docker, Singularity, etc.) to ensure reproducibility and eliminate software installation issues. The profile system allows you to easily switch between different container engines or execution environments. + For more details on how configuration profiles work in Nextflow, see [Hello Nextflow Part 6: Configuration](../hello_nextflow/06_hello_configuration.md). + Let's try it! ```bash diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index e29b93d53d..a0f7ad8117 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -292,7 +292,8 @@ If you haven't completed the [Hello Nextflow](../hello_nextflow/index.md) traini 4. **Collects all uppercase greetings** into a single batch file 5. **Adds ASCII art** using cowpy to display the collected greetings with a fun character -The workflow uses four Nextflow processes (`sayHello`, `convertToUpper`, `collectGreetings`, and `cowpy`) organized into separate module files, takes an input CSV file of greetings, and produces a whimsical ASCII art output file. +The workflow takes a CSV file containing greetings, runs four consecutive steps to transform the greetings, and writes them out as part of an ASCII picture. +These four steps are implemented as modularized Nextflow processes (`sayHello`, `convertToUpper`, `collectGreetings`, and `cowpy`) organized into separate module files. We provide you with a clean, fully functional copy of the completed Hello Nextflow workflow in the directory `original-hello` along with its modules and the default CSV file it expects to use as input. diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index 9d7ee1a2d7..b642a3be72 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -12,8 +12,6 @@ We'll apply three essential nf-core patterns incrementally: 2. **`ext.args`**: Keep the module interface minimal by handling optional tool arguments via configuration rather than as inputs 3. **`ext.prefix`**: Standardize output file naming with configurable prefixes -Once you understand these patterns, we'll show you how to use the official nf-core tooling to create modules efficiently. - !!! note This section assumes you have completed [Part 3: Use an nf-core module](./03_use_module.md) and have integrated the `CAT_CAT` module into your pipeline. @@ -160,24 +158,30 @@ executor > local (8) -[core/hello] Pipeline completed successfully- ``` -### 1.2. Simplify the interface with ext.args +### 1.2. Control module behavior via configuration -Now let's address another nf-core pattern: simplifying module interfaces by using `ext.args` for optional command-line arguments. +Currently, our `cowpy` module hardcodes two aspects of its behavior: -Currently, our `cowpy` module requires the `character` parameter to be passed as an input via the process `input:` block. -This works, but it forces us to provide a value for `character` every time we call the process, even if we're happy with a default. -For tools with many optional parameters, having to provide values for all of them gets annoying fast. +1. **Tool arguments**: The `character` parameter is passed as a process input, so we must provide a value every time we call the process +2. **Output publishing**: The module contains a `publishDir` directive, making publishing decisions at the module level -nf-core modules use a different approach for **tool configuration arguments**: instead of declaring inputs for every tool option, they use `ext.args` to pass these via configuration. -This keeps the module interface focused on essential data (files, metadata, and any mandatory per-sample parameters), while tool configuration options are handled through `ext.args`. +This makes the module less flexible. +For tool arguments, we're forced to provide values even when we'd be happy with defaults, which gets cumbersome for tools with many optional parameters. +For output publishing, we can't control where outputs go at the workflow level - each module makes its own publishing decisions. -#### 1.2.1. Understanding ext.args +nf-core modules handle both of these differently, controlling tool arguments and output publishing through configuration files. +This centralizes control at the workflow level and makes modules more reusable. -The `task.ext.args` pattern is an nf-core convention for passing command-line arguments to tools through configuration rather than as process inputs. +Let's update our module to follow both of these nf-core configuration patterns. -!!! note "ext.args can do more" +#### 1.2.1. Tool arguments with ext.args - The `ext.args` system has powerful additional capabilities not covered here, including switching argument values dynamically based on metadata. See the [nf-core module specifications](https://nf-co.re/docs/guidelines/components/modules) for more details. +For tool arguments, nf-core modules use a special configuration variable called `task.ext.args`. +Instead of declaring process inputs for every tool option, you write the module to reference `task.ext.args` in its command line. +This variable can be set in configuration files to pass arguments to the tool. +When you configure a module to use `task.ext.args`, it checks if the variable is defined and includes those arguments in the tool's command line. + +This approach keeps the module interface focused on essential data (files, metadata, and any mandatory per-sample parameters), while tool configuration options are handled separately through configuration. Benefits of this approach: @@ -187,9 +191,13 @@ Benefits of this approach: - **Portability**: Modules can be reused without hardcoded tool options - **No workflow changes**: Adding or changing tool options doesn't require updating workflow code +!!! note "ext.args can do more" + + The `ext.args` system has powerful additional capabilities not covered here, including switching argument values dynamically based on metadata. See the [nf-core module specifications](https://nf-co.re/docs/guidelines/components/modules) for more details. + #### 1.2.2. Centralized publishing configuration -Before we update the module to use `ext.args`, let's address an important nf-core convention: **modules should not contain hardcoded `publishDir` directives**. +For output publishing, nf-core pipelines centralize control at the workflow level by configuring `publishDir` in `conf/modules.config` rather than in individual modules. Currently, our `cowpy` module has `publishDir 'results', mode: 'copy'` which hardcodes the output location. In nf-core pipelines, publishing is instead configured in `conf/modules.config`. From c973b08e2ce8c4838d7397409348b5f2d8630be5 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Tue, 4 Nov 2025 12:06:01 +0000 Subject: [PATCH 089/113] Move publishDir configuration details to note box MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reorganize section 1.2.2 to present benefits first, then move the default publishDir configuration details into a note box with a link to nf-core documentation. This improves readability and flow. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/04_make_module.md | 34 ++++++++++++++++------------ 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index b642a3be72..527ccba70b 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -202,21 +202,6 @@ For output publishing, nf-core pipelines centralize control at the workflow leve Currently, our `cowpy` module has `publishDir 'results', mode: 'copy'` which hardcodes the output location. In nf-core pipelines, publishing is instead configured in `conf/modules.config`. -The nf-core template includes a **default publishDir configuration** that applies to all processes: - -```groovy -process { - publishDir = [ - path: { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] -} -``` - -This default automatically publishes outputs to `${params.outdir}//` for every process. -Individual processes can customize their publishing using `withName:` blocks in the same config file. - Benefits of this approach: - **Single source of truth**: All publishing configuration lives in `modules.config` @@ -224,6 +209,25 @@ Benefits of this approach: - **Easy customization**: Override publishing behavior in config, not in module code - **Portable modules**: Modules don't hardcode output locations +!!! note "Default publishDir configuration" + + The nf-core template includes a default publishDir configuration that applies to all processes: + + ```groovy + process { + publishDir = [ + path: { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } + ``` + + This default automatically publishes outputs to `${params.outdir}//` for every process. + Individual processes can customize their publishing using `withName:` blocks in the same config file. + + For more details, see the [nf-core modules specifications](https://nf-co.re/docs/guidelines/components/modules). + #### 1.2.3. Update the module Now let's update the cowpy module to use `ext.args` and remove the local `publishDir`. From 659e07599711a9ad8c079fc0e63d7713a6c091e1 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Tue, 4 Nov 2025 12:07:40 +0000 Subject: [PATCH 090/113] Restore backticks --- docs/hello_nf-core/04_make_module.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index 527ccba70b..18bd665a5e 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -211,7 +211,7 @@ Benefits of this approach: !!! note "Default publishDir configuration" - The nf-core template includes a default publishDir configuration that applies to all processes: + The nf-core template includes a default `publishDir` configuration that applies to all processes: ```groovy process { From e5301caa6a628454a4f5a343b1bf4ca7257b6e6e Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Tue, 4 Nov 2025 12:15:51 +0000 Subject: [PATCH 091/113] Improve publishDir configuration explanation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expand the note box to explain the three parts of the default publishDir configuration: path (with workflow hierarchy explanation), mode, and saveAs. Acknowledge the complexity upfront and provide concrete example of SAMTOOLS_SORT -> samtools/. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/04_make_module.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index 18bd665a5e..b156f97474 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -223,8 +223,13 @@ Benefits of this approach: } ``` - This default automatically publishes outputs to `${params.outdir}//` for every process. - Individual processes can customize their publishing using `withName:` blocks in the same config file. + This looks complicated, but it breaks down into three parts: + + - **path**: Determines the output directory based on the process name. When processes run, their full name includes the workflow hierarchy (like `CORE_HELLO:HELLO:SAMTOOLS_SORT`). The `tokenize` operations strip away that hierarchy to get just the process name, then take the first part before any underscore, and convert it to lowercase. So `SAMTOOLS_SORT` would publish to `${params.outdir}/samtools/`. + - **mode**: Controls how files are published (copy, symlink, etc.), configurable via the `params.publish_dir_mode` parameter. + - **saveAs**: Filters which files to publish. This example excludes `versions.yml` files by returning `null` for them, preventing them from being published. + + Individual processes can override this default using `withName:` blocks in the same config file. For more details, see the [nf-core modules specifications](https://nf-co.re/docs/guidelines/components/modules). From 4b4517d0d5487d754c02691a6e5215ce7a6eef81 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Tue, 4 Nov 2025 12:23:38 +0000 Subject: [PATCH 092/113] Minor editorial improvements to module creation section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change "understand" to "learned" for better flow - Replace "boilerplate" with clearer "extra code" - Expand resource label explanation to introduce the concept 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/04_make_module.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index b156f97474..d076fad8ab 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -592,7 +592,7 @@ However, you might want to keep it as a reference for understanding the differen ## 2. Use nf-core tooling to create modules -Now that you understand the nf-core module patterns by applying them manually, let's look at how you'd create modules in practice. +Now that you've learned the nf-core module patterns by applying them manually, let's look at how you'd create modules in practice. The nf-core project provides the `nf-core modules create` command that generates properly structured module templates with all these patterns built in from the start. ### 2.1. Using nf-core modules create @@ -605,14 +605,14 @@ For example, to create the `cowpy` module with a minimal template: nf-core modules create --empty-template cowpy ``` -The `--empty-template` flag creates a clean starter template without extra boilerplate, making it easier to see the essential structure. +The `--empty-template` flag creates a clean starter template without extra code, making it easier to see the essential structure. The command runs interactively, guiding you through the setup. It automatically looks up tool information from package repositories like Bioconda and bio.tools to pre-populate metadata. -You'll be prompted for: +You'll be prompted for several configuration options: - **Author information**: Your GitHub username for attribution -- **Resource label**: The computational requirements (e.g., `process_single` for lightweight tools, `process_high` for demanding ones) +- **Resource label**: A predefined set of computational requirements. nf-core provides standard labels like `process_single` for lightweight tools and `process_high` for demanding ones. These labels help manage resource allocation across different execution environments. - **Metadata requirement**: Whether the module needs sample-specific information via a `meta` map (usually yes for data processing modules) The tool handles the complexity of finding package information and setting up the structure, allowing you to focus on implementing the tool's specific logic. @@ -771,7 +771,7 @@ For detailed instructions, see the [nf-core components tutorial](https://nf-co.r ## Takeaway -You now know how to create nf-core modules! You learned the three key patterns that make modules portable and maintainable: +You now know how to create nf-core modules! You learned the four key patterns that make modules portable and maintainable: - **Metadata tuples** track sample information through the workflow - **`ext.args`** simplifies module interfaces by handling optional arguments via configuration From f8b57c98d12c371f082e0bb68cf9ffee869312be Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Tue, 4 Nov 2025 12:45:26 +0000 Subject: [PATCH 093/113] Clarify module template customization requirements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses PR feedback about confusion around which parts of the generated module template need customization vs work as-is. Changes: - Split features in section 2.1.1 into "work as-is" vs "placeholders to customize" - Added three new sections (2.1.3-2.1.5) with before/after examples showing how to: - Define inputs and outputs for the specific tool - Write the script block with actual tool command - Implement the stub block to match script outputs - Referenced manual module work from section 1 for consistency - Clarified that features like tag, label, and when block are already functional 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/04_make_module.md | 145 +++++++++++++++++++++++++-- 1 file changed, 139 insertions(+), 6 deletions(-) diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index d076fad8ab..bfaaedd26a 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -695,16 +695,25 @@ process COWPY { ``` Notice how all three patterns you applied manually are already there! -The template also includes several additional nf-core conventions that we didn't cover in the hands-on section: +The template also includes several additional nf-core conventions. +Some of these work out of the box, while others are placeholders we'll need to fill in: + +**Features that work as-is:** - **`tag "$meta.id"`**: Adds sample ID to process names in logs for easier tracking - **`label 'process_single'`**: Resource label for configuring CPU/memory requirements - **`when:` block**: Allows conditional execution via `task.ext.when` configuration -- **`stub:` block**: Provides a fast mock implementation for testing pipeline logic without running the actual tool -- **`versions.yml` output**: Captures software version information for reproducibility -- **Container placeholders**: Template structure for Singularity and Docker containers -These additional conventions make modules more maintainable and provide better visibility into pipeline execution. +These features are already functional and make modules more maintainable. + +**Placeholders we'll customize below:** + +- **`input:` and `output:` blocks**: Generic declarations we'll update to match our tool +- **`script:` block**: Contains a comment where we'll add the cowpy command +- **`stub:` block**: Template we'll update to produce the correct outputs +- **Container and environment**: Placeholders we'll fill with package information + +The next sections walk through completing these customizations. #### 2.1.2. Completing the environment and container setup @@ -732,7 +741,131 @@ container "community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273" Most bioinformatics tools are in Bioconda, but for conda-forge tools, Seqera Containers provides an easy solution for containerization. -Once you've completed the environment setup and filled in the command logic, the module is ready to test! +#### 2.1.3. Defining inputs and outputs + +The generated template includes generic input and output declarations that you'll need to customize for your specific tool. +Looking back at our manual cowpy module from section 1, we can use that as a guide. + +Update the input and output blocks: + +=== "After" + + ```groovy title="modules/local/cowpy/main.nf" linenums="8" hl_lines="2 5" + input: + tuple val(meta), path(input_file) + + output: + tuple val(meta), path("${prefix}.txt"), emit: cowpy_output + path "versions.yml" , emit: versions + ``` + +=== "Before" + + ```groovy title="modules/local/cowpy/main.nf" linenums="8" hl_lines="2 5" + input: + tuple val(meta), path(input) + + output: + tuple val(meta), path("*"), emit: output + path "versions.yml" , emit: versions + ``` + +This specifies: +- The input file parameter name (`input_file` instead of generic `input`) +- The output filename using the configurable prefix pattern (`${prefix}.txt` instead of wildcard `*`) +- A descriptive emit name (`cowpy_output` instead of generic `output`) + +#### 2.1.4. Writing the script block + +The template provides a comment placeholder where you add the actual tool command. +We can reference our manual module from section 1.3.2 for the command logic: + +=== "After" + + ```groovy title="modules/local/cowpy/main.nf" linenums="15" hl_lines="3 6" + script: + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + + """ + cat $input_file | cowpy $args > ${prefix}.txt + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + cowpy: \$(cowpy --version) + END_VERSIONS + """ + ``` + +=== "Before" + + ```groovy title="modules/local/cowpy/main.nf" linenums="15" hl_lines="6" + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + // Add your tool command here + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + cowpy: \$(cowpy --version) + END_VERSIONS + """ + ``` + +Key changes: +- Change `def prefix` to just `prefix` (without `def`) so it's accessible in the output block +- Replace the comment with the actual cowpy command that uses both `$args` and `${prefix}.txt` + +#### 2.1.5. Implementing the stub block + +The stub block provides a fast mock implementation for testing pipeline logic without running the actual tool. +It must produce the same output files as the script block: + +=== "After" + + ```groovy title="modules/local/cowpy/main.nf" linenums="27" hl_lines="3 5" + stub: + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + + """ + touch ${prefix}.txt + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + cowpy: \$(cowpy --version) + END_VERSIONS + """ + ``` + +=== "Before" + + ```groovy title="modules/local/cowpy/main.nf" linenums="27" hl_lines="5-6" + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + echo $args + touch ${prefix}.txt + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + cowpy: \$(cowpy --version) + END_VERSIONS + """ + ``` + +Key changes: +- Change `def prefix` to just `prefix` to match the script block +- Remove the `echo $args` line (which was just template placeholder code) +- The stub creates an empty `${prefix}.txt` file matching what the script block produces + +This allows you to test workflow logic and file handling without waiting for the actual tool to run. + +Once you've completed the environment setup (section 2.1.2), inputs/outputs (section 2.1.3), script block (section 2.1.4), and stub block (section 2.1.5), the module is ready to test! ### 2.2. Contributing modules back to nf-core From 38e35502212dcc4c7dbef3a994b6d7d65e8e99f9 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Tue, 4 Nov 2025 12:53:05 +0000 Subject: [PATCH 094/113] Restructure validation intro and reduce overconfident claims MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses PR feedback about section structure and overconfident language. Changes: - Moved intro material (sections 1.1-1.3) to top-level overview before numbered sections - Moved configuration step (old 1.4) into section 1 as first practical step before examining schema - Renumbered all sections (old section 2 → 1, old section 3 → 2) - Updated takeaways to be less presumptuous: "You've learned" instead of "You now know", "seen in action" instead of "know how" - Removed abrupt section ending - section 1 now flows naturally from config to schema examination to adding parameters The structure now feels more complete with each section having clear practical outcomes rather than ending on pure exposition. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/05_input_validation.md | 134 ++++++++++------------ 1 file changed, 59 insertions(+), 75 deletions(-) diff --git a/docs/hello_nf-core/05_input_validation.md b/docs/hello_nf-core/05_input_validation.md index 8f9480954f..976a5e1b6a 100644 --- a/docs/hello_nf-core/05_input_validation.md +++ b/docs/hello_nf-core/05_input_validation.md @@ -32,49 +32,10 @@ Pipeline failed before execution - please fix the errors above The pipeline fails immediately with clear, actionable error messages. This saves time, compute resources, and frustration. -## Two types of validation +## The nf-schema plugin -nf-core pipelines validate two different kinds of input: - -### Parameter validation - -This validates command-line parameters (flags like `--outdir`, `--batch`, `--input`): - -- Checks parameter types, ranges, and formats -- Ensures required parameters are provided -- Validates file paths exist -- Defined in `nextflow_schema.json` - -### Input data validation - -This validates the contents of input files (like sample sheets or CSV files) - -- Checks column structure and data types -- Validates file references within the input file -- Ensures required fields are present -- Defined in `assets/schema_input.json` - -!!! note - - This section assumes you have completed [Part 4: Make an nf-core module](./04_make_module.md) and have a working `core-hello` pipeline with nf-core-style modules. - - If you didn't complete Part 4 or want to start fresh for this section, you can use the `core-hello-part4` solution as your starting point: - - ```bash - cp -r hello-nf-core/solutions/core-hello-part4 core-hello - cd core-hello - ``` - - This gives you a fully functional nf-core pipeline with modules ready for adding input validation. - ---- - -## 1. The nf-schema plugin - -The [nf-schema plugin](https://nextflow-io.github.io/nf-schema/latest/) is a Nextflow plugin that provides comprehensive validation capabilities for any Nextflow pipeline. -While nf-schema is a standalone tool that can be used in any Nextflow workflow, it's heavily integrated into the nf-core ecosystem and is the standard validation solution for all nf-core pipelines. - -### 1.1. Core functionality +The [nf-schema plugin](https://nextflow-io.github.io/nf-schema/latest/) is a Nextflow plugin that provides comprehensive validation capabilities for Nextflow pipelines. +While nf-schema works with any Nextflow workflow, it's the standard validation solution for all nf-core pipelines. nf-schema provides several key functions: @@ -86,7 +47,7 @@ nf-schema provides several key functions: nf-schema is the successor to the deprecated nf-validation plugin and uses standard [JSON Schema Draft 2020-12](https://json-schema.org/) for validation. -### 1.2. The two schema files +## Two schema files An nf-core pipeline uses two schema files for validation: @@ -97,7 +58,25 @@ An nf-core pipeline uses two schema files for validation: Both schemas use JSON Schema format, a widely-adopted standard for describing and validating data structures. -### 1.3. When validation occurs +### Two types of validation + +nf-core pipelines validate two different kinds of input: + +**Parameter validation** validates command-line parameters (flags like `--outdir`, `--batch`, `--input`): + +- Checks parameter types, ranges, and formats +- Ensures required parameters are provided +- Validates file paths exist +- Defined in `nextflow_schema.json` + +**Input data validation** validates the contents of input files (like sample sheets or CSV files): + +- Checks column structure and data types +- Validates file references within the input file +- Ensures required fields are present +- Defined in `assets/schema_input.json` + +### When validation occurs ```mermaid graph LR @@ -110,7 +89,26 @@ graph LR Validation happens **before** any pipeline processes run, providing fast feedback and preventing wasted compute time. -### 1.4. Configure validation to skip input file validation +!!! note + + This section assumes you have completed [Part 4: Make an nf-core module](./04_make_module.md) and have a working `core-hello` pipeline with nf-core-style modules. + + If you didn't complete Part 4 or want to start fresh for this section, you can use the `core-hello-part4` solution as your starting point: + + ```bash + cp -r hello-nf-core/solutions/core-hello-part4 core-hello + cd core-hello + ``` + + This gives you a fully functional nf-core pipeline with modules ready for adding input validation. + +--- + +## 1. Parameter validation (nextflow_schema.json) + +Let's start by adding parameter validation to our pipeline. This validates command-line flags like `--input`, `--outdir`, and `--batch`. + +### 1.1. Configure validation to skip input file validation The nf-core pipeline template comes with nf-schema already installed and configured: @@ -120,7 +118,7 @@ The nf-core pipeline template comes with nf-schema already installed and configu The validation behavior is controlled through the `validation{}` scope in `nextflow.config`. -Since we'll be working on parameter validation first (section 2) and won't configure the input data schema until section 3, we need to temporarily tell nf-schema to skip validating the `input` parameter's file contents. +Since we'll be working on parameter validation first (this section) and won't configure the input data schema until section 2, we need to temporarily tell nf-schema to skip validating the `input` parameter's file contents. Open `nextflow.config` and find the `validation` block (around line 246). Add `ignoreParams` to skip input file validation: @@ -146,30 +144,16 @@ Open `nextflow.config` and find the `validation` block (around line 246). Add `i This configuration tells nf-schema to: - **`defaultIgnoreParams`**: Skip validation of complex parameters like `genomes` (set by template developers) -- **`ignoreParams`**: Skip validation of the `input` parameter's file contents (temporary - we'll remove this in section 3) +- **`ignoreParams`**: Skip validation of the `input` parameter's file contents (temporary - we'll remove this in section 2) - **`monochromeLogs`**: Control colored output in validation messages !!! note "Why ignore the input parameter?" The `input` parameter in `nextflow_schema.json` has `"schema": "assets/schema_input.json"` which tells nf-schema to validate the *contents* of the input CSV file against that schema. Since we haven't configured that schema yet, we temporarily ignore this validation. - We'll remove this setting in section 3 after configuring the input data schema. - -### Takeaway - -You now understand what nf-schema does, the two types of validation it provides, when validation occurs, and how to configure validation behavior. You've also temporarily disabled input file validation so we can focus on parameter validation first. - -### What's next? - -Start by implementing parameter validation for command-line flags. - ---- - -## 2. Parameter validation (nextflow_schema.json) - -Let's start by adding parameter validation to our pipeline. This validates command-line flags like `--input`, `--outdir`, and `--batch`. + We'll remove this setting in section 2 after configuring the input data schema. -### 2.1. Examine the parameter schema +### 1.2. Examine the parameter schema Let's look at a section of the `nextflow_schema.json` file that came with our pipeline template: @@ -225,7 +209,7 @@ Key validation features: Notice the `batch` parameter we've been using isn't defined yet in the schema! -### 2.2. Add the batch parameter +### 1.3. Add the batch parameter While the schema is a JSON file that can be edited manually, **manual editing is error-prone and not recommended**. Instead, nf-core provides an interactive GUI tool that handles the JSON Schema syntax for you and validates your changes: @@ -317,7 +301,7 @@ grep -A 25 '"input_output_options"' nextflow_schema.json You should see that the `batch` parameter has been added to the schema with the "required" field now showing `["input", "outdir", "batch"]`. -### 2.3. Test parameter validation +### 1.4. Test parameter validation Now let's test that parameter validation works correctly. @@ -348,7 +332,7 @@ The pipeline should run successfully, and the `batch` parameter is now validated ### Takeaway -You now know how to use the interactive `nf-core pipelines schema build` tool to add parameters to `nextflow_schema.json` and test parameter validation. +You've learned how to use the interactive `nf-core pipelines schema build` tool to add parameters to `nextflow_schema.json` and seen parameter validation in action. The web interface handles all the JSON Schema syntax for you, making it easy to manage complex parameter schemas without error-prone manual JSON editing. ### What's next? @@ -357,11 +341,11 @@ Now that parameter validation is working, let's add validation for the input dat --- -## 3. Input data validation (schema_input.json) +## 2. Input data validation (schema_input.json) Now let's add validation for the contents of our input CSV file. While parameter validation checks command-line flags, input data validation ensures the data inside the CSV file is structured correctly. -### 3.1. Understand the greetings.csv format +### 2.1. Understand the greetings.csv format Let's remind ourselves what our input looks like: @@ -381,7 +365,7 @@ This is a simple CSV with: - One greeting per line - Text strings with no special format requirements -### 3.2. Design the schema structure +### 2.2. Design the schema structure For our use case, we want to: @@ -392,7 +376,7 @@ For our use case, we want to: We'll structure this as an array of objects, where each object has a `greeting` field. -### 3.3. Update the schema file +### 2.3. Update the schema file The nf-core pipeline template includes a default `assets/schema_input.json` designed for paired-end sequencing data. We need to replace it with a simpler schema for our greetings use case. @@ -469,7 +453,7 @@ The key changes: - **`errorMessage`**: Custom error message shown if validation fails - **`required`**: Changed from `["sample", "fastq_1"]` to `["greeting"]` -### 3.4. Add a header to the greetings.csv file +### 2.4. Add a header to the greetings.csv file When nf-schema reads a CSV file, it expects the first row to contain column headers that match the field names in the schema. @@ -504,7 +488,7 @@ You've created a JSON schema for the greetings input file and added the required Implement the validation in the pipeline code using `samplesheetToList`. -### 3.5. Implement samplesheetToList in the pipeline +### 2.5. Implement samplesheetToList in the pipeline Now we need to replace our simple CSV parsing with nf-schema's `samplesheetToList` function, which validates and converts the sample sheet. @@ -609,7 +593,7 @@ You've successfully implemented input data validation using `samplesheetToList` Re-enable input validation in the config and test both parameter and input data validation to see them in action. -### 3.6. Re-enable input validation +### 2.6. Re-enable input validation Now that we've configured the input data schema, we can remove the temporary ignore setting we added in section 1.4. @@ -636,7 +620,7 @@ Open `nextflow.config` and remove the `ignoreParams` line from the `validation` Now nf-schema will validate both parameter types AND the input file contents. -### 3.7. Test input validation +### 2.7. Test input validation Let's verify that our validation works by testing both valid and invalid inputs. @@ -713,7 +697,7 @@ The schema validation ensures that input files have the correct structure before ### Takeaway -You now know how to implement and test both parameter validation and input data validation. Your pipeline validates inputs before execution, providing fast feedback and clear error messages. +You've implemented and tested both parameter validation and input data validation. Your pipeline now validates inputs before execution, providing fast feedback and clear error messages. !!! tip "Further reading" From 35a281d4569296852414e97a8c7a50913a0bb0a9 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Tue, 4 Nov 2025 12:55:06 +0000 Subject: [PATCH 095/113] Clarify monochromeLogs parameter description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous description "Control colored output" was vague. Changed to "Disable colored output in validation messages when set to true" to be more specific about what the parameter does. Addresses PR feedback about vague wording. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/05_input_validation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hello_nf-core/05_input_validation.md b/docs/hello_nf-core/05_input_validation.md index 976a5e1b6a..d277bda037 100644 --- a/docs/hello_nf-core/05_input_validation.md +++ b/docs/hello_nf-core/05_input_validation.md @@ -145,7 +145,7 @@ This configuration tells nf-schema to: - **`defaultIgnoreParams`**: Skip validation of complex parameters like `genomes` (set by template developers) - **`ignoreParams`**: Skip validation of the `input` parameter's file contents (temporary - we'll remove this in section 2) -- **`monochromeLogs`**: Control colored output in validation messages +- **`monochromeLogs`**: Disable colored output in validation messages when set to `true` (controlled by `params.monochrome_logs`) !!! note "Why ignore the input parameter?" From 31273b7997b3b631330aa2bfea53eeab1a725861 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Tue, 4 Nov 2025 12:56:12 +0000 Subject: [PATCH 096/113] Clarify input data validation scope and monochromeLogs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses two PR feedback items: 1. Clarified that input data validation is for sample sheet/manifest structure, NOT for validating contents of large data files (BAM, FASTQ, etc.). Added note explaining that data file validation should happen in processes on worker nodes for large-scale genomics. 2. Made monochromeLogs description more specific: "Disable colored output in validation messages when set to true" instead of vague "Control colored output" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/05_input_validation.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/hello_nf-core/05_input_validation.md b/docs/hello_nf-core/05_input_validation.md index d277bda037..ee5ec7e155 100644 --- a/docs/hello_nf-core/05_input_validation.md +++ b/docs/hello_nf-core/05_input_validation.md @@ -69,13 +69,19 @@ nf-core pipelines validate two different kinds of input: - Validates file paths exist - Defined in `nextflow_schema.json` -**Input data validation** validates the contents of input files (like sample sheets or CSV files): +**Input data validation** validates the structure of sample sheets and manifest files (CSV/TSV files that describe your data): - Checks column structure and data types -- Validates file references within the input file +- Validates that file paths referenced in the sample sheet exist - Ensures required fields are present - Defined in `assets/schema_input.json` +!!! note "What input data validation does NOT do" + + Input data validation checks the structure of *manifest files* (sample sheets, CSV files), not the contents of your actual data files (FASTQ, BAM, VCF, etc.). + + For large-scale data, validating file contents (like checking BAM integrity) should happen in pipeline processes running on worker nodes, not during the validation stage on the orchestrating machine. + ### When validation occurs ```mermaid From 35b51fbf379d0a4a6c518946478a03f8eee26f99 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Tue, 4 Nov 2025 12:57:58 +0000 Subject: [PATCH 097/113] Make takeaway bullet more explicit about publishDir MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed "Centralized configuration" to "Centralized publishing via publishDir configured in modules.config rather than hardcoded in modules" to be explicit about what this pattern covers. Addresses PR feedback that the connection wasn't clear. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/04_make_module.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index bfaaedd26a..50dd71356f 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -909,7 +909,7 @@ You now know how to create nf-core modules! You learned the four key patterns th - **Metadata tuples** track sample information through the workflow - **`ext.args`** simplifies module interfaces by handling optional arguments via configuration - **`ext.prefix`** standardizes output file naming -- **Centralized configuration** in `modules.config` keeps modules reusable +- **Centralized publishing** via `publishDir` configured in `modules.config` rather than hardcoded in modules By transforming `cowpy` step-by-step, you developed a deep understanding of these patterns, making you equipped to work with, debug, and create nf-core modules. In practice, you'll use `nf-core modules create` to generate properly structured modules with these patterns built in from the start. From 43f75d97c66ec9478332965c16bd8ed17d74169f Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Tue, 4 Nov 2025 13:00:53 +0000 Subject: [PATCH 098/113] Prioritize visual output verification over command inspection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed testing section to check visual output first (faster and more natural for workshops), with command file inspection as an optional follow-up for those who want to see implementation details. Addresses PR feedback about making workshop experience more efficient. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/04_make_module.md | 38 +++++++++++++++------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index 50dd71356f..e2719728c0 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -379,23 +379,7 @@ The pipeline should run successfully. In the output, look for the cowpy process [bd/0abaf8] CORE_HELLO:HELLO:cowpy [100%] 1 of 1 ✔ ``` -Now let's verify that the `ext.args` configuration actually passed the character argument to the cowpy command. Use the task hash (the `bd/0abaf8` part) to inspect the `.command.sh` file in the work directory: - -```bash -cat work/bd/0abaf8*/.command.sh -``` - -You should see the cowpy command with the `-c cow` argument: - -```console title="Output" -#!/usr/bin/env bash -... -cat test.txt | cowpy -c kosh > cowpy-test.txt -``` - -This confirms that `task.ext.args` successfully passed the character parameter through the configuration rather than requiring it as a process input. - -We can also check the output: +Let's verify that the `ext.args` configuration worked by checking the output. Use the task hash (the `bd/0abaf8` part) to look at the output file: ```bash cat work/bd/0abaf8*/cowpy-test.txt @@ -423,6 +407,26 @@ cat work/bd/0abaf8*/cowpy-test.txt | | ``` +You should see the ASCII art displayed with the kosh character, confirming that the `ext.args` configuration worked! + +!!! note "Optional: Inspect the command file" + + If you want to see exactly how the configuration was applied, you can inspect the `.command.sh` file: + + ```bash + cat work/bd/0abaf8*/.command.sh + ``` + + You'll see the cowpy command with the `-c kosh` argument: + + ```console + #!/usr/bin/env bash + ... + cat test.txt | cowpy -c kosh > cowpy-test.txt + ``` + + This shows that the `.command.sh` file was generated correctly based on the `ext.args` configuration. + ### 1.3. Add configurable output naming with ext.prefix There's one more nf-core pattern we can apply: using `ext.prefix` for configurable output file naming. From 1476787fb9ae2e70169e95268559e75d7e1fba66 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Tue, 4 Nov 2025 13:05:23 +0000 Subject: [PATCH 099/113] Change section heading to avoid overloaded 'template' term MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed "Use nf-core tooling to create modules" to "Generate modules with nf-core tools" to avoid confusion between pipeline templates and module templates. Addresses: https://github.com/nextflow-io/training/pull/691#discussion_r2488029278 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/hello_nf-core/04_make_module.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index e2719728c0..38058f24fe 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -594,7 +594,7 @@ However, you might want to keep it as a reference for understanding the differen --- -## 2. Use nf-core tooling to create modules +## 2. Generate modules with nf-core tools Now that you've learned the nf-core module patterns by applying them manually, let's look at how you'd create modules in practice. The nf-core project provides the `nf-core modules create` command that generates properly structured module templates with all these patterns built in from the start. From bb96228748a30c0389eada31f2e0de30b140f983 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Tue, 4 Nov 2025 13:20:04 +0000 Subject: [PATCH 100/113] prettier --- docs/hello_nf-core/04_make_module.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index 38058f24fe..6a30c4a7df 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -775,6 +775,7 @@ Update the input and output blocks: ``` This specifies: + - The input file parameter name (`input_file` instead of generic `input`) - The output filename using the configurable prefix pattern (`${prefix}.txt` instead of wildcard `*`) - A descriptive emit name (`cowpy_output` instead of generic `output`) @@ -819,6 +820,7 @@ We can reference our manual module from section 1.3.2 for the command logic: ``` Key changes: + - Change `def prefix` to just `prefix` (without `def`) so it's accessible in the output block - Replace the comment with the actual cowpy command that uses both `$args` and `${prefix}.txt` @@ -863,6 +865,7 @@ It must produce the same output files as the script block: ``` Key changes: + - Change `def prefix` to just `prefix` to match the script block - Remove the `echo $args` line (which was just template placeholder code) - The stub creates an empty `${prefix}.txt` file matching what the script block produces From 464fc0cdaa34aaca17b3b2af5598ece6bc1466f0 Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Tue, 4 Nov 2025 13:51:59 +0000 Subject: [PATCH 101/113] Correct a highlight --- docs/hello_nf-core/04_make_module.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index 6a30c4a7df..b2f38ebff8 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -831,7 +831,7 @@ It must produce the same output files as the script block: === "After" - ```groovy title="modules/local/cowpy/main.nf" linenums="27" hl_lines="3 5" + ```groovy title="modules/local/cowpy/main.nf" linenums="27" hl_lines="3 6" stub: def args = task.ext.args ?: '' prefix = task.ext.prefix ?: "${meta.id}" From c0e1f41d90ceee0244c9489c9d4a30d7209a9c9e Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Thu, 6 Nov 2025 10:13:03 -0500 Subject: [PATCH 102/113] Move "What you'll learn" content to index page --- docs/hello_nf-core/00_orientation.md | 23 ---------- docs/hello_nf-core/index.md | 64 ++++++++++++++++++++++------ 2 files changed, 51 insertions(+), 36 deletions(-) diff --git a/docs/hello_nf-core/00_orientation.md b/docs/hello_nf-core/00_orientation.md index 80c8a68f01..5203d148e7 100644 --- a/docs/hello_nf-core/00_orientation.md +++ b/docs/hello_nf-core/00_orientation.md @@ -33,29 +33,6 @@ cd hello-nf-core/ Now let's have a look at the contents of this directory. -## What you'll learn - -This training course teaches you the core concepts for building nf-core-style pipelines. -We can't cover everything. nf-core pipelines have many features and conventions developed by the community over years. However, we focus on the essential concepts that will help you get started and understand how nf-core works. - -First, you'll **run an existing nf-core pipeline** to see what makes them different from basic Nextflow workflows. -The extensive directory structure, configuration system, and standardized conventions might seem overwhelming at first, but understanding them is essential for working within the template. - -Next, you'll **adapt a simple workflow to the nf-core template scaffold**. -Many pipeline development efforts start from existing code, so learning how to restructure workflows to work within nf-core's nested workflow system is a practical skill you'll use repeatedly. - -Then you'll discover one of nf-core's biggest advantages: the **community modules library**. -Instead of writing every process from scratch, you'll learn to integrate pre-built, tested modules that wrap common bioinformatics tools. -This approach saves time and ensures consistency across pipelines. - -Of course, the modules library doesn't have everything, so you'll **create your own nf-core-style module**. -You'll learn the specific structure, naming conventions, and metadata requirements that make modules shareable and maintainable by the community. - -Finally, you'll implement **comprehensive validation** for both command-line parameters and input data files using nf-schema. -This catches errors before pipelines run, providing fast feedback and clear error messages. This type of upfront validation makes pipelines more robust and easier to use. - -By the end, you'll have transformed a basic Nextflow workflow into an nf-core-style pipeline with standardized structure, reusable components, and robust validation. - ## Materials provided You can explore the contents of this directory by using the file explorer on the left-hand side of the training workspace. diff --git a/docs/hello_nf-core/index.md b/docs/hello_nf-core/index.md index 529a0a9c41..52657c88ce 100644 --- a/docs/hello_nf-core/index.md +++ b/docs/hello_nf-core/index.md @@ -10,23 +10,30 @@ hide: ![nf-core logo](./img/nf-core-logo.png) -These pipelines are designed to be modular, scalable, and portable, allowing researchers to easily adapt and execute them using their own data and compute resources. -The best practices guidelines enforced by the project further ensure that the pipelines are robust, well-documented, and validated against real-world datasets. This helps to increase the reliability and reproducibility of scientific analyses and ultimately enables researchers to accelerate their scientific discoveries. +The pipelines developed by the nf-core community are designed to be modular, scalable, and portable, allowing researchers to easily adapt and execute them using their own data and compute resources. +The best practices guidelines enforced by the project further ensure that the pipelines are robust, well-documented, and validated against real-world datasets. +This helps to increase the reliability and reproducibility of scientific analyses and ultimately enables researchers to accelerate their scientific discoveries. -During this training, you will be introduced to nf-core in a series of hands-on exercises. +During this training, you will be introduced to nf-core in a series of hands-on exercises as described further below. **Additional information:** You can learn more about the project's origins and governance at https://nf-co.re/about. **Reference publication:** nf-core is published in Nature Biotechnology: [Nat Biotechnol 38, 276–278 (2020). Nature Biotechnology](https://www.nature.com/articles/s41587-020-0439-x). An updated preprint is available at [bioRxiv](https://www.biorxiv.org/content/10.1101/2024.05.10.592912v1). -**Let's get started!** Click on the "Open in GitHub Codespaces" button below to launch the training environment (preferably in a separate tab), then read on while it loads. +## Audience & prerequisites + +This training is intended for learners who have at least basic Nextflow skills and wish to level up to using nf-core resources and best practices in their work. + +**Prerequisites** -[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/nextflow-io/training?quickstart=1&ref=master) +- A GitHub account OR a local installation as described [here](../envsetup/02_local). +- Experience with command line and basic scripting. +- Completed the [Hello Nextflow](../hello_nextflow/index.md) course or equivalent. ## Learning objectives -You will learn to use and develop nf-core compatible modules and pipelines, and utilize nf-core tooling effectively. +You will learn to use and develop nf-core compatible modules and pipelines, and to utilize nf-core tooling effectively. By the end of this training, you will be able to: @@ -34,15 +41,46 @@ By the end of this training, you will be able to: - Describe the code structure and project organization of nf-core pipelines - Create a basic nf-core compatible pipeline from a template - Convert basic Nextflow modules to nf-core compatible modules -- Manage inputs and parameters using nf-core tooling - Add nf-core modules to an nf-core compatible pipeline +- Validate inputs and parameters using nf-core tooling -## Audience & prerequisites +## Detailed lesson plan -This is a general-purpose training for learners who have at least basic Nextflow skills and wish to level up to using nf-core. +This training course aims to teach you the core concepts for running nf-core-style pipelines. +We won't cover everything there is to know about nf-core pipelines, because nf-core encompasses many features and conventions developed by the community over years. +Instead, we will focus on the essential concepts that will help you get started and understand how nf-core works. -**Prerequisites** +### Part 1: Run a demo pipeline -- A GitHub account OR a local installation as described [here](../envsetup/02_local). -- Experience with command line and basic scripting. -- Completed [Hello Nextflow](../hello_nextflow/index.md) or equivalent. +First, you'll **run an existing nf-core pipeline** and examine its code structure to get a sense of what makes these pipelines different from basic Nextflow workflows. +The elaborate directory structure, configuration system, and standardized conventions might seem like a lot at first, but the benefits will become clear as you learn to decode and utilize these resources effectively. + +### Part 2: Rewrite Hello for nf-core + +Next, you'll **adapt an existing workflow to the nf-core template scaffold**, starting from the simple workflow produced in the [Hello Nextflow](../hello_nextflow/index.md) course. +Many pipeline development efforts start from existing code, so learning how to restructure an existing workflow to leverage nf-core's nested workflow system is a practical skill you're likely to use repeatedly in your work. + +### Part 3: Use an nf-core module + +Then you'll discover one of nf-core's biggest advantages: the **community modules library**. +Instead of writing every process from scratch, you'll learn to integrate pre-built, tested modules that wrap common bioinformatics tools. +This approach saves time and ensures consistency across pipelines. + +### Part 4: Make an nf-core module + +Of course, the modules library doesn't have everything, so you'll also learn to **create your own nf-core-style module**. +You'll learn to work with the specific structure, naming conventions, and metadata requirements that make modules shareable and maintainable by the community. + +### Part 5: Add input validation + +Finally, you'll implement **input validation** for both command-line parameters and input data files using nf-schema. +This catches errors before pipelines start to run, providing fast feedback and clear error messages. This type of upfront validation makes pipelines more robust and easier to use. + +By the end of the course, you'll have transformed a basic Nextflow workflow into an nf-core-style pipeline with standardized structure, reusable components, and robust validation. + +## Getting started + +Ready to go? +Launch the training environment in a separate browser tab or window using the "Open in GitHub Codespaces" button below, then navigate to the next section and read on while the environment loads. + +[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/nextflow-io/training?quickstart=1&ref=master){:target="\_blank"} From 0a4d306702f263384afe4a4ab45eaa3d84298caf Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Thu, 6 Nov 2025 11:03:53 -0500 Subject: [PATCH 103/113] Improvements to the Orientation page If we like this new version we can implement it for all the course orientation pages that apply --- docs/hello_nf-core/00_orientation.md | 54 +++++++++++++++++++++------- docs/hello_nf-core/index.md | 19 +++++----- 2 files changed, 49 insertions(+), 24 deletions(-) diff --git a/docs/hello_nf-core/00_orientation.md b/docs/hello_nf-core/00_orientation.md index 5203d148e7..4239d9cb63 100644 --- a/docs/hello_nf-core/00_orientation.md +++ b/docs/hello_nf-core/00_orientation.md @@ -1,21 +1,34 @@ -# Orientation +# Getting started -## GitHub Codespaces +To start the course, launch the training environment by clicking the "Open in GitHub Codespaces" button below. +We recommend opening the training environment in a new browser tab (use right-click, ctrl-click or cmd-click depending on your equipment) so that you can read on while the environment loads. +You will need to keep these instructions open in parallel. -The GitHub Codespaces environment contains all the software, code and data necessary to work through this training course, so you don't need to install anything yourself. -However, you do need a (free) GitHub account to log in, and you should take a few minutes to familiarize yourself with the interface. +[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/nextflow-io/training?quickstart=1&ref=master) -If you have not yet done so, please go through the [Environment Setup](../../envsetup/) mini-course before going any further. +## Training environment + +Our training environment runs on GitHub Codespaces (free Github account required) and contains all the software, code and data necessary to work through this training course, so you don't need to install anything yourself. + +The codespace is set up with a VSCode interface, which includes a filesystem explorer, a code editor and a terminal shell. +All instructions given during the course (e.g. 'open the file', 'edit the code' or 'run this command') refer to those three parts of the VScode interface unless otherwise specified. + +If you are working through this course by yourself, please go through the [Environment Setup](../../envsetup/) mini-course for further details before going any further. !!! warning - This training is designed for nf-core tools version 3.4.1, which should be the version installed in the codespace. If you use a different version of nf-core tooling you may have difficulty following along. + This training is designed for nf-core tools version 3.4.1, which should be the version installed in the codespace we provide. + If you use a different version of nf-core tooling, you may have difficulty following along. You can check what version is installed using the command`nf-core --version`. -## Working directory +## Get ready to work! -Throughout this training course, we'll be working in the `hello-nf-core/` directory. +Once your codespace is running, there are two things you need to do before diving into the training: set your working directory for this specific course, and take a look at the materials provided. + +### Set the working directory + +By default, the codespace opens with the work directory set at the root of all training courses, but for this course, we'll be working in the `hello-nf-core/` directory. Change directory now by running this command in the terminal: @@ -25,7 +38,7 @@ cd hello-nf-core/ !!! tip - If for whatever reason you move out of this directory, you can always use the full path to return to it, assuming you're running this within the Github Codespaces training environment: + If for whatever reason you move out of this directory (e.g. your codespace goes to sleep), you can always use the full path to return to it, assuming you're running this within the Github Codespaces training environment: ```bash cd /workspaces/training/hello-nf-core @@ -33,7 +46,7 @@ cd hello-nf-core/ Now let's have a look at the contents of this directory. -## Materials provided +### Materials provided You can explore the contents of this directory by using the file explorer on the left-hand side of the training workspace. Alternatively, you can use the `tree` command. @@ -46,7 +59,7 @@ Here we generate a table of contents to the second level down: tree . -L 2 ``` -If you run this inside `hello-nf-core`, you should see the following output: +If you run this inside `hello-nf-core`, you should see the following output. ??? example "Directory contents" @@ -67,7 +80,12 @@ If you run this inside `hello-nf-core`, you should see the following output: 8 directories, 3 files ``` -**Here's a summary of what you should know to get started:** +!!! note + + We use collapsible sections like this to include expected command output in a concise way. + Click on the header (here, 'Directory contents') to expand the section and view its contents. + +**Content guide:** - **The `greetings.csv` file** is a CSV containing some minimal columnar data we use for testing purposes. @@ -76,4 +94,14 @@ If you run this inside `hello-nf-core`, you should see the following output: - **The `solutions` directory** contains the completed workflow scripts that result from each step of the course. They are intended to be used as a reference to check your work and troubleshoot any issues. -**Now, to begin the course, click on the arrow in the bottom right corner of this page.** +## Readiness checklist + +Think you're ready to dive in? + +- [ ] I understand the goal of this course and its prerequisites +- [ ] My codespace is up and running +- [ ] I've set my working directory appropriately + +If you checked all the boxes, you're good to go. + +**To continue to Part 1, click on the arrow in the bottom right corner of this page.** diff --git a/docs/hello_nf-core/index.md b/docs/hello_nf-core/index.md index 52657c88ce..17a70cbe26 100644 --- a/docs/hello_nf-core/index.md +++ b/docs/hello_nf-core/index.md @@ -50,37 +50,34 @@ This training course aims to teach you the core concepts for running nf-core-sty We won't cover everything there is to know about nf-core pipelines, because nf-core encompasses many features and conventions developed by the community over years. Instead, we will focus on the essential concepts that will help you get started and understand how nf-core works. -### Part 1: Run a demo pipeline +#### Part 1: Run a demo pipeline First, you'll **run an existing nf-core pipeline** and examine its code structure to get a sense of what makes these pipelines different from basic Nextflow workflows. The elaborate directory structure, configuration system, and standardized conventions might seem like a lot at first, but the benefits will become clear as you learn to decode and utilize these resources effectively. -### Part 2: Rewrite Hello for nf-core +#### Part 2: Rewrite Hello for nf-core Next, you'll **adapt an existing workflow to the nf-core template scaffold**, starting from the simple workflow produced in the [Hello Nextflow](../hello_nextflow/index.md) course. Many pipeline development efforts start from existing code, so learning how to restructure an existing workflow to leverage nf-core's nested workflow system is a practical skill you're likely to use repeatedly in your work. -### Part 3: Use an nf-core module +#### Part 3: Use an nf-core module Then you'll discover one of nf-core's biggest advantages: the **community modules library**. Instead of writing every process from scratch, you'll learn to integrate pre-built, tested modules that wrap common bioinformatics tools. This approach saves time and ensures consistency across pipelines. -### Part 4: Make an nf-core module +#### Part 4: Make an nf-core module Of course, the modules library doesn't have everything, so you'll also learn to **create your own nf-core-style module**. You'll learn to work with the specific structure, naming conventions, and metadata requirements that make modules shareable and maintainable by the community. -### Part 5: Add input validation +#### Part 5: Add input validation Finally, you'll implement **input validation** for both command-line parameters and input data files using nf-schema. This catches errors before pipelines start to run, providing fast feedback and clear error messages. This type of upfront validation makes pipelines more robust and easier to use. -By the end of the course, you'll have transformed a basic Nextflow workflow into an nf-core-style pipeline with standardized structure, reusable components, and robust validation. +**By the end of the course, you'll have transformed a basic Nextflow workflow into an nf-core-style pipeline with standardized structure, reusable components, and robust validation.** -## Getting started +Ready to take the course? -Ready to go? -Launch the training environment in a separate browser tab or window using the "Open in GitHub Codespaces" button below, then navigate to the next section and read on while the environment loads. - -[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/nextflow-io/training?quickstart=1&ref=master){:target="\_blank"} +[TODO: big green start button] From e92b430e3755c8dfbe72e329baf028a9a7198cc7 Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Thu, 6 Nov 2025 11:15:22 -0500 Subject: [PATCH 104/113] minor tweaks --- docs/hello_nf-core/00_orientation.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/hello_nf-core/00_orientation.md b/docs/hello_nf-core/00_orientation.md index 4239d9cb63..3a720aeb6d 100644 --- a/docs/hello_nf-core/00_orientation.md +++ b/docs/hello_nf-core/00_orientation.md @@ -22,7 +22,7 @@ If you are working through this course by yourself, please go through the [Envir You can check what version is installed using the command`nf-core --version`. -## Get ready to work! +## Get ready to work Once your codespace is running, there are two things you need to do before diving into the training: set your working directory for this specific course, and take a look at the materials provided. @@ -46,7 +46,7 @@ cd hello-nf-core/ Now let's have a look at the contents of this directory. -### Materials provided +### Check out the materials provided You can explore the contents of this directory by using the file explorer on the left-hand side of the training workspace. Alternatively, you can use the `tree` command. @@ -83,7 +83,7 @@ If you run this inside `hello-nf-core`, you should see the following output. !!! note We use collapsible sections like this to include expected command output in a concise way. - Click on the header (here, 'Directory contents') to expand the section and view its contents. + Click on the colored box to expand the section and view its contents. **Content guide:** @@ -102,6 +102,6 @@ Think you're ready to dive in? - [ ] My codespace is up and running - [ ] I've set my working directory appropriately -If you checked all the boxes, you're good to go. +If you can check all the boxes, you're good to go. **To continue to Part 1, click on the arrow in the bottom right corner of this page.** From 740b5d0b6086cd6e7370acc73e4e787e8bdca65e Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Thu, 6 Nov 2025 18:04:52 -0500 Subject: [PATCH 105/113] Reworked Part 1 (run demo) for clarity and flow --- docs/hello_nf-core/01_run_demo.md | 366 ++++++++---------- .../img/nf-core_demo_code_organization.svg | 5 + 2 files changed, 173 insertions(+), 198 deletions(-) create mode 100644 docs/hello_nf-core/img/nf-core_demo_code_organization.svg diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index e7a66c18c9..99794d912a 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -4,7 +4,7 @@ In this first part of the Hello nf-core training course, we show you how to find We are going to use a pipeline called nf-core/demo that is maintained by the nf-core project as part of its inventory of pipelines for demonstrating code structure and tool operations. -Make sure you are in the `hello-nf-core/` directory as instructed in the [Orientation](./00_orientation.md). +Make sure your working directory is set to `hello-nf-core/` as instructed on the [Getting started](./00_orientation.md) page. --- @@ -33,26 +33,30 @@ Whenever you are considering adopting a new pipeline, you should read the pipeli Have a look now and see if you can find out: -- which tools the pipeline will run (Check the tab: `Introduction`) -- which inputs and parameters the pipeline accepts or requires (Check the tab: `Parameters`) -- what are the outputs produced by the pipeline (Check the tab: `Output`) +- Which tools the pipeline will run (Check the tab: `Introduction`) +- Which inputs and parameters the pipeline accepts or requires (Check the tab: `Parameters`) +- What are the outputs produced by the pipeline (Check the tab: `Output`) - The `Introduction` tab provides an overview of the pipeline, including a visual representation (called a subway map) and a list of tools that are run as part of the pipeline. +#### 1.1.1. Pipeline overview - ![pipeline subway map](./img/nf-core-demo-subway-cropped.png) +The `Introduction` tab provides an overview of the pipeline, including a visual representation (called a subway map) and a list of tools that are run as part of the pipeline. - 1. Read QC (FASTQC) - 2. Adapter and quality trimming (SEQTK_TRIM) - 3. Present QC for raw reads (MULTIQC) +![pipeline subway map](./img/nf-core-demo-subway-cropped.png) - The documentation also provides an example input file (see below) and an example command line. +1. Read QC (FASTQC) +2. Adapter and quality trimming (SEQTK_TRIM) +3. Present QC for raw reads (MULTIQC) - ```bash - nextflow run nf-core/demo \ - -profile \ - --input samplesheet.csv \ - --outdir - ``` +#### 1.1.2. Example command line + +The documentation also provides an example input file (discussed further below) and an example command line. + +```bash +nextflow run nf-core/demo \ + -profile \ + --input samplesheet.csv \ + --outdir +``` You'll notice that the example command does NOT specify a workflow file, just the reference to the pipeline repository, `nf-core/demo`. @@ -61,7 +65,7 @@ Let's retrieve the code so we can examine this structure. ### 1.2. Retrieve the pipeline code -Once we've determined the pipeline appears to be suitable for our purposes, let's try it out. +Once we've determined that the pipeline appears to be suitable for our purposes, let's try it out. Fortunately Nextflow makes it easy to retrieve pipelines from correctly-formatted repositories without having to download anything manually. Let's return to the terminal and run the following: @@ -149,12 +153,18 @@ Learn how to try out an nf-core pipeline with minimal effort. ## 2. Try out the pipeline with its test profile Conveniently, every nf-core pipeline comes with a test profile. -This is a minimal set of configuration settings for the pipeline to run using a small test dataset hosted in the [nf-core/test-datasets](https://github.com/nf-core/test-datasets) repository. It's a great way to quickly try out a pipeline at small scale. +This is a minimal set of configuration settings for the pipeline to run using a small test dataset hosted in the [nf-core/test-datasets](https://github.com/nf-core/test-datasets) repository. +It's a great way to quickly try out a pipeline at small scale. + +!!! note + + Nextflow's configuration profile system allows you to easily switch between different container engines or execution environments. + For more details, see [Hello Nextflow Part 6: Configuration](../hello_nextflow/06_hello_configuration.md). ### 2.1. Examine the test profile It's good practice to check what a pipeline's test profile specifies before running it. -The `test` profile for `nf-core/demo` is shown below: +The `test` profile for `nf-core/demo` lives in the configuration file `conf/test.config` and is shown below. ```groovy title="conf/test.config" linenums="1" hl_lines="8 26" /* @@ -187,26 +197,44 @@ params { } ``` -The test profile shows us what has been pre-configured for testing: most notably, the `input` parameter is already set to point to a test dataset, so we don't need to provide our own data. +You'll notice right away that the comment block at the top includes a usage example showing how to run the pipeline with this test profile. -The comment block at the top also includes a usage example showing how to run with this test profile. -Notice that it includes `--outdir `. This tells us we'll need to specify an output directory when we run the pipeline. +```groovy title="conf/test.config" linenums="7" +Use as follows: + nextflow run nf-core/demo -profile test, --outdir +``` -### 2.2. Run the pipeline +The only things we need to supply are what's shown between carets in the example command: `` and ``. + +As a reminder, `` refers to the choice of container system. All nf-core pipelines are designed to be usable with containers (Docker, Singularity, etc.) to ensure reproducibility and eliminate software installation issues. +So we'll need to specify whether we want to use Docker or Singularity to test the pipeline. + +The `--outdir ` part refers to the directory where Nextflow will write the pipeline's outputs. +We need to provide a name for it, which we can just make up. +If it does not exist already, Nextflow will create it for us at runtime. + +Moving on to the section after the comment block, the test profile shows us what has been pre-configured for testing: most notably, the `input` parameter is already set to point to a test dataset, so we don't need to provide our own data. +If you follow the link to the pre-configured input, you'll see it is a csv file containing sample identifiers and file paths for several experimental samples. -Based on the usage example in the test profile, we know we need to specify `--outdir` to tell the pipeline where to save results. +```csv title="samplesheet_test_illumina_amplicon.csv" +sample,fastq_1,fastq_2 +SAMPLE1_PE,https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/illumina/amplicon/sample1_R1.fastq.gz,https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/illumina/amplicon/sample1_R2.fastq.gz +SAMPLE2_PE,https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/illumina/amplicon/sample2_R1.fastq.gz,https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/illumina/amplicon/sample2_R2.fastq.gz +SAMPLE3_SE,https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/illumina/amplicon/sample1_R1.fastq.gz, +SAMPLE3_SE,https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/illumina/amplicon/sample2_R1.fastq.gz, +``` -We're also going to specify `-profile docker,test`, which by nf-core convention enables the use of Docker containers, and of course, invokes the test profile. +This is called a samplesheet, and is the most common form of input to nf-core pipelines. + +!!! note -!!! note "Understanding container profiles" + Don't worry if you're not familiar with the data formats and types, it's not important for what follows. - The `-profile docker` option tells Nextflow to use Docker containers for running processes. - nf-core pipelines are designed to work with containers (Docker, Singularity, etc.) to ensure reproducibility and eliminate software installation issues. - The profile system allows you to easily switch between different container engines or execution environments. +So this confirms that we have everything we need to try out the pipeline. - For more details on how configuration profiles work in Nextflow, see [Hello Nextflow Part 6: Configuration](../hello_nextflow/06_hello_configuration.md). +### 2.2. Run the pipeline -Let's try it! +Let's decide to use Docker for the container system and `demo-results` as the output directory, and we're ready to run the test command: ```bash nextflow run nf-core/demo -profile docker,test --outdir demo-results @@ -214,7 +242,8 @@ nextflow run nf-core/demo -profile docker,test --outdir demo-results Here's the console output from the pipeline: -```console title="Output" +``` +??? example "Output" N E X T F L O W ~ version 25.04.3 Launching `https://github.com/nf-core/demo` [happy_varahamihira] DSL2 - revision: db7f526ce1 [master] @@ -269,7 +298,9 @@ executor > local (7) -[nf-core/demo] Pipeline completed successfully- ``` -You see that there is more console output than when you run a basic Nextflow pipeline. +If your output matches that, congratulations! You've just run your first nf-core pipeline. + +You'll notice that there is more a lot more console output than when you run a basic Nextflow pipeline. There's a header that includes a summary of the pipeline's version, inputs and outputs, and a few elements of configuration. !!! note @@ -286,11 +317,9 @@ Moving on to the execution output, let's have a look at the lines that tell us w This tells us that three processes were run, corresponding to the three tools shown in the pipeline documentation page on the nf-core website: FASTQC, SEQTK_TRIM and MULTIQC. -!!! note - - The full process names as shown here, such as `NFCORE_DEMO:DEMO:MULTIQC`, are longer than what you may have seen in the introductory Hello Nextflow material. - These includes the names of their parent workflows and reflect the modularity of the pipeline code. - We will go into more detail about that shortly. +The full process names as shown here, such as `NFCORE_DEMO:DEMO:MULTIQC`, are longer than what you may have seen in the introductory Hello Nextflow material. +These include the names of their parent workflows and reflect the modularity of the pipeline code. +We'll go into more detail about that in a little bit. ### 2.3. Examine the pipeline's outputs @@ -325,16 +354,15 @@ tree -L 2 demo-results └── pipeline_dag_2025-03-05_09-44-26.html ``` -If you're curious about the specifics of what that all means, check out [the nf-core/demo pipeline documentation page](https://nf-co.re/demo/1.0.2/). - +That might seem like a lot. At this stage, what's important to observe is that the results are organized by module, and there is additionally a directory called `pipeline_info` containing various timestamped reports about the pipeline execution. This is standard for nf-core pipelines. -Congratulations! You have just run your first nf-core pipeline. +To learn more about the `nf-core/demo` pipeline's outputs, check out its [documentation page](https://nf-co.re/demo/1.0.2/docs/output/). ### Takeaway -You know how to run an nf-core pipeline using its built-in test profile. +You know how to run an nf-core pipeline using its built-in test profile and where to find its outputs. ### What's next? @@ -342,15 +370,16 @@ Learn how the pipeline code is organized. --- -Now that we've successfully run the pipeline as users, let's shift our perspective to understand how nf-core pipelines are structured internally. -Understanding this organization will prepare you for developing your own nf-core-compatible pipelines in the upcoming parts of this course. - ## 3. Examine the pipeline code structure -The nf-core project enforces strong guidelines for how pipelines are structured, and how the code is organized, configured and documented. +Now that we've successfully run the pipeline as users, let's shift our perspective to look at how nf-core pipelines are structured internally. + +The nf-core project enforces strong guidelines for how pipelines are structured, and for how the code is organized, configured and documented. +Understanding how this is all organized is the first step toward developing your own nf-core-compatible pipelines, which we will tackle in Part 2 of this course. -Let's have a look at how the pipeline code is organized in the `nf-core/demo` repository (using the `pipelines` symlink we created earlier). -You can either use `tree` or use the file explorer in your IDE. +Let's have a look at how the pipeline code is organized in the `nf-core/demo` repository, using the `pipelines` symlink we created earlier. + +You can either use `tree` or use the file explorer to find and open the `nf-core/demo` directory. ```bash tree -L 1 pipelines/nf-core/demo @@ -381,68 +410,80 @@ tree -L 1 pipelines/nf-core/demo └── workflows ``` -There's a lot going on in there, so we'll tackle this in stages. -We're going to look at the following categories: +There's a lot going on in there, so we'll tackle this step by step. + +First, let's note that at the top level, you can find a README file with summary information, as well as accessory files that summarize project information such as licensing, contribution guidelines, citation and code of conduct. +Detailed pipeline documentation is located in the `docs` directory. +All of this content is used to generate the web pages on the nf-core website programmatically, so they're always up to date with the code. + +Now, for the rest, we're going to divide our exploration in three stages: 1. Pipeline code components (`main.nf`, `workflows`, `subworkflows`, `modules`) 2. Configuration, parameters and inputs -3. Documentation and related assets +3. Input validation -Let's start with the code proper, though note that for now, we're going to focus on the file hierarchy and structural organization, rather than diving into the code syntax within individual files. +Let's start with the pipeline code components. +We're going to focus on the file hierarchy and structural organization, rather than diving into the code within individual files. ### 3.1. Pipeline code components -The pipeline code organization follows a modular structure that is designed to maximize code reuse. +The standard nf-core pipeline code organization follows a modular structure that is designed to maximize code reuse, as introduced in [Hello Modules](../hello_nextflow/04_hello_modules.md), Part 4 of the [Hello Nextflow](../hello_nextflow/index.md) course, although in true nf-core fashion, this is implemented with a bit of additional complexity. +Specifically, nf-core pipelines make abundant use of subworkflows, i.e. workflow scripts that are imported by a parent workflow. + +That may sound a bit abstract, so let's take a look how this is used in practice in the `nf-core/demo` pipeline. !!! note - We won't go over the actual code for how these modular components are connected, because there is some additional complexity associated with the use of subworkflows that can be confusing, and understanding that is not necessary at this stage of the training. - For now, we're going to focus on the logic of this modular organization. + We won't go over the actual code for _how_ these modular components are connected, because there is some additional complexity associated with the use of subworkflows that can be confusing, and understanding that is not necessary at this stage of the training. + For now, we're going to focus on the overall organization and logic. -#### 3.1.1. Overall organization and `main.nf` script +#### 3.1.1. General overview -At the top level, there is the `main.nf` script, which is the entrypoint Nextflow starts from when we execute `nextflow run nf-core/demo`. That means when you run `nextflow run nf-core/demo` to run the pipeline, Nextflow automatically finds and executes the `main.nf` script, and everything else will flow from there. +Here is what the relationships between the relevant code components look like for the `nf-core/demo` pipeline: -The central logic of the pipeline is stored inside the `workflows` folder, in a file called `demo.nf`, which is called from `main.nf`. +
+ --8<-- "docs/hello_nf-core/img/nf-core_demo_code_organization.svg" +
-```bash -tree pipelines/nf-core/demo/workflows -``` +There is a so-called _entrypoint_ script called `main.nf`, which acts as a wrapper for two kinds of nested workflows: the workflow containing the actual analysis logic, located under `workflows/` and called `demo.nf`, and a set of housekeeping workflows located under `subworkflows/`. +The `demo.nf` workflow calls on **modules** located under `modules/`; these contain the **processes** that will perform the actual analysis steps. -??? example "Directory contents" +Now, let's review these components in turn. - ```console - pipelines/nf-core/demo/workflows - └── demo.nf - ``` +#### 3.1.2. The entrypoint script: `main.nf` -`main.nf` also calls a few 'housekeeping' subworkflows that we're going to ignore for now. +The `main.nf` script is the entrypoint that Nextflow starts from when we execute `nextflow run nf-core/demo`. +That means when you run `nextflow run nf-core/demo` to run the pipeline, Nextflow automatically finds and executes the `main.nf` script. +This works for any Nextflow pipeline that follows this conventional naming and structure, not just nf-core pipelines. -The `demo.nf` workflow itself calls out to various script components, namely, modules and subworkflows, stored in the corresponding `modules` and `subworkflows` folders. +Using an entrypoint script makes it easy to run standardized 'housekeeping' subworkflows before and after the actual analysis script gets run. +We'll go over those after we've reviewed the actual analysis workflow and its modules. -- **Module:** A wrapper around a single process. -- **Subworkflow:** A mini workflow that calls two or more modules and is designed to be called by another workflow. +#### 3.1.3. The analysis script: `workflows/demo.nf` -Here's an overview of the nested structure of a workflow composed of subworkflows and modules: +The `workflows/demo.nf` workflow is where the central logic of the pipeline is stored. +It is structured much like a normal Nextflow workflow, except it is designed to be called from a parent workflow, which requires a few extra features. +We'll cover the relevant differences in the next part of this course, when we tackle the conversion of the simple Hello pipeline from Hello Nextflow into an nf-core-compatible form. -
- --8<-- "docs/side_quests/img/nf-core/nested.excalidraw.svg" -
+The `demo.nf` workflow calls on **modules** located under `modules/`, which we'll review next. -Not all workflows use subworkflows to organize their modules, but this is a very common pattern that makes it possible to reuse chunks of code across different pipelines in a way that is flexible while minimizing maintenance burden. +!!! note -Within this structure, `modules` and `subworkflows` are further organized into `local` and `nf-core` folders. -The `nf-core` folder is for components that have come from the nf-core GitHub repository, while the `local` folder is for components that have been developed independently. -Usually these are operations that very specific to that pipeline. + Some nf-core analysis workflows display additional levels of nesting by calling on lower-level subworkflows. + This is mostly used for wrapping two or more modules that are commonly used together into easily reusable pipeline segments. + You can see some examples by browsing available [nf-core subworkflows](https://nf-co.re/subworkflows/) on the nf-core website. -Let's take a peek into those directories. + When the analysis script uses subworkflows, those are stored under the `subworkflows/` directory. -#### 3.1.2. Modules +#### 3.1.4. The modules The modules are where the process code lives, as described in [Part 4 of the Hello Nextflow training course](../hello_nextflow/04_hello_modules.md). -In the nf-core project, modules are organized using a nested structure that refers to toolkit and tool names. -The module code file describing the process is always called `main.nf`, and is accompanied by tests and `.yml` files. +In the nf-core project, modules are organized using a multi-level nested structure that reflect both their origin and their contents. +At the top level, modules are differentiated as either `nf-core` or `local` (not part of the nf-core project), and then further placed into a directory named after the tool(s) they wrap. +If the tool belongs to a toolkit (i.e. a package containing multiple tools) then there is an intermediate directory level named after the toolkit. + +You can see this applied in practice to the `nf-core/demo` pipeline modules: ```bash tree -L 3 pipelines/nf-core/demo/modules @@ -474,11 +515,14 @@ tree -L 3 pipelines/nf-core/demo/modules Here you see that the `fastqc` and `multiqc` modules sit at the top level within the `nf-core` modules, whereas the `trim` module sits under the toolkit that it belongs to, `seqtk`. In this case there are no `local` modules. -#### 3.1.3. Subworkflows +The module code file describing the process is always called `main.nf`, and is accompanied by tests and `.yml` files which we'll ignore for now. + +Taken together, the entrypoint workflow, analysis workflow and modules are sufficient for running the 'interesting' parts of the pipeline. +However, we know there are also housekeeping subworkflows in there, so let's look at those now. -As noted above, subworkflows function as wrappers that call two or more modules. +#### 3.1.5. The housekeeping subworkflows -In an nf-core pipeline, the subworkflows are divided into `local` and `nf-core` directories, and each subworkflow has its own nested directory structure with its own `main.nf` script. +Like modules, subworkflows are differentiated into `local` and `nf-core` directories, and each subworkflow has its own nested directory structure with its own `main.nf` script, tests and `.yml` file. ```bash tree -L 3 pipelines/nf-core/demo/subworkflows @@ -506,130 +550,39 @@ tree -L 3 pipelines/nf-core/demo/subworkflows └── tests ``` -In the case of the `nf-core/demo` pipeline, the subworkflows involved are all 'utility' or housekeeping subworkflows, as denoted by the `utils_` prefix in their names. +As noted above, the `nf-core/demo` pipeline does not include any analysis-specific subworkflows, so all the subworkflows we see here are so-called 'housekeeping' or 'utility' workflows, as denoted by the `utils_` prefix in their names. These subworkflows are what produces the fancy nf-core header in the console output, among other accessory functions. -Other pipelines may also use subworkflows as part of the main workflow of interest. +!!! tip -!!! note + Aside from their naming pattern, another indication that these subworkflows do not perform any truly analysis-related function is that they do not call any processes at all. - If you would like to learn how to compose workflows with subworkflows, see the [Workflows of Workflows](../side_quests/workflows_of_workflows/) Side Quest. +This completes the round-up of core code components that constitute the `nf-core/demo` pipeline. +Now let's take a look at the remaining elements that you should know a little bit about before diving into development: pipeline configuration and input validation. -### 3.2. Configuration +### 3.2. Pipeline configuration -The nf-core project applies guidelines for pipeline configuration that aim to build on Nextflow's flexible customization options in a way that provides greater consistency and maintainability across pipelines. +You've learned previously that Nextflow offers many options for configuring pipeline execution, be it in terms of inputs and parameters, computing resources, and other aspects of orchestration. +The nf-core project applies highly standardized guidelines for pipeline configuration that aim to build on Nextflow's flexible customization options in a way that provides greater consistency and maintainability across pipelines. -The central configuration file `nextflow.config` is used to set default values for parameters and other configuration options. The majority of these configuration options are applied by default while others (e.g., software dependency profiles) are included as optional profiles. +The central configuration file `nextflow.config` is used to set default values for parameters and other configuration options. +The majority of these configuration options are applied by default while others (e.g., software dependency profiles) are included as optional profiles. There are several additional configuration files that are stored in the `conf` folder and which can be added to the configuration by default or optionally as profiles: -- `base.config`: A 'blank slate' config file, appropriate for general use on most high-performance computing. environments. This defines broad bins of resource usage, for example, which are convenient to apply to modules. +- `base.config`: A 'blank slate' config file, appropriate for general use on most high-performance computing environments. This defines broad bins of resource usage, for example, which are convenient to apply to modules. - `modules.config`: Additional module directives and arguments. -- `test.config`: A profile to run the pipeline with minimal test data, which we used when we ran the demo pipeline in the previous section (code shown there). +- `test.config`: A profile to run the pipeline with minimal test data, which we used when we ran the demo pipeline. - `test_full.config`: A profile to run the pipeline with a full-sized test dataset. -### 3.3. Documentation and related assets - -At the top level, you can find a README file with summary information, as well as accessory files that summarize project information such as licensing, contribution guidelines, citation and code of conduct. - -Detailed pipeline documentation is located in the `docs` directory. -This content is used to generate the web pages on the nf-core website. - -In addition to these human-readable documents, there are two JSON files that provide useful machine-readable information describing parameters and input requirements, `nextflow_schema.json` and `assets/schema_input.json`. - -The `nextflow_schema.json` is a file used to store information about the pipeline parameters including type, description and help text in a machine readable format. -The schema is used for various purposes, including automated parameter validation, help text generation, and interactive parameter form rendering in UI interfaces. - -```json title="nextflow_schema.json (not showing full file)" linenums="1" -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://raw.githubusercontent.com/nf-core/demo/master/nextflow_schema.json", - "title": "nf-core/demo pipeline parameters", - "description": "An nf-core demo pipeline", - "type": "object", - "$defs": { - "input_output_options": { - "title": "Input/output options", - "type": "object", - "fa_icon": "fas fa-terminal", - "description": "Define where the pipeline should find input data and save output data.", - "required": ["input", "outdir"], - "properties": { - "input": { - "type": "string", - "format": "file-path", - "exists": true, - "schema": "assets/schema_input.json", - "pattern": "^\\S+\\.(csv|tsv|json|yaml|yml)$", - "description": "Path to comma-separated file containing information about the samples in the experiment.", - "help_text": "You will need to create a design file with information about the samples in your experiment before running the pipeline. Use this parameter to specify its location. It has to be a comma-separated file with 3 columns, and a header row. See [usage docs](https://nf-co.re/demo/usage#samplesheet-input).", - "fa_icon": "fas fa-file-csv" - }, - "outdir": { - "type": "string", - "format": "directory-path", - "description": "The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure.", - "fa_icon": "fas fa-folder-open" - }, - "email": { - "type": "string", - "description": "Email address for completion summary.", - "fa_icon": "fas fa-envelope", - "help_text": "Set this parameter to your e-mail address to get a summary e-mail with details of the run sent to you when the workflow exits. If set in your user config file (`~/.nextflow/config`) then you don't need to specify this on the command line for every run.", - "pattern": "^([a-zA-Z0-9_\\-\\.]+)@([a-zA-Z0-9_\\-\\.]+)\\.([a-zA-Z]{2,5})$" - }, - "multiqc_title": { - "type": "string", - "description": "MultiQC report title. Printed as page header, used for filename if not otherwise specified.", - "fa_icon": "fas fa-file-signature" - } - } - }, -(truncated) -``` - -The `schema_input.json` is a file used to define the input samplesheet structure. -Each column can have a type, pattern, description and help text in a machine readable format. +We will touch a few of those files later in the course. -```json title="assets/schema_input.json" linenums="1" -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://raw.githubusercontent.com/nf-core/demo/master/assets/schema_input.json", - "title": "nf-core/demo pipeline - params.input schema", - "description": "Schema for the file provided with params.input", - "type": "array", - "items": { - "type": "object", - "properties": { - "sample": { - "type": "string", - "pattern": "^\\S+$", - "errorMessage": "Sample name must be provided and cannot contain spaces", - "meta": ["id"] - }, - "fastq_1": { - "type": "string", - "format": "file-path", - "exists": true, - "pattern": "^\\S+\\.f(ast)?q\\.gz$", - "errorMessage": "FastQ file for reads 1 must be provided, cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" - }, - "fastq_2": { - "type": "string", - "format": "file-path", - "exists": true, - "pattern": "^\\S+\\.f(ast)?q\\.gz$", - "errorMessage": "FastQ file for reads 2 cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" - } - }, - "required": ["sample", "fastq_1"] - } -} -``` +### 3.3. Inputs and validation -The schema is used for various purposes, including automated validation, and providing helpful error messages. +As we noted earlier, when we examined the `nf-core/demo` pipeline's test profile, it is designed to take as input a samplesheet containing file paths and sample identifiers. +The file paths linked to real data located in the `nf-core/test-datasets` repository. -An example samplesheet is provided under the `assets` directory: +An example samplesheet is also provided under the `assets` directory, although the paths in this one are not real. ```csv title="assets/samplesheet.csv" linenums="1" sample,fastq_1,fastq_2 @@ -638,17 +591,34 @@ SAMPLE_SINGLE_END,/path/to/fastq/files/AEG588A4_S4_L003_R1_001.fastq.gz, ``` -!!! note +This particular samplesheet is fairly simple, but some pipelines run on samplesheets that are more complex, with a lot more metadata associated with the primary inputs. + +Unfortunately, because these files can be difficult to check by eye, improper formatting of input data is a very common source of pipeline failures. +A related problem is when parameters are provided incorrectly. + +The solution to these problems is to run automated validation checks on all input files to ensure they contain the expected types of information, formatted correctly, and on parameters to ensure they are of the expected type. +This is called input validation, and should ideally be done _before_ trying to run a pipeline, rather than waiting for the pipeline to fail to find out there was a problem with the inputs. - The paths in this example samplesheet are not real. - For paths to real data files, you should look in the test profiles, which link to data in the `nf-core/test-datasets` repository. +Just like for configuration, the nf-core project is very opinionated about input validation, and recommends the use of the [nf-schema plugin](https://nextflow-io.github.io/nf-schema/latest/), a Nextflow plugin that provides comprehensive validation capabilities for Nextflow pipelines. - In general, it's considered good practice to link out to example data rather than include it in the pipeline code repository, unless the example data is of trivial size (as is the case for the `greetings.csv` in the Hello Nextflow training series). +We'll cover this topic in more detail in Part 5 of this course. +For now, just be aware that there are two JSON files provided for that purpose, `nextflow_schema.json` and `assets/schema_input.json`. + +The `nextflow_schema.json` is a file used to store information about the pipeline parameters including type, description and help text in a machine readable format. +This is used for various purposes, including automated parameter validation, help text generation, and interactive parameter form rendering in UI interfaces. + +The `schema_input.json` is a file used to define the input samplesheet structure. +Each column can have a type, pattern, description and help text in a machine readable format. +The schema is used for various purposes, including automated validation, and providing helpful error messages. ### Takeaway -You know what are the main components of an nf-core pipeline and how the code is organized, what are the main elements of configuration, and what are some additional sources of information that can be useful. +You know what are the main components of an nf-core pipeline and how the code is organized; where the main elements of configuration are located; and you're aware of what input validation is for. ### What's next? Take a break! That was a lot. When you're feeling refreshed and ready, move on to the next section to apply what you've learned to write an nf-core compatible pipeline. + +!!! tip + + If you would like to learn how to compose workflows with subworkflows before moving on to the next part, check out the [Workflows of Workflows](../side_quests/workflows_of_workflows/) Side Quest. diff --git a/docs/hello_nf-core/img/nf-core_demo_code_organization.svg b/docs/hello_nf-core/img/nf-core_demo_code_organization.svg new file mode 100644 index 0000000000..c0aafa5280 --- /dev/null +++ b/docs/hello_nf-core/img/nf-core_demo_code_organization.svg @@ -0,0 +1,5 @@ + + +subworkflows/workflows/demo.nffastqc/main.nfmultiqc/main.nfseqtk/trim/main.nfmain.nfincludeincludemodules/nf-core/local/utils_nfcore_demo_pipeline/main.nfnf-core/utils_*/main.nf \ No newline at end of file From 850fe489fe4438f5a998ee91a47d9af2cab68f9a Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Fri, 7 Nov 2025 16:17:38 -0500 Subject: [PATCH 106/113] Add green start button --- docs/hello_nf-core/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hello_nf-core/index.md b/docs/hello_nf-core/index.md index 17a70cbe26..79a9432808 100644 --- a/docs/hello_nf-core/index.md +++ b/docs/hello_nf-core/index.md @@ -80,4 +80,4 @@ This catches errors before pipelines start to run, providing fast feedback and c Ready to take the course? -[TODO: big green start button] +[Start learning :material-arrow-right:](00_orientation.md){ .md-button .md-button--primary } From 1fb065cec6421a978882471a9cd59ab03a9e6328 Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Fri, 7 Nov 2025 17:40:08 -0500 Subject: [PATCH 107/113] Completed updates to Part 2 plus a formatting fix to Part 1 --- docs/hello_nf-core/01_run_demo.md | 109 +++++------ docs/hello_nf-core/02_rewrite_hello.md | 255 ++++++++++++++----------- 2 files changed, 198 insertions(+), 166 deletions(-) diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index 99794d912a..980fce4d15 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -242,61 +242,62 @@ nextflow run nf-core/demo -profile docker,test --outdir demo-results Here's the console output from the pipeline: -``` ??? example "Output" - N E X T F L O W ~ version 25.04.3 - -Launching `https://github.com/nf-core/demo` [happy_varahamihira] DSL2 - revision: db7f526ce1 [master] - - ------------------------------------------------------- - ,--./,-. - ___ __ __ __ ___ /,-._.--~' - |\ | |__ __ / ` / \ |__) |__ } { - | \| | \__, \__/ | \ |___ \`-._,-`-, - `._,._,' - nf-core/demo 1.0.2 ------------------------------------------------------- -Input/output options - input : https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/samplesheet/samplesheet_test_illumina_amplicon.csv - outdir : demo-results - -Institutional config options - config_profile_name : Test profile - config_profile_description: Minimal test dataset to check pipeline function - -Generic options - trace_report_suffix : 2025-10-30_13-22-01 - -Core Nextflow options - revision : master - runName : happy_varahamihira - containerEngine : docker - launchDir : /workspaces/training/hello-nf-core - workDir : /workspaces/training/hello-nf-core/work - projectDir : /workspaces/.nextflow/assets/nf-core/demo - userName : root - profile : docker,test - configFiles : /workspaces/.nextflow/assets/nf-core/demo/nextflow.config - -!! Only displaying parameters that differ from the pipeline defaults !! ------------------------------------------------------- -* The pipeline - https://doi.org/10.5281/zenodo.12192442 - -* The nf-core framework - https://doi.org/10.1038/s41587-020-0439-x - -* Software dependencies - https://github.com/nf-core/demo/blob/master/CITATIONS.md - - -executor > local (7) -[db/fae3ff] NFCORE_DEMO:DEMO:FASTQC (SAMPLE3_SE) [100%] 3 of 3 ✔ -[d0/f6ea55] NFCORE_DEMO:DEMO:SEQTK_TRIM (SAMPLE1_PE) [100%] 3 of 3 ✔ -[af/e6da56] NFCORE_DEMO:DEMO:MULTIQC [100%] 1 of 1 ✔ --[nf-core/demo] Pipeline completed successfully- -``` + + ```console + N E X T F L O W ~ version 25.04.3 + + Launching `https://github.com/nf-core/demo` [happy_varahamihira] DSL2 - revision: db7f526ce1 [master] + + + ------------------------------------------------------ + ,--./,-. + ___ __ __ __ ___ /,-._.--~' + |\ | |__ __ / ` / \ |__) |__ } { + | \| | \__, \__/ | \ |___ \`-._,-`-, + `._,._,' + nf-core/demo 1.0.2 + ------------------------------------------------------ + Input/output options + input : https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/samplesheet/samplesheet_test_illumina_amplicon.csv + outdir : demo-results + + Institutional config options + config_profile_name : Test profile + config_profile_description: Minimal test dataset to check pipeline function + + Generic options + trace_report_suffix : 2025-10-30_13-22-01 + + Core Nextflow options + revision : master + runName : happy_varahamihira + containerEngine : docker + launchDir : /workspaces/training/hello-nf-core + workDir : /workspaces/training/hello-nf-core/work + projectDir : /workspaces/.nextflow/assets/nf-core/demo + userName : root + profile : docker,test + configFiles : /workspaces/.nextflow/assets/nf-core/demo/nextflow.config + + !! Only displaying parameters that differ from the pipeline defaults !! + ------------------------------------------------------ + * The pipeline + https://doi.org/10.5281/zenodo.12192442 + + * The nf-core framework + https://doi.org/10.1038/s41587-020-0439-x + + * Software dependencies + https://github.com/nf-core/demo/blob/master/CITATIONS.md + + + executor > local (7) + [db/fae3ff] NFCORE_DEMO:DEMO:FASTQC (SAMPLE3_SE) [100%] 3 of 3 ✔ + [d0/f6ea55] NFCORE_DEMO:DEMO:SEQTK_TRIM (SAMPLE1_PE) [100%] 3 of 3 ✔ + [af/e6da56] NFCORE_DEMO:DEMO:MULTIQC [100%] 1 of 1 ✔ + -[nf-core/demo] Pipeline completed successfully- + ``` If your output matches that, congratulations! You've just run your first nf-core pipeline. diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index a0f7ad8117..28c00defcc 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -1,6 +1,6 @@ # Part 2: Rewrite Hello for nf-core -In this second part of the Hello nf-core training course, we show you how to create an nf-core compatible version of the pipeline produced by the [Hello Nextflow](../hello_nextflow/index.md) course. +In this second part of the Hello nf-core training course, we show you how to create an nf-core compatible version of the pipeline produced by the [Hello Nextflow](../hello_nextflow/index.md) beginners' course. You'll have noticed in the first section of the training that nf-core pipelines follow a fairly elaborate structure with a lot of accessory files. Creating all that from scratch would be very tedious, so the nf-core community has developed tooling to do it from a template instead, to bootstrap the process. @@ -144,12 +144,13 @@ tree core-hello That's a lot of files! -Don't worry too much right now about what they all are; we are going to walk through the important parts together in the course of this training. +Hopefully you'll recognize a lot of them as the same we came across when we explored the `nf-core/demo` pipeline structure. +But don't worry if you're still feeling a little lost; we'll walk through the important parts together in the course of this training. !!! note One important difference compared to the `nf-core/demo` pipeline we examined in the first part of this training is that there is no `modules` directory. - This is because we didn't include any of the default nf-core modules. + This is because we didn't elect to include any of the default nf-core modules. ### 1.2. Test that the scaffold is functional @@ -159,37 +160,39 @@ Believe it or not, even though you haven't yet added any modules to make it do r nextflow run ./core-hello -profile docker,test --outdir core-hello-results ``` -```console title="Output" - N E X T F L O W ~ version 25.04.3 - -Launching `./core-hello/main.nf` [insane_davinci] DSL2 - revision: b9e9b3b8de - -Downloading plugin nf-schema@2.5.1 -Input/output options - input : https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/samplesheet/samplesheet_test_illumina_amplicon.csv - outdir : core-hello-results - -Institutional config options - config_profile_name : Test profile - config_profile_description: Minimal test dataset to check pipeline function - -Generic options - trace_report_suffix : 2025-10-30_15-45-16 - -Core Nextflow options - runName : insane_davinci - containerEngine : docker - launchDir : /workspaces/training/hello-nf-core - workDir : /workspaces/training/hello-nf-core/work - projectDir : /workspaces/training/hello-nf-core/core-hello - userName : root - profile : docker,test - configFiles : /workspaces/training/hello-nf-core/core-hello/nextflow.config - -!! Only displaying parameters that differ from the pipeline defaults !! ------------------------------------------------------- --[core/hello] Pipeline completed successfully- -``` +??? example "Output" + + ```console + N E X T F L O W ~ version 25.04.3 + + Launching `./core-hello/main.nf` [insane_davinci] DSL2 - revision: b9e9b3b8de + + Downloading plugin nf-schema@2.5.1 + Input/output options + input : https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/samplesheet/samplesheet_test_illumina_amplicon.csv + outdir : core-hello-results + + Institutional config options + config_profile_name : Test profile + config_profile_description: Minimal test dataset to check pipeline function + + Generic options + trace_report_suffix : 2025-10-30_15-45-16 + + Core Nextflow options + runName : insane_davinci + containerEngine : docker + launchDir : /workspaces/training/hello-nf-core + workDir : /workspaces/training/hello-nf-core/work + projectDir : /workspaces/training/hello-nf-core/core-hello + userName : root + profile : docker,test + configFiles : /workspaces/training/hello-nf-core/core-hello/nextflow.config + + !! Only displaying parameters that differ from the pipeline defaults !! + ------------------------------------------------------ + -[core/hello] Pipeline completed successfully- + ``` This shows you that all the basic wiring is in place. You can take a look at the reports in the `pipeline_info` directory to see what was run; not much at all! @@ -202,7 +205,8 @@ You can take a look at the reports in the `pipeline_info` directory to see what ### 1.3. Examine the placeholder workflow If you look inside the `main.nf` file, you'll see it imports a workflow called `HELLO` from `workflows/hello`. -This is a placeholder workflow for our workflow of interest, with some nf-core functionality already in place. + +This is equivalent to the `workflows/demo.nf` workflow we encountered in Part 1, and serves as a placeholder workflow for our workflow of interest, with some nf-core functionality already in place. ```groovy title="core-hello/workflows/hello.nf" linenums="1" hl_lines="15 17 19 35" /* @@ -279,21 +283,26 @@ Learn how to make a simple workflow composable as a prelude to making it nf-core ## 2. Make the original Hello Nextflow workflow composable -Before we can integrate our workflow into the nf-core scaffold, we need to make it **composable**. -A composable workflow must be called from a parent workflow. It cannot run on its own, which is exactly how the nf-core template is structured. +Now it's time to get to work integrating our workflow into the nf-core scaffold. +As a reminder, we're working with the workflow featured in our [Hello Nextflow](../hello_nextflow/index.md) training course. + +??? example "What does the Hello Nextflow workflow do?" + + If you haven't done the [Hello Nextflow](../hello_nextflow/index.md) training, here's a quick overview of what this simple workflow does. -### What does the Hello Nextflow workflow do? + The workflow takes a CSV file containing greetings, runs four consecutive transformation steps on them, and outputs a single text file containing an ASCII picture of a fun character saying the greetings. -If you haven't completed the [Hello Nextflow](../hello_nextflow/index.md) training, here's a quick overview of what this simple workflow does: + The four steps are implemented as Nextflow processes (`sayHello`, `convertToUpper`, `collectGreetings`, and `cowpy`) stored in separate module files. -1. **Reads greetings** from a CSV file (e.g., "Hello", "Bonjour", "Holà") -2. **Writes each greeting** to its own output file (e.g., "Hello-output.txt") -3. **Converts to uppercase** (e.g., "HELLO") -4. **Collects all uppercase greetings** into a single batch file -5. **Adds ASCII art** using cowpy to display the collected greetings with a fun character + -The workflow takes a CSV file containing greetings, runs four consecutive steps to transform the greetings, and writes them out as part of an ASCII picture. -These four steps are implemented as modularized Nextflow processes (`sayHello`, `convertToUpper`, `collectGreetings`, and `cowpy`) organized into separate module files. + 1. **`sayHello`:** Writes each greeting to its own output file (e.g., "Hello-output.txt") + 2. **`convertToUpper`:** Converts each greeting to uppercase (e.g., "HELLO") + 3. **`collectGreetings`:** Collects all uppercase greetings into a single batch file + 4. **`cowpy`:** Generates ASCII art using the `cowpy` tool + +Importantly, the original Hello Nextflow was written as a simple unnamed workflow that can be run on its own. +In order to make it runnable from within a parent workflow as the nf-core template requires, we need to make it **composable**. We provide you with a clean, fully functional copy of the completed Hello Nextflow workflow in the directory `original-hello` along with its modules and the default CSV file it expects to use as input. @@ -320,20 +329,22 @@ Feel free to run it to satisfy yourself that it works: nextflow run original-hello/hello.nf ``` -```console title="Output" - N E X T F L O W ~ version 25.04.3 +??? example "Output" -Launching `original-hello/hello.nf` [goofy_babbage] DSL2 - revision: e9e72441e9 + ```console + N E X T F L O W ~ version 25.04.3 -executor > local (8) -[a4/081cec] sayHello (1) | 3 of 3 ✔ -[e7/7e9058] convertToUpper (3) | 3 of 3 ✔ -[0c/17263b] collectGreetings | 1 of 1 ✔ -[94/542280] cowpy | 1 of 1 ✔ -There were 3 greetings in this batch -``` + Launching `original-hello/hello.nf` [goofy_babbage] DSL2 - revision: e9e72441e9 -Open the `hello.nf` workflow file to inspect the code, which is shown in full below (not counting the processes, which are in modules): + executor > local (8) + [a4/081cec] sayHello (1) | 3 of 3 ✔ + [e7/7e9058] convertToUpper (3) | 3 of 3 ✔ + [0c/17263b] collectGreetings | 1 of 1 ✔ + [94/542280] cowpy | 1 of 1 ✔ + There were 3 greetings in this batch + ``` + +Now let's open the `hello.nf` workflow file to inspect the code, which is shown in full below (not counting the processes, which are in modules): ```groovy title="original-hello/hello.nf" linenums="1" #!/usr/bin/env nextflow @@ -594,19 +605,21 @@ nextflow run ./original-hello If you made all the changes correctly, this should run to completion. -```console title="Output" - N E X T F L O W ~ version 25.04.3 +??? example "Output" -Launching `original-hello/main.nf` [friendly_wright] DSL2 - revision: 1ecd2d9c0a + ```console + N E X T F L O W ~ version 25.04.3 -executor > local (8) -[24/c6c0d8] HELLO:sayHello (3) | 3 of 3 ✔ -[dc/721042] HELLO:convertToUpper (3) | 3 of 3 ✔ -[48/5ab2df] HELLO:collectGreetings | 1 of 1 ✔ -[e3/693b7e] HELLO:cowpy | 1 of 1 ✔ -There were 3 greetings in this batch -Output: /workspaces/training/hello-nf-core/work/e3/693b7e48dc119d0c54543e0634c2e7/cowpy-COLLECTED-test-batch-output.txt -``` + Launching `original-hello/main.nf` [friendly_wright] DSL2 - revision: 1ecd2d9c0a + + executor > local (8) + [24/c6c0d8] HELLO:sayHello (3) | 3 of 3 ✔ + [dc/721042] HELLO:convertToUpper (3) | 3 of 3 ✔ + [48/5ab2df] HELLO:collectGreetings | 1 of 1 ✔ + [e3/693b7e] HELLO:cowpy | 1 of 1 ✔ + There were 3 greetings in this batch + Output: /workspaces/training/hello-nf-core/work/e3/693b7e48dc119d0c54543e0634c2e7/cowpy-COLLECTED-test-batch-output.txt + ``` This means we've successfully upgraded our HELLO workflow to be composable. @@ -623,12 +636,11 @@ Learn how to graft a basic composable workflow onto the nf-core scaffold. ## 3. Fit the updated workflow logic into the placeholder workflow Now that we've verified our composable workflow works correctly, let's return to the nf-core pipeline scaffold we created in section 1. -We're going to integrate the composable workflow we just developed into the nf-core template structure. +We want to integrate the composable workflow we just developed into the nf-core template structure, so the end result should look something like this. -This is the current content of the `HELLO` workflow in `core-hello/workflows/hello.nf` (the nf-core scaffold). -Overall this code does very little aside from some housekeeping that has to do with capturing the version of any software tools that get run in the pipeline. + -We need to add the relevant code from the composable version of the original workflow that we developed in section 2. +So how do we make that happen? Let's have a look at the current content of the `HELLO` workflow in `core-hello/workflows/hello.nf` (the nf-core scaffold). ```groovy title="core-hello/workflows/hello.nf" linenums="1" /* @@ -677,6 +689,10 @@ workflow HELLO { */ ``` +Overall this code does very little aside from some housekeeping that has to do with capturing the version of any software tools that get run in the pipeline. + +We need to add the relevant code from the composable version of the original workflow that we developed in section 2. + We're going to tackle this in the following stages: 1. Copy over the modules and set up module imports @@ -806,7 +822,9 @@ We need to copy this code into the new version of the workflow, with a few modif - Omit the `main:` keyword (it's already there) - Remove the `.view` line (line 776 above) - this was just for console output in the standalone version -There is already some code in there that has to do with capturing the versions of the tools that get run by the workflow. We're going to leave that alone for now (we'll deal with the tool versions later). We'll keep the `ch_versions = channel.empty()` initialization at the top, then insert our workflow logic, keeping the version collation code at the end. This ordering makes sense because in a real pipeline, the processes would emit version information that would be mixed into the `ch_versions` channel as the workflow runs. +There is already some code in there that has to do with capturing the versions of the tools that get run by the workflow. We're going to leave that alone for now (we'll deal with the tool versions later). +We'll keep the `ch_versions = channel.empty()` initialization at the top, then insert our workflow logic, keeping the version collation code at the end. +This ordering makes sense because in a real pipeline, the processes would emit version information that would be added to the `ch_versions` channel as the workflow runs. === "After" @@ -899,6 +917,9 @@ Finally, we need to update the `emit` block to include the declaration of the wo ``` This concludes the modifications we need to make to the HELLO workflow itself. +At this point, we have achieved the overall code structure we set out to implement. + + ### Takeaway @@ -910,11 +931,11 @@ Learn how to adapt how the inputs are handled in the nf-core pipeline scaffold. --- -Now that we've successfully integrated our workflow logic into the nf-core scaffold, we need to address one more critical piece: ensuring that our input data is processed correctly. -The nf-core template comes with sophisticated input handling designed for complex genomics datasets, but we can adapt it to work with our simpler `greetings.csv` file. - ## 4. Adapt the input handling +Now that we've successfully integrated our workflow logic into the nf-core scaffold, we need to address one more critical piece: ensuring that our input data is processed correctly. +The nf-core template comes with sophisticated input handling designed for complex genomics datasets, so we need to adapt it to work with our simpler `greetings.csv` file. + ### 4.1. Identify where inputs are handled The first step is to figure out where the input handling is done. @@ -1143,6 +1164,8 @@ Under `core-hello/conf`, we find two templated test profiles: `test.config` and Given the purpose of our pipeline, there's not really a point to setting up a full-size test profile, so feel free to ignore or delete `test_full.config`. We're going to focus on setting up `test.config` to run on our `greetings.csv` file with a few default parameters. +### 4.3.1. Copy over the `greetings.csv` file + First we need to copy the `greetings.csv` file to an appropriate place in our pipeline project. Typically small test files are stored in the `assets` directory, so let's copy the file over from our working directory. @@ -1150,6 +1173,10 @@ Typically small test files are stored in the `assets` directory, so let's copy t cp greetings.csv core-hello/assets/. ``` +Now the `greetings.csv` file is ready to be used as test input. + +### 4.3.2. Update the `test.config` file + Now we can update the `test.config` file as follows: === "After" @@ -1227,42 +1254,44 @@ nextflow run core-hello --outdir core-hello-results -profile test,docker --valid If you've done all of the modifications correctly, it should run to completion. -```console title="Output" - N E X T F L O W ~ version 25.04.3 - -Launching `core-hello/main.nf` [small_torvalds] DSL2 - revision: b9e9b3b8de - -Input/output options - input : /workspaces/training/hello-nf-core/core-hello/assets/greetings.csv - outdir : core-hello-results - -Institutional config options - config_profile_name : Test profile - config_profile_description: Minimal test dataset to check pipeline function - -Generic options - validate_params : false - trace_report_suffix : 2025-10-30_18-05-47 - -Core Nextflow options - runName : small_torvalds - containerEngine : docker - launchDir : /workspaces/training/hello-nf-core - workDir : /workspaces/training/hello-nf-core/work - projectDir : /workspaces/training/hello-nf-core/core-hello - userName : root - profile : test,docker - configFiles : /workspaces/training/hello-nf-core/core-hello/nextflow.config - -!! Only displaying parameters that differ from the pipeline defaults !! ------------------------------------------------------- -executor > local (8) -[da/fe2e20] COR…LLO:sayHello (1) | 3 of 3 ✔ -[f5/4e47cf] COR…nvertToUpper (2) | 3 of 3 ✔ -[22/61caea] COR…collectGreetings | 1 of 1 ✔ -[a8/de5051] COR…ELLO:HELLO:cowpy | 1 of 1 ✔ --[core/hello] Pipeline completed successfully- -``` +??? example "Output" + + ```console + N E X T F L O W ~ version 25.04.3 + + Launching `core-hello/main.nf` [small_torvalds] DSL2 - revision: b9e9b3b8de + + Input/output options + input : /workspaces/training/hello-nf-core/core-hello/assets/greetings.csv + outdir : core-hello-results + + Institutional config options + config_profile_name : Test profile + config_profile_description: Minimal test dataset to check pipeline function + + Generic options + validate_params : false + trace_report_suffix : 2025-10-30_18-05-47 + + Core Nextflow options + runName : small_torvalds + containerEngine : docker + launchDir : /workspaces/training/hello-nf-core + workDir : /workspaces/training/hello-nf-core/work + projectDir : /workspaces/training/hello-nf-core/core-hello + userName : root + profile : test,docker + configFiles : /workspaces/training/hello-nf-core/core-hello/nextflow.config + + !! Only displaying parameters that differ from the pipeline defaults !! + ------------------------------------------------------ + executor > local (8) + [da/fe2e20] COR…LLO:sayHello (1) | 3 of 3 ✔ + [f5/4e47cf] COR…nvertToUpper (2) | 3 of 3 ✔ + [22/61caea] COR…collectGreetings | 1 of 1 ✔ + [a8/de5051] COR…ELLO:HELLO:cowpy | 1 of 1 ✔ + -[core/hello] Pipeline completed successfully- + ``` As you can see, this produced the typical nf-core summary at the start thanks to the initialisation subworkflow, and the lines for each module now show the full PIPELINE:WORKFLOW:module names. @@ -1322,6 +1351,8 @@ tree core-hello-results In our case, we didn't explicitly mark anything else as an output, so there's nothing else there. + + And there it is! It may seem like a lot of work to accomplish the same result as the original pipeline, but you do get all those lovely reports generated automatically, and you now have a solid foundation for taking advantage of additional features of nf-core, including input validation and some neat metadata handling capabilities that we'll cover in a later section. --- From 4953af32fba80ca93c478128b1a33070bfd55a3d Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Mon, 10 Nov 2025 14:33:49 -0500 Subject: [PATCH 108/113] Header formatting fix --- docs/hello_nf-core/02_rewrite_hello.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index 28c00defcc..49f87596f4 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -1164,7 +1164,7 @@ Under `core-hello/conf`, we find two templated test profiles: `test.config` and Given the purpose of our pipeline, there's not really a point to setting up a full-size test profile, so feel free to ignore or delete `test_full.config`. We're going to focus on setting up `test.config` to run on our `greetings.csv` file with a few default parameters. -### 4.3.1. Copy over the `greetings.csv` file +#### 4.3.1. Copy over the `greetings.csv` file First we need to copy the `greetings.csv` file to an appropriate place in our pipeline project. Typically small test files are stored in the `assets` directory, so let's copy the file over from our working directory. @@ -1175,7 +1175,7 @@ cp greetings.csv core-hello/assets/. Now the `greetings.csv` file is ready to be used as test input. -### 4.3.2. Update the `test.config` file +#### 4.3.2. Update the `test.config` file Now we can update the `test.config` file as follows: From 59610eaa6499d3c2d158d66c4946b312cea643ca Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Tue, 11 Nov 2025 02:52:39 -0500 Subject: [PATCH 109/113] Improvements to Part 3 (add a module) Made minor rearrangements to the order of the content, broke it down into 3 subsections, fleshed out some of the explanations to be more accessible to less experienced developers. Also added a screenshot. Planning a diagram for additional clarity. --- docs/hello_nf-core/03_use_module.md | 631 +++++++++++------- .../img/module-search-results.png | Bin 0 -> 425604 bytes 2 files changed, 402 insertions(+), 229 deletions(-) create mode 100644 docs/hello_nf-core/img/module-search-results.png diff --git a/docs/hello_nf-core/03_use_module.md b/docs/hello_nf-core/03_use_module.md index 8b89daa9ab..562456bfff 100644 --- a/docs/hello_nf-core/03_use_module.md +++ b/docs/hello_nf-core/03_use_module.md @@ -2,18 +2,20 @@ In this third part of the Hello nf-core training course, we show you how to find, install, and use an existing nf-core module in your pipeline. -One of the great advantages of nf-core pipelines is the ability to leverage pre-built, tested modules from the [nf-core/modules](https://github.com/nf-core/modules) repository. Rather than writing every process from scratch, you can install and use community-maintained modules that follow best practices. You can browse available modules at [nf-co.re/modules](https://nf-co.re/modules). +One of the great benefits of working with nf-core is the ability to leverage pre-built, tested modules from the [nf-core/modules](https://github.com/nf-core/modules) repository. +Rather than writing every process from scratch, you can install and use community-maintained modules that follow best practices. -In this section, we'll replace the custom `collectGreetings` module with the `cat/cat` module from nf-core/modules. +To demonstrate how this works, we'll replace the custom `collectGreetings` module with the `cat/cat` module from nf-core/modules in the `core-hello` pipeline. !!! note This section assumes you have completed [Part 2: Rewrite Hello for nf-core](./02_rewrite_hello.md) and have a working `core-hello` pipeline. + If you did not complete Part 2 or want to start fresh for this section, you can use the `core-hello-part2` solution as your starting point. - If you didn't complete Part 2 or want to start fresh for this section, you can use the `core-hello-part2` solution as your starting point: + Run this command from within the `hello-nf-core/` directory: ```bash - cp -r hello-nf-core/solutions/core-hello-part2 core-hello + cp -r solutions/core-hello-part2 core-hello cd core-hello ``` @@ -21,42 +23,52 @@ In this section, we'll replace the custom `collectGreetings` module with the `ca --- -## 1. Use an nf-core module +## 1. Find and install a suitable nf-core module -First, let's learn how to find, install, and use an existing nf-core module in our pipeline. +First, let's learn how to find an existing nf-core module and install it into our pipeline. -The `collectGreetings` process in our pipeline uses the Unix `cat` command to concatenate multiple greeting files into one. -This is a perfect use case for the nf-core `cat/cat` module, which is designed specifically for concatenating files. -Replacing our custom module with an nf-core module gives us the benefit of community testing and maintenance, while also demonstrating the module system. +We'll aim to replace the `collectGreetings` process, which uses the Unix `cat` command to concatenate multiple greeting files into one. +Concatenating files is a very common operation, so it stands to reason that there might already be a module in nf-core designed for that purpose. -!!! note "Module naming convention" - - nf-core modules follow the naming convention `software/command` when a tool provides multiple commands, like `samtools/view` (samtools package, view command) or `gatk/haplotypecaller` (GATK package, HaplotypeCaller command). For tools that provide only one main command, modules use a single level like `fastqc` or `multiqc`. The `cat/cat` naming reflects the organizational structure in the modules repository. +Let's dive in. ### 1.1. Browse available modules on the nf-core website The nf-core project maintains a centralized catalog of modules at [https://nf-co.re/modules](https://nf-co.re/modules). -Navigate to the modules page in your web browser and use the search bar to search for "cat_cat". +Navigate to the modules page in your web browser and use the search bar to search for 'concatenate'. + +![module search results](./img/module-search-results.png) + +As you can see, there are quite a few results, many of them modules designed to concatenate very specific types of files. +Among them, you should see one called `cat_cat` that is general-purpose. + +!!! note "Module naming convention" + + The underscore (`_`) is used as a stand-in for the slash (`/`) character in module names. + + nf-core modules follow the naming convention `software/command` when a tool provides multiple commands, like `samtools/view` (samtools package, view command) or `gatk/haplotypecaller` (GATK package, HaplotypeCaller command). + For tools that provide only one main command, modules use a single level like `fastqc` or `multiqc`. -You should see `cat/cat` in the search results. Click on it to view the module documentation. +Click on the `cat_cat` module box to view the module documentation. The module page shows: -- A description: "A module for concatenation of gzipped or uncompressed files" +- A short description: "A module for concatenation of gzipped or uncompressed files" - Installation command: `nf-core modules install cat/cat` - Input and output channel structure - Available parameters ### 1.2. List available modules from the command line -You can also search for modules directly from the command line using nf-core tools. +Alternatively, you can also search for modules directly from the command line using nf-core tools. ```bash nf-core modules list remote ``` -This will display a list of all available modules in the nf-core/modules repository. You can scroll through or pipe to `grep` to find specific modules: +This will display a list of all available modules in the nf-core/modules repository, though it's a little less convenient if you don't already know the name of the module you're searching for. +However, if you do, you can pipe the list to `grep` to find specific modules: ```bash nf-core modules list remote | grep 'cat/cat' @@ -66,17 +78,88 @@ nf-core modules list remote | grep 'cat/cat' cat/cat ``` +Just keep in mind the that `grep` approach will only pull out results with the search term in their name, which would not work for `cat_cat`. + ### 1.3. Get detailed information about the module -To see detailed information about a specific module, use the `info` command: +To see detailed information about a specific module from the command line, use the `info` command: ```bash nf-core modules info cat/cat ``` -This displays documentation about the module, including its inputs, outputs, and basic usage information: +This displays documentation about the module, including its inputs, outputs, and basic usage information. + +??? example "Output" + +````console + + ,--./,-. + ___ __ __ __ ___ /,-._.--~\ + |\ | |__ __ / ` / \ |__) |__ } { + | \| | \__, \__/ | \ |___ \`-._,-`-, + `._,._,' + + nf-core/tools version 3.4.1 - https://nf-co.re + + + ╭─ Module: cat/cat ─────────────────────────────────────────────────╮ + │ 🌐 Repository: https://github.com/nf-core/modules.git │ + │ 🔧 Tools: cat │ + │ 📖 Description: A module for concatenation of gzipped or │ + │ uncompressed files │ + ╰────────────────────────────────────────────────────────────────────╯ + ╷ ╷ + 📥 Inputs │Description │Pattern + ╺━━━━━━━━━━━━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┿━━━━━━━╸ + input[0] │ │ + ╶─────────────────┼──────────────────────────────────────────┼───────╴ + meta (map) │Groovy Map containing sample information │ + │e.g. [ id:'test', single_end:false ] │ + ╶─────────────────┼──────────────────────────────────────────┼───────╴ + files_in (file)│List of compressed / uncompressed files │ * + ╵ ╵ + ╷ ╷ + 📥 Outputs │Description │ Pattern + ╺━━━━━━━━━━━━━━━━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┿━━━━━━━━━━━━╸ + file_out │ │ + ╶─────────────────────┼─────────────────────────────────┼────────────╴ + meta (map) │Groovy Map containing sample │ + │information │ + ╶─────────────────────┼─────────────────────────────────┼────────────╴ + ${prefix} (file) │Concatenated file. Will be │ ${file_out} + │gzipped if file_out ends with │ + │".gz" │ + ╶─────────────────────┼─────────────────────────────────┼────────────╴ + versions │ │ + ╶─────────────────────┼─────────────────────────────────┼────────────╴ + versions.yml (file)│File containing software versions│versions.yml + ╵ ╵ + + 💻 Installation command: nf-core modules install cat/cat + ``` -```console title="Output" +This is the exact same information you can find on the website. + +### 1.4. Install the cat/cat module + +Now that we've found the module we want, we need to add it to our pipeline's source code. + +The good news is that the nf-core project includes some tooling to make this part easy. +Specifically, the `nf-core modules install` command makes it possible to automate retrieving the code and making it available to your project in a single step. + +Navigate to your pipeline directory and run the installation command: + +```bash +cd core-hello +nf-core modules install cat/cat +```` + +The tool will first prompt you to specify a repository type. + +??? example "Output" + +````console ,--./,-. ___ __ __ __ ___ /,-._.--~\ @@ -87,63 +170,35 @@ This displays documentation about the module, including its inputs, outputs, and nf-core/tools version 3.4.1 - https://nf-co.re -╭─ Module: cat/cat ─────────────────────────────────────────────────╮ -│ 🌐 Repository: https://github.com/nf-core/modules.git │ -│ 🔧 Tools: cat │ -│ 📖 Description: A module for concatenation of gzipped or │ -│ uncompressed files │ -╰────────────────────────────────────────────────────────────────────╯ - ╷ ╷ - 📥 Inputs │Description │Pattern -╺━━━━━━━━━━━━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┿━━━━━━━╸ - input[0] │ │ -╶─────────────────┼──────────────────────────────────────────┼───────╴ - meta (map) │Groovy Map containing sample information │ - │e.g. [ id:'test', single_end:false ] │ -╶─────────────────┼──────────────────────────────────────────┼───────╴ - files_in (file)│List of compressed / uncompressed files │ * - ╵ ╵ - ╷ ╷ - 📥 Outputs │Description │ Pattern -╺━━━━━━━━━━━━━━━━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┿━━━━━━━━━━━━╸ - file_out │ │ -╶─────────────────────┼─────────────────────────────────┼────────────╴ - meta (map) │Groovy Map containing sample │ - │information │ -╶─────────────────────┼─────────────────────────────────┼────────────╴ - ${prefix} (file) │Concatenated file. Will be │ ${file_out} - │gzipped if file_out ends with │ - │".gz" │ -╶─────────────────────┼─────────────────────────────────┼────────────╴ - versions │ │ -╶─────────────────────┼─────────────────────────────────┼────────────╴ - versions.yml (file)│File containing software versions│versions.yml - ╵ ╵ - - 💻 Installation command: nf-core modules install cat/cat -``` - -### 1.4. Install and verify the cat/cat module + WARNING 'repository_type' not defined in .nf-core.yml + ? Is this repository a pipeline or a modules repository? (Use arrow keys) + » Pipeline + Modules repository -!!! note +Press enter to accept the default response (`Pipeline`) and continue. - Make sure you are in the `core-hello` directory (your pipeline root) in your terminal before running the module installation command. +The tool will then offer to amend the configuration of your project to avoid this prompt in the future. -Navigate to your pipeline directory and run the installation command: +??? example "Output" +`console + INFO To avoid this prompt in the future, add the 'repository_type' key to your .nf-core.yml file. + ? Would you like me to add this config now? [y/n] (y): + ` -```bash -cd core-hello -nf-core modules install cat/cat -``` +Might as well take advantage of this convenient tooling! +Press enter to accept the default response (yes). -The tool will prompt you to confirm the installation. Press Enter to accept the default options. +Finally, the tool will proceed to install the module. -```console title="Output" -INFO Installing 'cat/cat' -INFO Use the following statement to include this module: +??? example "Output" +```console +INFO Config added to '.nf-core.yml' +INFO Reinstalling modules found in 'modules.json' but missing from directory: +INFO Installing 'cat/cat' +INFO Use the following statement to include this module: - include { CAT_CAT } from '../modules/nf-core/cat/cat/main' -``` + include { CAT_CAT } from '../modules/nf-core/cat/cat/main' + ``` The command automatically: @@ -151,30 +206,37 @@ The command automatically: - Updates `modules.json` to track the installed module - Provides you with the correct `include` statement to use in your workflow +!!! note + + Always make sure your current working directory is the root of your pipeline project before running the module installation command. + Let's check that the module was installed correctly: ```bash -tree modules/nf-core/cat -``` +tree -L 4 modules +```` ??? example "Directory contents" ```console - modules/nf-core/cat - └── cat - ├── environment.yml - ├── main.nf - ├── meta.yml - └── tests - ├── main.nf.test - ├── main.nf.test.snap - ├── nextflow_unzipped_zipped.config - └── nextflow_zipped_unzipped.config - - 2 directories, 7 files + modules + ├── local + │ ├── collectGreetings.nf + │ ├── convertToUpper.nf + │ ├── cowpy.nf + │ └── sayHello.nf + └── nf-core + └── cat + └── cat + ├── environment.yml + ├── main.nf + ├── meta.yml + └── tests + + 5 directories, 7 files ``` -You can also verify the installation by listing locally installed modules: +You can also verify the installation by asking the nf-core utility to list locally installed modules: ```bash nf-core modules list local @@ -191,15 +253,27 @@ INFO Modules installed in '.': └─────────────┴─────────────────┴─────────────┴────────────────────────────────────────┴────────────┘ ``` -### 1.5. Add the import statement to your workflow +This confirms that the `cat/cat` module is now part of your project's source code. -Open [core-hello/workflows/hello.nf](core-hello/workflows/hello.nf) and add the `include` statement for the `CAT_CAT` module in the imports section. +However, to actually use the new module, we need to import it into our pipeline. -The nf-core convention is to use uppercase for module names when importing them. +### 1.5. Update the module imports + +Let's replace the `include` statement for the `collectGreetings` module with the one for `CAT_CAT` in the imports section of the `workflows/hello.nf` workflow. + +As a reminder, the module install tool gave us the exact statement to use: + +```groovy title="Import statement produced by install command" +include { CAT_CAT } from '../modules/nf-core/cat/cat/main'` +``` + +Note that the nf-core convention is to use uppercase for module names when importing them. + +Open up [core-hello/workflows/hello.nf](core-hello/workflows/hello.nf) and make the following substitution: === "After" - ```groovy title="core-hello/workflows/hello.nf" linenums="1" hl_lines="12" + ```groovy title="core-hello/workflows/hello.nf" linenums="1" hl_lines="10" /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS @@ -209,14 +283,13 @@ The nf-core convention is to use uppercase for module names when importing them. include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' include { sayHello } from '../modules/local/sayHello.nf' include { convertToUpper } from '../modules/local/convertToUpper.nf' - include { collectGreetings } from '../modules/local/collectGreetings.nf' - include { cowpy } from '../modules/local/cowpy.nf' include { CAT_CAT } from '../modules/nf-core/cat/cat/main' + include { cowpy } from '../modules/local/cowpy.nf' ``` === "Before" - ```groovy title="core-hello/workflows/hello.nf" linenums="1" + ```groovy title="core-hello/workflows/hello.nf" linenums="1" hl_lines="10" /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS @@ -230,20 +303,66 @@ The nf-core convention is to use uppercase for module names when importing them. include { cowpy } from '../modules/local/cowpy.nf' ``` -Note how the path for the nf-core module differs from the local modules: +Notice how the path for the nf-core module differs from the local modules: -- **nf-core module**: `'../modules/nf-core/cat/cat/main'` (includes the tool name twice and references `main.nf`) +- **nf-core module**: `'../modules/nf-core/cat/cat/main'` (references `main.nf`) - **Local module**: `'../modules/local/collectGreetings.nf'` (single file reference) -### 1.6. Examine the cat/cat module interface +The module is now available to the workflow, so all we need to do is swap out the call to `collectGreetings` to use `CAT_CAT`. Right? -Let's look at the `cat/cat` module's main.nf file to understand its interface: +Not so fast. -```bash -head -30 modules/nf-core/cat/cat/main.nf +At this point, you might be tempted to dive in and start editing code, but it's worth taking a moment to examine carefully what the new module expects and what it produces. + +We're going to tackle that as a separate section because it involves a new mechanism we haven't covered yet: metadata maps. + +### Takeaway + +You know how to find an nf-core module and make it available to your project. + +### What's next? + +Assess what a new module requires and identify any important changes needed in order to integrate it into a pipeline. + +--- + +## 2. Assess the requirements of the new module + +Specifically, we need to examine the **interface** of the module, i.e. its input and output definitions, and compare it to the interface of the module we're seeking to replace. +This will allow us to determine whether we can just treat the new module as a drop-in replacement or whether we'll need to adapt some of the wiring. + +Ideally this is something you should do _before_ you even install the module, but hey, better late than never. +(For what it's worth, there is an `uninstall` command to get rid of modules you decide you no longer want.) + +!!! note + + The CAT_CAT process includes some rather clever handling of different compression types, file extensions and so on that aren't strictly relevant to what we're trying to show you here, so we'll ignore most of it and focus only on the parts that are important. + +### 2.1. Compare the two modules' interfaces + +As a reminder, this is what the interface to our `collectGreetings` module looks like: + +```groovy title="modules/local/collectGreetings.nf (excerpt)" linenums="1" hl_lines="6-7 10" +process collectGreetings { + + publishDir 'results', mode: 'copy' + + input: + path input_files + val batch_name + + output: + path "COLLECTED-${batch_name}-output.txt" , emit: outfile ``` -The key parts of the module are: +The `collectGreetings` module takes two inputs: + +- `input_files` contains one or more input files to process; +- `batch_name` is a value that we use to assign a run-specific name to the output file, which is a form of metadata. + +Upon completion, `collectGreetings` outputs a single file path, emitted with the `outfile` tag. + +In comparison, the `cat/cat` module's interface is more complex: ```groovy title="modules/nf-core/cat/cat/main.nf (excerpt)" linenums="1" hl_lines="11 14" process CAT_CAT { @@ -263,37 +382,51 @@ process CAT_CAT { path "versions.yml" , emit: versions ``` -The module expects: +The CAT_CAT module takes a single input, but that input is a tuple containing two things: -- **Input**: A tuple containing metadata (`meta`) and input file(s) (`files_in`) -- **Output**: A tuple containing metadata and the concatenated output file, plus a versions file +- `meta` is a structure containing metadata, called a metamap; +- `input_files` contains one or more input files to process, equivalent to `collectGreetings`'s `input_files`. -### 1.7. Compare with collectGreetings interface +Upon completion, CAT_CAT delivers its outputs in two parts: -Our custom `collectGreetings` module has a simpler interface: +- Another tuple containing the metamap and the concatenated output file, emitted with the `file_out` tag; +- A `versions.yml` file that captures information about the software version that was used, emitted with the `versions` tag. -```groovy title="modules/local/collectGreetings.nf (excerpt)" linenums="1" hl_lines="6-7 10" -process collectGreetings { +Note also that by default, the output file will be named based on an identifier that is part of the metadata (code not shown here). - publishDir 'results', mode: 'copy' +This may seem like a lot to keep track of just looking at the code, so here's a diagram to help you visualize how everything fits together. - input: - path input_files - val batch_name + - output: - path "COLLECTED-${batch_name}-output.txt" , emit: outfile -``` +You can see that the two modules have similar input requirements in terms of content (a set of input files plus some metadata) but very different expectations for how that content is packaged. +Ignoring the versions file for now, their main output is equivalent too (a concatenated file), except CAT_CAT also emits the metamap in conjunction with the output file. -The main differences are: +The packaging differences will be fairly easy to deal with, as you'll see in a little bit. +However, to understand the metamap part, we need to introduce you to some additional context. -- `CAT_CAT` requires a metadata map (`tuple val(meta), path(files_in)`), while `collectGreetings` takes separate `path` and `val` inputs -- `CAT_CAT` outputs a tuple with metadata, while `collectGreetings` outputs a simple path -- `CAT_CAT` uses `meta.id` for the filename prefix, while `collectGreetings` uses the `batch_name` parameter +### 2.2. Understanding metamaps -### 1.8. Understanding metadata maps +We just told you that the CAT_CAT module expects a metadata map as part of its input tuple. +Let's take a few minutes to take a closer look at what that is. -You've just seen that `CAT_CAT` expects inputs and outputs structured as tuples with metadata: +The **metadata map**, often referred to as **metamap** for short, is a Groovy-style map containing information about units of data. +In the context of Nextflow pipelines, units of data can be anything you want: individual samples, batches of samples, or entire datasets. + +By convention, an nf-core metamap is named `meta` and contains the required field `id`, which is used for naming outputs and tracking units of data. + +For example, a typical metadata map might look like this: + +```groovy title="Example of sample-level metamap" +[id: 'sample1', single_end: false, strandedness: 'forward'] +``` + +Or in a case where the metadata is attached at the batch level: + +```groovy title="Example of batch-level metamap" +[id: 'batch1', date: '25.10.01'] +``` + +Now let's put this in the context of the `CAT_CAT` process, which expects the input files to be packaged into a tuple with a metamap, and outputs the metamap as part of the output tuple as well. ```groovy title="modules/nf-core/cat/cat/main.nf (excerpt)" linenums="1" hl_lines="2 5" input: @@ -303,44 +436,94 @@ output: tuple val(meta), path("${prefix}"), emit: file_out ``` -This pattern is standard across all nf-core modules. The metadata map (commonly called `meta`) is a Groovy-style map containing information about a sample or dataset, with `id` being the required field used for naming outputs and tracking samples. +As a result, every unit of data travels through the pipeline with the relevant metadata attached. +Subsequent processes can then readily access that metadata too. -For example, a typical metadata map might look like: +Remember how we told you that the file output by `CAT_CAT` will be named based on an identifier that is part of the metadata? +This is the relevant code: -```groovy -[id: 'sample1', single_end: false, strandedness: 'forward'] +```groovy title="modules/nf-core/cat/cat/main.nf (excerpt)" linenums="35" +prefix = task.ext.prefix ?: "${meta.id}${getFileSuffix(file_list[0])}" ``` -In this tutorial, we use a simple metadata map with just the batch name: +This translates roughly as follows: if a `prefix` is provided via the external task parameter system (`task.ext`), use that to name the output file; otherwise create one using `${meta.id}`, which corresponds to the `id` field in the metamap. + +You can imagine the input channel coming into this module with contents like this: -```groovy -[id: 'test'] +```groovy title="Example input channel contents" +ch_input = [[[id: 'batch1', date: '25.10.01'], ['file1A.txt', 'file1B.txt']], + [[id: 'batch2', date: '25.10.26'], ['file2A.txt', 'file2B.txt']], + [[id: 'batch3', date: '25.11.14'], ['file3A.txt', 'file3B.txt']]] ``` -Why use metadata maps? +Then the output channel contents coming out like this: -- **Sample tracking**: Keep sample information with data throughout the workflow -- **Standardization**: All nf-core modules follow this pattern -- **Flexibility**: Easy to add custom metadata fields -- **Output naming**: Consistent file naming based on sample IDs +```groovy title="Example output channel contents" +ch_input = [[[id: 'batch1', date: '25.10.01'], 'batch1.txt'], + [[id: 'batch2', date: '25.10.26'], 'batch2.txt'], + [[id: 'batch3', date: '25.11.14'], 'batch3.txt']] +``` + +As mentioned earlier, the `tuple val(meta), path(files_in)` input setup is a standard pattern used across all nf-core modules. + +Hopefully you can start to see how useful this can be. +Not only does it allow you to name outputs based on metadata, but you can also do things like use it to apply different parameter values, and in combination with specific operators, you can even group, sort or filter out data as it flows through the pipeline. !!! note "Learn more about metadata" For a comprehensive introduction to working with metadata in Nextflow workflows, including how to read metadata from samplesheets and use it to customize processing, see the [Metadata in workflows](../side_quests/metadata) side quest. -For now, we'll pass the output from `CAT_CAT` to `cowpy` with the character parameter. In the next section, we'll adapt `cowpy` to follow nf-core conventions. +### 2.3. Summarize changes to be made + +Based on what we've reviewed, these are the major changes we need to make to our pipeline to utilize the `cat/cat` module: -### 1.9. Wire up CAT_CAT in the workflow +- Create a metamap containing the batch name; +- Package the metamap into a tuple with the set of input files to concatenate (coming out of `convertToUpper`); +- Switch the call from `collectGreetings()` to `CAT_CAT`; +- Extract the output file from the tuple produced by the `CAT_CAT` process before passing it to `cowpy`. -Now we need to modify our workflow code to use `CAT_CAT` instead of `collectGreetings`. Since `CAT_CAT` requires metadata tuples, we'll do this in several steps to make it clear how to work with metadata. +That should do the trick! Now that we've got a plan, we're ready to dive in. -Open [core-hello/workflows/hello.nf](core-hello/workflows/hello.nf) and make the following changes to the workflow logic in the `main` block. +### Takeaway -#### 1.9.1. Create a metadata map +You know how to assess the input and output interface of a new module to identify its requirements, and you've learned how metamaps are used by nf-core pipelines to keep metadata closely associated with the data as it flows through a pipeline. -First, we need to create a metadata map for `CAT_CAT`. Remember that nf-core modules require metadata with at least an `id` field. +### What's next? -Add these lines after the `convertToUpper` call, removing the `collectGreetings` call: +Integrate the new module into a workflow. + +--- + +## 3. Integrate CAT_CAT into the `hello.nf` workflow + +Now that you know everything about metamaps (or enough for the purposes of this course, anyway), it's time to actually implement the changes we outlined above. + +For the sake of clarity, we'll break this down and cover each step separately. + +!!! note + + All the changes shown below are made to the workflow logic in the `main` block in the `core-hello/workflows/hello.nf` workflow file. + +### 3.1. Create a metadata map + +First, we need to create a metadata map for `CAT_CAT`, keeping in mind that nf-core modules require the metamap to at least an `id` field. + +Since we don't need any other metadata, we can keep it simple and use something like this: + +```groovy title="Syntax example" +def cat_meta = [id: 'test'] +``` + +Except we don't want to hardcode the `id` value; we want to use the value of the `params.batch` parameter. +So the code becomes: + +```groovy title="Syntax example" +def cat_meta = [id: params.batch] +``` + +Yes, it is literally that simple to create a basic metamap. + +Let's add these lines after the `convertToUpper` call, removing the `collectGreetings` call: === "After" @@ -374,9 +557,9 @@ Add these lines after the `convertToUpper` call, removing the `collectGreetings` cowpy(collectGreetings.out.outfile, params.character) ``` -This creates a simple metadata map where the `id` is set to our batch name (which will be "test" when using the test profile). +This creates a simple metadata map where the `id` is set to our batch name (which will be `test` when using the test profile). -#### 1.9.2. Create a channel with metadata tuples +### 3.2. Create a channel with metadata tuples Next, transform the channel of files into a channel of tuples containing metadata and files: @@ -391,6 +574,7 @@ Next, transform the channel of files into a channel of tuples containing metadat // create metadata map with batch name as the ID def cat_meta = [ id: params.batch ] + // create a channel with metadata and files in tuple format ch_for_cat = convertToUpper.out.collect().map { files -> tuple(cat_meta, files) } @@ -414,14 +598,16 @@ Next, transform the channel of files into a channel of tuples containing metadat cowpy(collectGreetings.out.outfile, params.character) ``` -This line does two things: +The line we've added achieves two things: - `.collect()` gathers all files from the `convertToUpper` output into a single list - `.map { files -> tuple(cat_meta, files) }` creates a tuple of `[metadata, files]` in the format `CAT_CAT` expects -#### 1.9.3. Call CAT_CAT +That is all we need to do to set up the input tuple for `CAT_CAT`. + +### 3.3. Call the CAT_CAT module -Now call `CAT_CAT` with the properly formatted channel: +Now call `CAT_CAT` on the newly created channel: === "After" @@ -434,6 +620,8 @@ Now call `CAT_CAT` with the properly formatted channel: // create metadata map with batch name as the ID def cat_meta = [ id: params.batch ] + + // create a channel with metadata and files in tuple format ch_for_cat = convertToUpper.out.collect().map { files -> tuple(cat_meta, files) } // concatenate files using the nf-core cat/cat module @@ -454,52 +642,22 @@ Now call `CAT_CAT` with the properly formatted channel: // create metadata map with batch name as the ID def cat_meta = [ id: params.batch ] + + // create a channel with metadata and files in tuple format ch_for_cat = convertToUpper.out.collect().map { files -> tuple(cat_meta, files) } // generate ASCII art of the greetings with cowpy cowpy(collectGreetings.out.outfile, params.character) ``` -#### 1.9.4. Remove the legacy collectGreetings import - -Since we're no longer using the `collectGreetings` module, remove its import statement from the top of the file: - -=== "After" - - ```groovy title="core-hello/workflows/hello.nf" linenums="1" hl_lines="10" - /* - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - */ - include { paramsSummaryMap } from 'plugin/nf-schema' - include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' - include { sayHello } from '../modules/local/sayHello.nf' - include { convertToUpper } from '../modules/local/convertToUpper.nf' - include { cowpy } from '../modules/local/cowpy.nf' - include { CAT_CAT } from '../modules/nf-core/cat/cat/main' - ``` - -=== "Before" +This completes the trickiest part of this substitution, but we're not quite done yet: we still need to update how we pass the concatenated output to the `cowpy` process. - ```groovy title="core-hello/workflows/hello.nf" linenums="1" hl_lines="10" - /* - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - */ - include { paramsSummaryMap } from 'plugin/nf-schema' - include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' - include { sayHello } from '../modules/local/sayHello.nf' - include { convertToUpper } from '../modules/local/convertToUpper.nf' - include { collectGreetings } from '../modules/local/collectGreetings.nf' - include { cowpy } from '../modules/local/cowpy.nf' - include { CAT_CAT } from '../modules/nf-core/cat/cat/main' - ``` +### 3.4. Extract the output file from the tuple for `cowpy` -#### 1.9.5. Update cowpy to use CAT_CAT output +Previously, the `collectGreetings` process just produced a file that we could pass to `cowpy` directly. +However, the `CAT_CAT` process produces a tuple that includes the metamap in addition to the output file. -Finally, update the `cowpy` call to use the output from `CAT_CAT`. Since `cowpy` doesn't accept metadata tuples yet (we'll fix this in the next section), we need to extract just the file: +Since `cowpy` doesn't accept metadata tuples yet (we'll fix this in the next section), we need to extract the output file from the tuple produced by `CAT_CAT` before handing it to `cowpy`: === "After" @@ -512,13 +670,17 @@ Finally, update the `cowpy` call to use the output from `CAT_CAT`. Since `cowpy` // create metadata map with batch name as the ID def cat_meta = [ id: params.batch ] + + // create a channel with metadata and files in tuple format ch_for_cat = convertToUpper.out.collect().map { files -> tuple(cat_meta, files) } + // concatenate the greetings CAT_CAT(ch_for_cat) - // generate ASCII art of the greetings with cowpy // extract the file from the tuple since cowpy doesn't use metadata yet ch_for_cowpy = CAT_CAT.out.file_out.map{ meta, file -> file } + + // generate ASCII art of the greetings with cowpy cowpy(ch_for_cowpy, params.character) ``` @@ -533,80 +695,91 @@ Finally, update the `cowpy` call to use the output from `CAT_CAT`. Since `cowpy` // create metadata map with batch name as the ID def cat_meta = [ id: params.batch ] + + // create a channel with metadata and files in tuple format ch_for_cat = convertToUpper.out.collect().map { files -> tuple(cat_meta, files) } + // concatenate the greetings CAT_CAT(ch_for_cat) // generate ASCII art of the greetings with cowpy cowpy(collectGreetings.out.outfile, params.character) ``` -The `.map{ meta, file -> file }` operation extracts just the file from the `[metadata, file]` tuple that `CAT_CAT` outputs. + + +The `.map{ meta, file -> file }` operation extracts the file from the `[metadata, file]` tuple produced by `CAT_CAT` into a new channel, `ch_for_cowpy`. + +Then it's just a matter of passing `ch_for_cowpy` to `cowpy` instead of `collectGreetings.out.outfile`. !!! note - We're extracting just the file from `CAT_CAT`'s output tuple to pass to `cowpy`. In the next section, we'll update `cowpy` to work with metadata tuples directly, so this extraction step won't be necessary. + In the next section, we'll update `cowpy` to work with metadata tuples directly, so this extraction step will no longer be necessary. -### 1.10. Test the workflow +### 3.5. Test the workflow -Let's test that the workflow works with the `cat/cat` module: +Let's test that the workflow works with the newly integrated `cat/cat` module: ```bash nextflow run . --outdir core-hello-results -profile test,docker --validate_params false ``` -```console title="Output" - N E X T F L O W ~ version 25.04.3 - -Launching `./main.nf` [evil_pike] DSL2 - revision: b9e9b3b8de - -Input/output options - input : /workspaces/training/hello-nf-core/core-hello/assets/greetings.csv - outdir : core-hello-results - -Institutional config options - config_profile_name : Test profile - config_profile_description: Minimal test dataset to check pipeline function - -Generic options - validate_params : false - trace_report_suffix : 2025-10-30_18-50-58 - -Core Nextflow options - runName : evil_pike - containerEngine : docker - launchDir : /workspaces/training/hello-nf-core/core-hello - workDir : /workspaces/training/hello-nf-core/core-hello/work - projectDir : /workspaces/training/hello-nf-core/core-hello - userName : root - profile : test,docker - configFiles : /workspaces/training/hello-nf-core/core-hello/nextflow.config - -!! Only displaying parameters that differ from the pipeline defaults !! ------------------------------------------------------- -executor > local (8) -[b3/f005fd] CORE_HELLO:HELLO:sayHello (3) [100%] 3 of 3 ✔ -[08/f923d0] CORE_HELLO:HELLO:convertToUpper (3) [100%] 3 of 3 ✔ -[34/3729a9] CORE_HELLO:HELLO:CAT_CAT (test) [100%] 1 of 1 ✔ -[24/df918a] CORE_HELLO:HELLO:cowpy [100%] 1 of 1 ✔ --[core/hello] Pipeline completed successfully- -``` +This should run reasonably quickly. + +??? example title="Output" + +````console +N E X T F L O W ~ version 25.04.3 + + Launching `./main.nf` [evil_pike] DSL2 - revision: b9e9b3b8de + + Input/output options + input : /workspaces/training/hello-nf-core/core-hello/assets/greetings.csv + outdir : core-hello-results + + Institutional config options + config_profile_name : Test profile + config_profile_description: Minimal test dataset to check pipeline function + + Generic options + validate_params : false + trace_report_suffix : 2025-10-30_18-50-58 + + Core Nextflow options + runName : evil_pike + containerEngine : docker + launchDir : /workspaces/training/hello-nf-core/core-hello + workDir : /workspaces/training/hello-nf-core/core-hello/work + projectDir : /workspaces/training/hello-nf-core/core-hello + userName : root + profile : test,docker + configFiles : /workspaces/training/hello-nf-core/core-hello/nextflow.config + + !! Only displaying parameters that differ from the pipeline defaults !! + ------------------------------------------------------ + executor > local (8) + [b3/f005fd] CORE_HELLO:HELLO:sayHello (3) [100%] 3 of 3 ✔ + [08/f923d0] CORE_HELLO:HELLO:convertToUpper (3) [100%] 3 of 3 ✔ + [34/3729a9] CORE_HELLO:HELLO:CAT_CAT (test) [100%] 1 of 1 ✔ + [24/df918a] CORE_HELLO:HELLO:cowpy [100%] 1 of 1 ✔ + -[core/hello] Pipeline completed successfully- + ``` Notice that `CAT_CAT` now appears in the process execution list instead of `collectGreetings`. +And that's it! We're now using a robust community-curated module instead of custom prototype-grade code for that step in the pipeline. + ### Takeaway You now know how to: - Find and install nf-core modules -- Understand metadata maps and why nf-core modules use them -- Create metadata structures to pass to nf-core modules -- Wire up nf-core modules in your workflow +- Assess the requirements of an nf-core module +- Create a simple metadata map for use with an nf-core module +- Integrate an nf-core module into your workflow ### What's next? -Adapt your local modules to follow nf-core conventions. - ---- - -In [Part 4](./04_make_module.md), we'll show you how to make an nf-core module. +Learn to adapt your local modules to follow nf-core conventions. +We'll also show you how to create new nf-core modules from a template using the nf-core tooling. +```` diff --git a/docs/hello_nf-core/img/module-search-results.png b/docs/hello_nf-core/img/module-search-results.png new file mode 100644 index 0000000000000000000000000000000000000000..149e9a35662d4dfe61796434e2f903045fa6660f GIT binary patch literal 425604 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 )ySft24j{G49HW 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%0Ap4@Dov76`7;vdYj3M*k7GU*Z{6AcmHIJR&m3 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¨>F|Jv2-#x;Ct=2zg-<+#kG&+zp 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 z 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ⅇZ9NM;YH-GJ4%XmH*L<2DjS#cV6`wEi$RS! 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 CgnLB* literal 0 HcmV?d00001 From 6d1b1162e6c5fb4267189db962e265164574b9dd Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Wed, 12 Nov 2025 18:07:19 -0500 Subject: [PATCH 110/113] Partial revamp of Part 4 (some placeholders left) Mainly rearranged the content to make it more accessible. I was a bit confused on my first pass. This breaks things down into more separate buckets of work. Also some formatting fixes to Part 3 --- docs/hello_nf-core/03_use_module.md | 121 +++++---- docs/hello_nf-core/04_make_module.md | 356 +++++++++++++++------------ 2 files changed, 274 insertions(+), 203 deletions(-) diff --git a/docs/hello_nf-core/03_use_module.md b/docs/hello_nf-core/03_use_module.md index 562456bfff..25ddacbd4b 100644 --- a/docs/hello_nf-core/03_use_module.md +++ b/docs/hello_nf-core/03_use_module.md @@ -92,7 +92,7 @@ This displays documentation about the module, including its inputs, outputs, and ??? example "Output" -````console + ```console ,--./,-. ___ __ __ __ ___ /,-._.--~\ @@ -137,6 +137,7 @@ This displays documentation about the module, including its inputs, outputs, and ╵ ╵ 💻 Installation command: nf-core modules install cat/cat + ``` This is the exact same information you can find on the website. @@ -153,13 +154,13 @@ Navigate to your pipeline directory and run the installation command: ```bash cd core-hello nf-core modules install cat/cat -```` +``` The tool will first prompt you to specify a repository type. ??? example "Output" -````console + ```console ,--./,-. ___ __ __ __ ___ /,-._.--~\ @@ -174,16 +175,18 @@ The tool will first prompt you to specify a repository type. ? Is this repository a pipeline or a modules repository? (Use arrow keys) » Pipeline Modules repository + ``` Press enter to accept the default response (`Pipeline`) and continue. The tool will then offer to amend the configuration of your project to avoid this prompt in the future. ??? example "Output" -`console - INFO To avoid this prompt in the future, add the 'repository_type' key to your .nf-core.yml file. - ? Would you like me to add this config now? [y/n] (y): - ` + + ```console + INFO To avoid this prompt in the future, add the 'repository_type' key to your .nf-core.yml file. + ? Would you like me to add this config now? [y/n] (y): + ``` Might as well take advantage of this convenient tooling! Press enter to accept the default response (yes). @@ -191,13 +194,14 @@ Press enter to accept the default response (yes). Finally, the tool will proceed to install the module. ??? example "Output" -```console -INFO Config added to '.nf-core.yml' -INFO Reinstalling modules found in 'modules.json' but missing from directory: -INFO Installing 'cat/cat' -INFO Use the following statement to include this module: - include { CAT_CAT } from '../modules/nf-core/cat/cat/main' + ```console + INFO Config added to '.nf-core.yml' + INFO Reinstalling modules found in 'modules.json' but missing from directory: + INFO Installing 'cat/cat' + INFO Use the following statement to include this module: + + include { CAT_CAT } from '../modules/nf-core/cat/cat/main' ``` The command automatically: @@ -214,7 +218,7 @@ Let's check that the module was installed correctly: ```bash tree -L 4 modules -```` +``` ??? example "Directory contents" @@ -261,6 +265,16 @@ However, to actually use the new module, we need to import it into our pipeline. Let's replace the `include` statement for the `collectGreetings` module with the one for `CAT_CAT` in the imports section of the `workflows/hello.nf` workflow. +!!! note + + You can optionally delete the `collectGreetings.nf` file: + + ```bash + rm modules/local/collectGreetings.nf + ``` + + However, you might want to keep it as a reference for understanding the differences between local and nf-core modules. + As a reminder, the module install tool gave us the exact statement to use: ```groovy title="Import statement produced by install command" @@ -726,43 +740,43 @@ nextflow run . --outdir core-hello-results -profile test,docker --validate_param This should run reasonably quickly. -??? example title="Output" - -````console -N E X T F L O W ~ version 25.04.3 - - Launching `./main.nf` [evil_pike] DSL2 - revision: b9e9b3b8de - - Input/output options - input : /workspaces/training/hello-nf-core/core-hello/assets/greetings.csv - outdir : core-hello-results - - Institutional config options - config_profile_name : Test profile - config_profile_description: Minimal test dataset to check pipeline function - - Generic options - validate_params : false - trace_report_suffix : 2025-10-30_18-50-58 - - Core Nextflow options - runName : evil_pike - containerEngine : docker - launchDir : /workspaces/training/hello-nf-core/core-hello - workDir : /workspaces/training/hello-nf-core/core-hello/work - projectDir : /workspaces/training/hello-nf-core/core-hello - userName : root - profile : test,docker - configFiles : /workspaces/training/hello-nf-core/core-hello/nextflow.config - - !! Only displaying parameters that differ from the pipeline defaults !! - ------------------------------------------------------ - executor > local (8) - [b3/f005fd] CORE_HELLO:HELLO:sayHello (3) [100%] 3 of 3 ✔ - [08/f923d0] CORE_HELLO:HELLO:convertToUpper (3) [100%] 3 of 3 ✔ - [34/3729a9] CORE_HELLO:HELLO:CAT_CAT (test) [100%] 1 of 1 ✔ - [24/df918a] CORE_HELLO:HELLO:cowpy [100%] 1 of 1 ✔ - -[core/hello] Pipeline completed successfully- +??? example "Output" + + ```console + N E X T F L O W ~ version 25.04.3 + + Launching `./main.nf` [evil_pike] DSL2 - revision: b9e9b3b8de + + Input/output options + input : /workspaces/training/hello-nf-core/core-hello/assets/greetings.csv + outdir : core-hello-results + + Institutional config options + config_profile_name : Test profile + config_profile_description: Minimal test dataset to check pipeline function + + Generic options + validate_params : false + trace_report_suffix : 2025-10-30_18-50-58 + + Core Nextflow options + runName : evil_pike + containerEngine : docker + launchDir : /workspaces/training/hello-nf-core/core-hello + workDir : /workspaces/training/hello-nf-core/core-hello/work + projectDir : /workspaces/training/hello-nf-core/core-hello + userName : root + profile : test,docker + configFiles : /workspaces/training/hello-nf-core/core-hello/nextflow.config + + !! Only displaying parameters that differ from the pipeline defaults !! + ------------------------------------------------------ + executor > local (8) + [b3/f005fd] CORE_HELLO:HELLO:sayHello (3) [100%] 3 of 3 ✔ + [08/f923d0] CORE_HELLO:HELLO:convertToUpper (3) [100%] 3 of 3 ✔ + [34/3729a9] CORE_HELLO:HELLO:CAT_CAT (test) [100%] 1 of 1 ✔ + [24/df918a] CORE_HELLO:HELLO:cowpy [100%] 1 of 1 ✔ + -[core/hello] Pipeline completed successfully- ``` Notice that `CAT_CAT` now appears in the process execution list instead of `collectGreetings`. @@ -782,4 +796,7 @@ You now know how to: Learn to adapt your local modules to follow nf-core conventions. We'll also show you how to create new nf-core modules from a template using the nf-core tooling. -```` + +``` + +``` diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index b2f38ebff8..c8efb53836 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -2,16 +2,10 @@ In this fourth part of the Hello nf-core training course, we show you how to create an nf-core module by applying the key conventions that make modules portable and maintainable. -The nf-core project provides a command (`nf-core modules create`) that generates properly structured module templates automatically. +The nf-core project provides a command (`nf-core modules create`) that generates properly structured module templates automatically, similar to what we used for the workflow in Part 2. However, for teaching purposes, we're going to start by doing it manually: transforming the local `cowpy` module in your `core-hello` pipeline into an nf-core-style module step-by-step. After that, we'll show you how to use the template-based module creation to work more efficiently in the future. -We'll apply three essential nf-core patterns incrementally: - -1. **Metadata tuples**: Accept and propagate sample metadata through the workflow -2. **`ext.args`**: Keep the module interface minimal by handling optional tool arguments via configuration rather than as inputs -3. **`ext.prefix`**: Standardize output file naming with configurable prefixes - !!! note This section assumes you have completed [Part 3: Use an nf-core module](./03_use_module.md) and have integrated the `CAT_CAT` module into your pipeline. @@ -27,10 +21,19 @@ We'll apply three essential nf-core patterns incrementally: --- -## 1. Transform cowpy into an nf-core module +## 1. Transform `cowpy` into an nf-core module In this section, we'll apply nf-core conventions to the local `cowpy` module in your `core-hello` pipeline, transforming it into a module that follows community standards. +We'll apply the following nf-core conventions incrementally: + +1. **Update `cowpy` to use metadata tuples** to propagate sample metadata through the workflow. +2. **Centralize tool argument configuration with `ext.args`** to increase module versatility while keeping the interface minimal. +3. **Standardize output naming with `ext.prefix`** to promote consistency. +4. **Centralize the publishing configuration** to promote consistency. + + + !!! tip "Working directory" Make sure you're in the `core-hello` directory (your pipeline root) for all the commands and file edits in this section. @@ -39,11 +42,27 @@ In this section, we'll apply nf-core conventions to the local `cowpy` module in cd core-hello ``` -### 1.1. Update cowpy to use metadata tuples +### 1.1. Update `cowpy` to use metadata tuples + +In the current version of the `core-hello` pipeline, we're extracting the file from `CAT_CAT`'s output tuple to pass to `cowpy`. + + + +It would be better to have `cowpy` accept metadata tuples directly, allowing metadata to flow on through the workflow. +To the end, we'll need to make the following changes: + +1. Update the input and output definitions +2. Update the process call in the workflow +3. Update the emit block in the workflow -Currently, we're extracting the file from `CAT_CAT`'s output tuple to pass to `cowpy`. It would be better to have `cowpy` accept metadata tuples directly, allowing metadata to flow through the entire workflow. +Once we've done all that, we'll run the pipeline to test that everything still works as before. -Open `core-hello/modules/local/cowpy.nf` and modify it to accept metadata tuples: + + +#### 1.1.1. Update the input and output definitions + +Let's get started! +Open the `cowpy.nf` module file (under `core-hello/modules/local/`) and modify it to accept metadata tuples as shown below. === "After" @@ -99,13 +118,17 @@ Open `core-hello/modules/local/cowpy.nf` and modify it to accept metadata tuples } ``` -Key changes: +As you can see, we changed both the **main input** and the **output** to a tuple that follows the `tuple val(meta), path(input_file)` pattern introduced in Part 3 of this training. +For the output, we also took this opportunity to add `emit: cowpy_output` in order to give the output channel a descriptive name. + +Now that we've changed what the process expects, we need to update what we provide to it in the process call. + +#### 1.1.2. Update the process call in the workflow -1. **Input**: Changed from `path input_file` to `tuple val(meta), path(input_file)` to accept metadata -2. **Output**: Changed to emit a tuple with metadata: `tuple val(meta), path("cowpy-${input_file}"), emit: cowpy_output` -3. **Named emit**: Added `emit: cowpy_output` to give the output channel a descriptive name +The good news is that this change simplifies the process call. +Now that the output of `CAT_CAT` and the input of `cowpy` are the same 'shape', i.e. they both consist of a `tuple val(meta), path(input_file)` structure, we can simply connect them directly instead of having to extract the file explicitly from the output of the `CAT_CAT` process. -Now update the workflow to pass the tuple directly instead of extracting the file. Open `core-hello/workflows/hello.nf`: +Open the `hello.nf` workflow file (under `core-hello/workflows/`) and update the call to `cowpy` as shown below. === "After" @@ -116,14 +139,19 @@ Now update the workflow to pass the tuple directly instead of extracting the fil === "Before" - ```groovy title="core-hello/workflows/hello.nf" linenums="43" hl_lines="2-4" - // generate ASCII art of the greetings with cowpy - // Extract the file from the tuple since cowpy doesn't use metadata yet + ```groovy title="core-hello/workflows/hello.nf" linenums="43" hl_lines="5" + // extract the file from the tuple since cowpy doesn't use metadata yet ch_for_cowpy = CAT_CAT.out.file_out.map{ meta, file -> file } + + // generate ASCII art of the greetings with cowpy cowpy(ch_for_cowpy, params.character) ``` -Also update the emit block to use the named emit: +You can see that we no longer need to construct the `ch_for_cowpy` channel, so that line (and its comment line) can be deleted entirely. + +#### 1.1.3. Update the emit block in the workflow + +Since `cowpy` now emits a named output, `cowpy_output`, we can update the `hello.nf` workflow's `emit:` block to use that. === "After" @@ -141,13 +169,17 @@ Also update the emit block to use the named emit: versions = ch_versions // channel: [ path(versions.yml) ] ``` -Test the workflow to ensure metadata flows through correctly: +This is technically not required, but it's good practice to refer to named outputs whenever possible. + +#### 1.1.4. Run the pipeline to test it + +Let's run the workflow to test that everything is working correctly after these changes. ```bash nextflow run . --outdir core-hello-results -profile test,docker --validate_params false ``` -The pipeline should run successfully with metadata now flowing from `CAT_CAT` through `cowpy`: +The pipeline should run successfully, with metadata now flowing from `CAT_CAT` through `cowpy`: ```console title="Output (excerpt)" executor > local (8) @@ -158,86 +190,35 @@ executor > local (8) -[core/hello] Pipeline completed successfully- ``` -### 1.2. Control module behavior via configuration - -Currently, our `cowpy` module hardcodes two aspects of its behavior: - -1. **Tool arguments**: The `character` parameter is passed as a process input, so we must provide a value every time we call the process -2. **Output publishing**: The module contains a `publishDir` directive, making publishing decisions at the module level +That completes what we needed to do to make `cowpy` handle metadata tuples. +Now let's look at what else we can do to take advantage of nf-core module patterns. -This makes the module less flexible. -For tool arguments, we're forced to provide values even when we'd be happy with defaults, which gets cumbersome for tools with many optional parameters. -For output publishing, we can't control where outputs go at the workflow level - each module makes its own publishing decisions. +### 1.2. Centralize tool argument configuration with `ext.args` -nf-core modules handle both of these differently, controlling tool arguments and output publishing through configuration files. -This centralizes control at the workflow level and makes modules more reusable. +In its current state, the `cowpy` process expects to receive a value for the `character` parameter. +As a result, we have to provide a value every time we call the process, even if we'd be happy with the defaults set by the tool. +For `cowpy` this is admittedly not a big problem, but for tools with many optional parameters, it can get quite cumbersome. -Let's update our module to follow both of these nf-core configuration patterns. +The nf-core project recommends using a Nextflow feature called `ext.args`, which makes it possible to manage tool arguments more conveniently. +Specifically, nf-core modules use a special configuration variable called `task.ext.args`. -#### 1.2.1. Tool arguments with ext.args - -For tool arguments, nf-core modules use a special configuration variable called `task.ext.args`. Instead of declaring process inputs for every tool option, you write the module to reference `task.ext.args` in its command line. -This variable can be set in configuration files to pass arguments to the tool. -When you configure a module to use `task.ext.args`, it checks if the variable is defined and includes those arguments in the tool's command line. - -This approach keeps the module interface focused on essential data (files, metadata, and any mandatory per-sample parameters), while tool configuration options are handled separately through configuration. - -Benefits of this approach: - -- **Clean interface**: The module focuses on essential data inputs (metadata and files) -- **Flexibility**: Users can specify tool arguments via configuration, including sample-specific values -- **Consistency**: All nf-core modules follow this pattern -- **Portability**: Modules can be reused without hardcoded tool options -- **No workflow changes**: Adding or changing tool options doesn't require updating workflow code - -!!! note "ext.args can do more" +Then it's just a matter of adding the arguments and values you want to use in the `modules.config` file, which consolidates configuration details for all modules. +Nextflow will add those arguments with their values into the tool command line at runtime. - The `ext.args` system has powerful additional capabilities not covered here, including switching argument values dynamically based on metadata. See the [nf-core module specifications](https://nf-co.re/docs/guidelines/components/modules) for more details. - -#### 1.2.2. Centralized publishing configuration +Let's apply it to the `cowpy` module. +We're going to need to make the following changes: -For output publishing, nf-core pipelines centralize control at the workflow level by configuring `publishDir` in `conf/modules.config` rather than in individual modules. +1. Update the `cowpy` module +2. Add the character parameter to `ext.args` +3. Update the `hello.nf` workflow -Currently, our `cowpy` module has `publishDir 'results', mode: 'copy'` which hardcodes the output location. -In nf-core pipelines, publishing is instead configured in `conf/modules.config`. - -Benefits of this approach: - -- **Single source of truth**: All publishing configuration lives in `modules.config` -- **Useful default**: Processes work out-of-the-box without per-module configuration -- **Easy customization**: Override publishing behavior in config, not in module code -- **Portable modules**: Modules don't hardcode output locations +Once we've done all that, we'll run the pipeline to test that everything still works as before. -!!! note "Default publishDir configuration" +#### 1.2.1. Update the `cowpy` module - The nf-core template includes a default `publishDir` configuration that applies to all processes: - - ```groovy - process { - publishDir = [ - path: { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - ``` - - This looks complicated, but it breaks down into three parts: - - - **path**: Determines the output directory based on the process name. When processes run, their full name includes the workflow hierarchy (like `CORE_HELLO:HELLO:SAMTOOLS_SORT`). The `tokenize` operations strip away that hierarchy to get just the process name, then take the first part before any underscore, and convert it to lowercase. So `SAMTOOLS_SORT` would publish to `${params.outdir}/samtools/`. - - **mode**: Controls how files are published (copy, symlink, etc.), configurable via the `params.publish_dir_mode` parameter. - - **saveAs**: Filters which files to publish. This example excludes `versions.yml` files by returning `null` for them, preventing them from being published. - - Individual processes can override this default using `withName:` blocks in the same config file. - - For more details, see the [nf-core modules specifications](https://nf-co.re/docs/guidelines/components/modules). - -#### 1.2.3. Update the module - -Now let's update the cowpy module to use `ext.args` and remove the local `publishDir`. - -Open `modules/local/cowpy.nf`: +Let's get started! +Open the `cowpy.nf` module file (under `core-hello/modules/local/`) and modify it to use `ext.args` as shown below. === "After" @@ -247,6 +228,8 @@ Open `modules/local/cowpy.nf`: // Generate ASCII art with cowpy (https://github.com/jeffbuttars/cowpy) process cowpy { + publishDir 'results', mode: 'copy' + container 'community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273' conda 'conda-forge::cowpy==1.1.5' @@ -291,20 +274,41 @@ Open `modules/local/cowpy.nf`: } ``` -Key changes: +You can see we made three changes. + +1. **In the `input:` block, we removed the `val character` input.** + Going forward, we'll supply that argument via the `task.ext.args` configuration (see next step). + +2. **In the `script:` block, we added the line `def args = task.ext.args ?: ''`.** + That line uses the `?:` operator to determine the value of the `args` variable: the content of `task.ext.args` if it is not empty, or an empty string if it is. -1. **Removed character input**: The module no longer requires `character` as a separate input -2. **Removed local publishDir**: Deleted the `publishDir 'results', mode: 'copy'` directive to rely on centralized configuration -3. **Added ext.args**: The line `def args = task.ext.args ?: ''` uses the Elvis operator (`?:`) to provide an empty string as default if `task.ext.args` is not set -4. **Updated command**: Changed from hardcoded `-c "$character"` to using the configurable `$args` +3. **In the command line, we replaced `-c "$character"` with `$args`.** + This is where Nextflow will inject any tool arguments set in `task.ext.args`. The module interface is now simpler - it only accepts the essential metadata and file inputs. By removing the local `publishDir`, we follow the nf-core convention of centralizing all publishing configuration in `modules.config`. -#### 1.2.4. Configure ext.args +!!! note + + The `?:` operator is often called the 'Elvis operator' because it looks like a sideways Elvis Presley face, with the `?` character symbolizing the wave in his hair. + +#### 1.2.2. Add the `character` parameter to `ext.args` + +Now that we've taken the `character` declaration out of the module, we've got to add it to `ext.args` in the `modules.config` configuration file. -Now we need to configure the `ext.args` to pass the character option. This allows us to keep the module interface simple while still providing the character option at the pipeline level. +Specifically, we're going to add this little chunk of code to the `process {}` block: -Open `conf/modules.config` and add the cowpy configuration: +```groovy title="Configuration syntax" +withName: 'cowpy' { + ext.args = { "-c ${params.character}" } +} +``` + +The `withName:` syntax assigns this configuration to the `cowpy` process only, and `ext.args = { "-c ${params.character}" }` simply composes a string that will include the value of the `character` parameter. +Note the use of curly braces, which tell Nextflow to evaluate the value of the parameter at runtime. + +Makes sense? Let's add it in. + +Open `conf/modules.config` and add the configuration code inside the `process {}` block as shown below. === "After" @@ -330,20 +334,16 @@ Open `conf/modules.config` and add the cowpy configuration: } ``` -This configuration passes the `params.character` value to cowpy's `-c` flag. Note that we use a closure (`{ "-c ${params.character}" }`) to allow the parameter to be evaluated at runtime. +Hopefully you can imagine having all the modules in a pipeline have their `ext.args` specified in this file, with the following benefits: -Key points: +- The **module interface stays simple** - It only accepts the essential metadata and file inputs +- The **pipeline still exposes `params.character`** - End-users can still configure it as before +- The **module is now portable** - It can be reused in other pipelines without expecting a specific parameter name +- The configuration is **centralized** in `modules.config`, keeping workflow logic clean -- The **module interface stays simple** - it only accepts the essential metadata and file inputs -- The **pipeline still exposes `params.character`** - users can configure it as before -- The **module is now portable** - it can be reused in other pipelines without expecting a specific parameter name -- Configuration is **centralized** in `modules.config`, keeping workflow logic clean +By using the `modules.config` file as the place where all pipelines centralize per-module configuration, we make our modules more reusable across different pipelines. -!!! note - - The `modules.config` file is where nf-core pipelines centralize per-module configuration. This separation of concerns makes modules more reusable across different pipelines. - -#### 1.2.5. Update the workflow +#### 1.2.3. Update the `hello.nf` workflow Since the cowpy module no longer requires the `character` parameter as an input, we need to update the workflow call. @@ -365,7 +365,7 @@ Open `workflows/hello.nf` and update the cowpy call: The workflow code is now cleaner - we don't need to pass `params.character` directly to the process. The module interface is kept minimal, making it more portable, while the pipeline still provides the explicit option through configuration. -#### 1.2.6. Test +#### 1.2.4. Run the pipeline to test it Test that the workflow still works with the ext.args configuration. Let's specify a different character to verify the configuration is working (using `kosh`, one of the more... enigmatic options): @@ -427,11 +427,28 @@ You should see the ASCII art displayed with the kosh character, confirming that This shows that the `.command.sh` file was generated correctly based on the `ext.args` configuration. -### 1.3. Add configurable output naming with ext.prefix + + +It may seem unnecessary for a simple tool like `cowpy`, but it can make a big difference for data analysis tools that have a lot of optional arguments. +This approach keeps the module interface focused on essential data (files, metadata, and any mandatory per-sample parameters), while options that control the behavior of the tool are handled separately through configuration. + +To summarize the benefits: + +- **Clean interface**: The module focuses on essential data inputs (metadata and files) +- **Flexibility**: Users can specify tool arguments via configuration, including sample-specific values +- **Consistency**: All nf-core modules follow this pattern +- **Portability**: Modules can be reused without hardcoded tool options +- **No workflow changes**: Adding or changing tool options doesn't require updating workflow code + +!!! note "ext.args can do more" + + The `ext.args` system has powerful additional capabilities not covered here, including switching argument values dynamically based on metadata. See the [nf-core module specifications](https://nf-co.re/docs/guidelines/components/modules) for more details. + +### 1.3. Standardize output naming with `ext.prefix` There's one more nf-core pattern we can apply: using `ext.prefix` for configurable output file naming. -#### 1.3.1. Understanding ext.prefix +Currently, the `cowpy` module includes a `publishDir` directive, making publishing decisions at the module level. We can't control where outputs go at the workflow level - each module makes its own publishing decisions. The `task.ext.prefix` pattern is another nf-core convention for standardizing output file naming across modules while keeping it configurable. @@ -442,7 +459,7 @@ Benefits: - **Consistent**: All nf-core modules follow this pattern - **Predictable**: Easy to know what output files will be called -#### 1.3.2. Update the module +#### 1.3.1. Update the module Let's update the cowpy module to use `ext.prefix` for output file naming. @@ -507,7 +524,7 @@ Key changes: Note that the local `publishDir` has already been removed in the previous step, so we're continuing with the centralized configuration approach. -#### 1.3.3. Configure ext.prefix +#### 1.3.2. Configure ext.prefix To maintain the same output file naming as before (`cowpy-.txt`), we can configure `ext.prefix` in modules.config. @@ -536,7 +553,7 @@ Note that we use a closure (`{ "cowpy-${meta.id}" }`) which has access to `meta` The `ext.prefix` closure has access to `meta` because the configuration is evaluated in the context of the process execution, where metadata is available. -#### 1.3.4. Test and verify +#### 1.3.3. Test and verify Test the workflow once more: @@ -555,42 +572,71 @@ You should see the cowpy output files with the same naming as before: `cowpy-tes If you wanted to change the naming (for example, to just `test.txt`), you would only need to modify the `ext.prefix` configuration - no changes to the module or workflow code would be required. -### Takeaway +### 1.4. Centralize the publishing configuration -You now know how to adapt local modules to follow nf-core conventions: +For output publishing, nf-core pipelines centralize control at the workflow level by configuring `publishDir` in `conf/modules.config` rather than in individual modules. -- Update modules to accept and propagate metadata tuples -- Use `ext.args` to keep module interfaces minimal and portable -- Use `ext.prefix` for configurable, standardized output file naming -- Configure process-specific parameters through `modules.config` +Currently, our `cowpy` module has `publishDir 'results', mode: 'copy'` which hardcodes the output location. +In nf-core pipelines, publishing is instead configured in `conf/modules.config`. -### What's next? +#### 1.4.1. Update the module -Clean up by optionally removing the now-unused local module. + ---- + publishDir 'results', mode: 'copy' -### 1.4. Optional: Clean up unused local modules +#### 1.4.2. Run the pipeline to see what happens -Now that we're using the nf-core `cat/cat` module, the local `collectGreetings` module is no longer needed. + + -Remove or comment out the import line for `collectGreetings` in `workflows/hello.nf`: +The nf-core template includes a default `publishDir` configuration that applies to all processes: -```groovy title="core-hello/workflows/hello.nf" linenums="10" -include { sayHello } from '../modules/local/sayHello.nf' -include { convertToUpper } from '../modules/local/convertToUpper.nf' -// include { collectGreetings } from '../modules/local/collectGreetings.nf' // No longer needed -include { cowpy } from '../modules/local/cowpy.nf' -include { CAT_CAT } from '../modules/nf-core/cat/cat/main' +```groovy +process { + publishDir = [ + path: { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] +} ``` -You can optionally delete the `collectGreetings.nf` file: +This looks complicated, but it breaks down into three parts: -```bash -rm modules/local/collectGreetings.nf -``` +- **path**: Determines the output directory based on the process name. When processes run, their full name includes the workflow hierarchy (like `CORE_HELLO:HELLO:SAMTOOLS_SORT`). The `tokenize` operations strip away that hierarchy to get just the process name, then take the first part before any underscore, and convert it to lowercase. So `SAMTOOLS_SORT` would publish to `${params.outdir}/samtools/`. +- **mode**: Controls how files are published (copy, symlink, etc.), configurable via the `params.publish_dir_mode` parameter. +- **saveAs**: Filters which files to publish. This example excludes `versions.yml` files by returning `null` for them, preventing them from being published. + +Individual processes can override this default using `withName:` blocks in the same config file. + +#### 1.4.3. Override the default + + + + + +Benefits of this approach: + +- **Single source of truth**: All publishing configuration lives in `modules.config` +- **Useful default**: Processes work out-of-the-box without per-module configuration +- **Easy customization**: Override publishing behavior in config, not in module code +- **Portable modules**: Modules don't hardcode output locations -However, you might want to keep it as a reference for understanding the differences between local and nf-core modules. +For more details, see the [nf-core modules specifications](https://nf-co.re/docs/guidelines/components/modules). + +### Takeaway + +You now know how to adapt local modules to follow nf-core conventions: + +- Update modules to accept and propagate metadata tuples +- Use `ext.args` to keep module interfaces minimal and portable +- Use `ext.prefix` for configurable, standardized output file naming +- Configure process-specific parameters through `modules.config` + +### What's next? + + --- @@ -621,7 +667,7 @@ You'll be prompted for several configuration options: The tool handles the complexity of finding package information and setting up the structure, allowing you to focus on implementing the tool's specific logic. -#### 2.1.1. What gets generated +### 2.2. What gets generated The tool creates a complete module structure in `modules/local/` (or `modules/nf-core/` if you're in the nf-core/modules repository): @@ -719,7 +765,7 @@ These features are already functional and make modules more maintainable. The next sections walk through completing these customizations. -#### 2.1.2. Completing the environment and container setup +### 2.3. Completing the environment and container setup In the case of cowpy, the tool warned that it couldn't find the package in Bioconda (the primary channel for bioinformatics tools). However, cowpy is available in conda-forge, so you would complete the `environment.yml` like this: @@ -745,7 +791,7 @@ container "community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273" Most bioinformatics tools are in Bioconda, but for conda-forge tools, Seqera Containers provides an easy solution for containerization. -#### 2.1.3. Defining inputs and outputs +### 2.4. Defining inputs and outputs The generated template includes generic input and output declarations that you'll need to customize for your specific tool. Looking back at our manual cowpy module from section 1, we can use that as a guide. @@ -780,7 +826,7 @@ This specifies: - The output filename using the configurable prefix pattern (`${prefix}.txt` instead of wildcard `*`) - A descriptive emit name (`cowpy_output` instead of generic `output`) -#### 2.1.4. Writing the script block +### 2.5. Writing the script block The template provides a comment placeholder where you add the actual tool command. We can reference our manual module from section 1.3.2 for the command logic: @@ -824,7 +870,7 @@ Key changes: - Change `def prefix` to just `prefix` (without `def`) so it's accessible in the output block - Replace the comment with the actual cowpy command that uses both `$args` and `${prefix}.txt` -#### 2.1.5. Implementing the stub block +### 2.6. Implementing the stub block The stub block provides a fast mock implementation for testing pipeline logic without running the actual tool. It must produce the same output files as the script block: @@ -874,11 +920,21 @@ This allows you to test workflow logic and file handling without waiting for the Once you've completed the environment setup (section 2.1.2), inputs/outputs (section 2.1.3), script block (section 2.1.4), and stub block (section 2.1.5), the module is ready to test! -### 2.2. Contributing modules back to nf-core +### Takeaway + + + +### What's next? + + + +--- + +## 3. Contributing modules back to nf-core The [nf-core/modules](https://github.com/nf-core/modules) repository welcomes contributions of well-tested, standardized modules. -#### 2.2.1. Why contribute? +### 3.1. Why contribute? Contributing your modules to nf-core: @@ -887,7 +943,7 @@ Contributing your modules to nf-core: - Provides quality assurance through code review and automated testing - Gives your work visibility and recognition -#### 2.2.2. Contributing workflow +### 3.2. Contributing workflow To contribute a module to nf-core: @@ -901,14 +957,12 @@ To contribute a module to nf-core: For detailed instructions, see the [nf-core components tutorial](https://nf-co.re/docs/tutorials/nf-core_components/components). -#### 2.2.3. Resources +### 3.3. Resources - **Components tutorial**: [Complete guide to creating and contributing modules](https://nf-co.re/docs/tutorials/nf-core_components/components) - **Module specifications**: [Technical requirements and guidelines](https://nf-co.re/docs/guidelines/components/modules) - **Community support**: [nf-core Slack](https://nf-co.re/join) - Join the `#modules` channel ---- - ## Takeaway You now know how to create nf-core modules! You learned the four key patterns that make modules portable and maintainable: From c7c1cbb9ab2498f81fdaf357a500f64ce246a2e9 Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Thu, 13 Nov 2025 02:36:25 -0500 Subject: [PATCH 111/113] Completed revamped Part 4 (minus a few optional TODOs) Will need to recheck all the code/line numbers etc --- docs/hello_nf-core/04_make_module.md | 381 ++++++++++++++++++--------- 1 file changed, 261 insertions(+), 120 deletions(-) diff --git a/docs/hello_nf-core/04_make_module.md b/docs/hello_nf-core/04_make_module.md index c8efb53836..ec18a517ab 100644 --- a/docs/hello_nf-core/04_make_module.md +++ b/docs/hello_nf-core/04_make_module.md @@ -32,7 +32,7 @@ We'll apply the following nf-core conventions incrementally: 3. **Standardize output naming with `ext.prefix`** to promote consistency. 4. **Centralize the publishing configuration** to promote consistency. - +After each step, we'll run the pipeline to test that everything works as expected. !!! tip "Working directory" @@ -57,8 +57,6 @@ To the end, we'll need to make the following changes: Once we've done all that, we'll run the pipeline to test that everything still works as before. - - #### 1.1.1. Update the input and output definitions Let's get started! @@ -125,7 +123,7 @@ Now that we've changed what the process expects, we need to update what we provi #### 1.1.2. Update the process call in the workflow -The good news is that this change simplifies the process call. +The good news is that this change will simplify the process call. Now that the output of `CAT_CAT` and the input of `cowpy` are the same 'shape', i.e. they both consist of a `tuple val(meta), path(input_file)` structure, we can simply connect them directly instead of having to extract the file explicitly from the output of the `CAT_CAT` process. Open the `hello.nf` workflow file (under `core-hello/workflows/`) and update the call to `cowpy` as shown below. @@ -147,7 +145,9 @@ Open the `hello.nf` workflow file (under `core-hello/workflows/`) and update the cowpy(ch_for_cowpy, params.character) ``` -You can see that we no longer need to construct the `ch_for_cowpy` channel, so that line (and its comment line) can be deleted entirely. +We now call `cowpy` on `CAT_CAT.out.file_out` directly. + +As a result, we no longer need to construct the `ch_for_cowpy` channel, so that line (and its comment line) can be deleted entirely. #### 1.1.3. Update the emit block in the workflow @@ -191,7 +191,7 @@ executor > local (8) ``` That completes what we needed to do to make `cowpy` handle metadata tuples. -Now let's look at what else we can do to take advantage of nf-core module patterns. +Now, let's look at what else we can do to take advantage of nf-core module patterns. ### 1.2. Centralize tool argument configuration with `ext.args` @@ -199,26 +199,25 @@ In its current state, the `cowpy` process expects to receive a value for the `ch As a result, we have to provide a value every time we call the process, even if we'd be happy with the defaults set by the tool. For `cowpy` this is admittedly not a big problem, but for tools with many optional parameters, it can get quite cumbersome. -The nf-core project recommends using a Nextflow feature called `ext.args`, which makes it possible to manage tool arguments more conveniently. -Specifically, nf-core modules use a special configuration variable called `task.ext.args`. +The nf-core project recommends using a Nextflow feature called `ext.args` to manage tool arguments more conveniently via configuration files. -Instead of declaring process inputs for every tool option, you write the module to reference `task.ext.args` in its command line. -Then it's just a matter of adding the arguments and values you want to use in the `modules.config` file, which consolidates configuration details for all modules. +Instead of declaring process inputs for every tool option, you write the module to reference `ext.args` in the construction of its command line. +Then it's just a matter of setting up the `ext.args` variable to hold the arguments and values you want to use in the `modules.config` file, which consolidates configuration details for all modules. Nextflow will add those arguments with their values into the tool command line at runtime. -Let's apply it to the `cowpy` module. +Let's apply this approach to the `cowpy` module. We're going to need to make the following changes: 1. Update the `cowpy` module -2. Add the character parameter to `ext.args` +2. Configure `ext.args` in the `modules.config` file 3. Update the `hello.nf` workflow Once we've done all that, we'll run the pipeline to test that everything still works as before. #### 1.2.1. Update the `cowpy` module -Let's get started! -Open the `cowpy.nf` module file (under `core-hello/modules/local/`) and modify it to use `ext.args` as shown below. +Let's do it. +Open the `cowpy.nf` module file (under `core-hello/modules/local/`) and modify it to reference `ext.args` as shown below. === "After" @@ -277,27 +276,28 @@ Open the `cowpy.nf` module file (under `core-hello/modules/local/`) and modify i You can see we made three changes. 1. **In the `input:` block, we removed the `val character` input.** - Going forward, we'll supply that argument via the `task.ext.args` configuration (see next step). + Going forward, we'll supply that argument via the `ext.args` configuration as described further below. 2. **In the `script:` block, we added the line `def args = task.ext.args ?: ''`.** That line uses the `?:` operator to determine the value of the `args` variable: the content of `task.ext.args` if it is not empty, or an empty string if it is. + Note that while we generally refer to `ext.args`, this code must reference `task.ext.args` to pull out the module-level `ext.args` configuration. 3. **In the command line, we replaced `-c "$character"` with `$args`.** - This is where Nextflow will inject any tool arguments set in `task.ext.args`. + This is where Nextflow will inject any tool arguments set in `ext.args` in the `modules.config` file. -The module interface is now simpler - it only accepts the essential metadata and file inputs. By removing the local `publishDir`, we follow the nf-core convention of centralizing all publishing configuration in `modules.config`. +As a result, the module interface is now simpler: it only expects the essential metadata and file inputs. !!! note The `?:` operator is often called the 'Elvis operator' because it looks like a sideways Elvis Presley face, with the `?` character symbolizing the wave in his hair. -#### 1.2.2. Add the `character` parameter to `ext.args` +#### 1.2.2. Configure `ext.args` in the `modules.config` file Now that we've taken the `character` declaration out of the module, we've got to add it to `ext.args` in the `modules.config` configuration file. Specifically, we're going to add this little chunk of code to the `process {}` block: -```groovy title="Configuration syntax" +```groovy title="Code to add" withName: 'cowpy' { ext.args = { "-c ${params.character}" } } @@ -345,9 +345,9 @@ By using the `modules.config` file as the place where all pipelines centralize p #### 1.2.3. Update the `hello.nf` workflow -Since the cowpy module no longer requires the `character` parameter as an input, we need to update the workflow call. +Since the cowpy module no longer requires the `character` parameter as an input, we need to update the workflow call accordingly. -Open `workflows/hello.nf` and update the cowpy call: +Open the `hello.nf` workflow file (under `core-hello/workflows/`) and update the call to `cowpy` as shown below. === "After" @@ -363,51 +363,59 @@ Open `workflows/hello.nf` and update the cowpy call: cowpy(CAT_CAT.out.file_out, params.character) ``` -The workflow code is now cleaner - we don't need to pass `params.character` directly to the process. The module interface is kept minimal, making it more portable, while the pipeline still provides the explicit option through configuration. +The workflow code is now cleaner: we don't need to pass `params.character` directly to the process. +The module interface is kept minimal, making it more portable, while the pipeline still provides the explicit option through configuration. #### 1.2.4. Run the pipeline to test it -Test that the workflow still works with the ext.args configuration. Let's specify a different character to verify the configuration is working (using `kosh`, one of the more... enigmatic options): +Let's test that the workflow still works as expected, specifying a different character to verify that the `ext.args` configuration is working. + +Run this command using `kosh`, one of the more... enigmatic options: ```bash nextflow run . --outdir core-hello-results -profile test,docker --validate_params false --character kosh ``` -The pipeline should run successfully. In the output, look for the cowpy process execution line which will show something like: +The pipeline should run successfully. +In the output, look for the cowpy process execution line, which will show something like this: ```console title="Output (excerpt)" [bd/0abaf8] CORE_HELLO:HELLO:cowpy [100%] 1 of 1 ✔ ``` -Let's verify that the `ext.args` configuration worked by checking the output. Use the task hash (the `bd/0abaf8` part) to look at the output file: +So it ran successfully, great! +Now let's verify that the `ext.args` configuration worked by checking the output. +Find the output in the file browser or use the task hash (the `bd/0abaf8` part in the example above) to look at the output file: ```bash cat work/bd/0abaf8*/cowpy-test.txt ``` -```console title="Output" - _________ -/ HELLO \ -| HOLà | -\ BONJOUR / - --------- - \ - \ - \ - ___ _____ ___ - / \ / /| / \ -| | / / | | | -| | /____/ | | | -| | | | | | | -| | | {} | / | | -| | |____|/ | | -| | |==| | | -| \___________/ | -| | -| | -``` +??? example "Output" + + ```console + _________ + / HELLO \ + | HOLà | + \ BONJOUR / + --------- + \ + \ + \ + ___ _____ ___ + / \ / /| / \ + | | / / | | | + | | /____/ | | | + | | | | | | | + | | | {} | / | | + | | |____|/ | | + | | |==| | | + | \___________/ | + | | + | | + ``` -You should see the ASCII art displayed with the kosh character, confirming that the `ext.args` configuration worked! +You should see the ASCII art displayed with the `kosh` character, confirming that the `ext.args` configuration worked! !!! note "Optional: Inspect the command file" @@ -417,7 +425,7 @@ You should see the ASCII art displayed with the kosh character, confirming that cat work/bd/0abaf8*/.command.sh ``` - You'll see the cowpy command with the `-c kosh` argument: + You'll see the `cowpy` command with the `-c kosh` argument: ```console #!/usr/bin/env bash @@ -427,12 +435,12 @@ You should see the ASCII art displayed with the kosh character, confirming that This shows that the `.command.sh` file was generated correctly based on the `ext.args` configuration. - - -It may seem unnecessary for a simple tool like `cowpy`, but it can make a big difference for data analysis tools that have a lot of optional arguments. +Take a moment to think about what we achieved here. This approach keeps the module interface focused on essential data (files, metadata, and any mandatory per-sample parameters), while options that control the behavior of the tool are handled separately through configuration. -To summarize the benefits: +This may seem unnecessary for a simple tool like `cowpy`, but it can make a big difference for data analysis tools that have a lot of optional arguments. + +To summarize the benefits of this approach: - **Clean interface**: The module focuses on essential data inputs (metadata and files) - **Flexibility**: Users can specify tool arguments via configuration, including sample-specific values @@ -440,39 +448,43 @@ To summarize the benefits: - **Portability**: Modules can be reused without hardcoded tool options - **No workflow changes**: Adding or changing tool options doesn't require updating workflow code -!!! note "ext.args can do more" +!!! note The `ext.args` system has powerful additional capabilities not covered here, including switching argument values dynamically based on metadata. See the [nf-core module specifications](https://nf-co.re/docs/guidelines/components/modules) for more details. ### 1.3. Standardize output naming with `ext.prefix` -There's one more nf-core pattern we can apply: using `ext.prefix` for configurable output file naming. +Now that we've given the `cowpy` process access to the metamap, we can start taking advantage of another useful nf-core pattern: naming output files based on metadata. -Currently, the `cowpy` module includes a `publishDir` directive, making publishing decisions at the module level. We can't control where outputs go at the workflow level - each module makes its own publishing decisions. +Here we're going to use a Nextflow feature called `ext.prefix` that will allow us to standardize output file naming across modules using `meta.id` (the identifier included in the metamap), while still being able to configure modules individually if desired. -The `task.ext.prefix` pattern is another nf-core convention for standardizing output file naming across modules while keeping it configurable. +This will be similar to what we did with `ext.args`, with a few differences that we'll detail as we go. -Benefits: +Let's apply this approach to the `cowpy` module. +We're going to need to make the following changes: -- **Standardized naming**: Output files are typically named using sample IDs from metadata -- **Configurable**: Users can override the default naming if needed -- **Consistent**: All nf-core modules follow this pattern -- **Predictable**: Easy to know what output files will be called +1. Update the `cowpy` module +2. Configure `ext.prefix` in the `modules.config` file -#### 1.3.1. Update the module +(No changes need to the workflow.) -Let's update the cowpy module to use `ext.prefix` for output file naming. +Once we've done that, we'll run the pipeline to test that everything still works as before. -Open `modules/local/cowpy.nf` and change as follows: +#### 1.3.1. Update the `cowpy` module + +Let's do it. +Open the `cowpy.nf` module file (under `core-hello/modules/local/`) and modify it to reference `ext.prefix` as shown below. === "After" - ```groovy title="core-hello/modules/local/cowpy.nf" linenums="1" hl_lines="13 17 19" + ```groovy title="core-hello/modules/local/cowpy.nf" linenums="1" hl_lines="15 19 21" #!/usr/bin/env nextflow // Generate ASCII art with cowpy (https://github.com/jeffbuttars/cowpy) process cowpy { + publishDir 'results', mode: 'copy' + container 'community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273' conda 'conda-forge::cowpy==1.1.5' @@ -493,12 +505,14 @@ Open `modules/local/cowpy.nf` and change as follows: === "Before" - ```groovy title="core-hello/modules/local/cowpy.nf" linenums="1" hl_lines="13 18" + ```groovy title="core-hello/modules/local/cowpy.nf" linenums="1" hl_lines="15 20" #!/usr/bin/env nextflow // Generate ASCII art with cowpy (https://github.com/jeffbuttars/cowpy) process cowpy { + publishDir 'results', mode: 'copy' + container 'community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273' conda 'conda-forge::cowpy==1.1.5' @@ -516,19 +530,36 @@ Open `modules/local/cowpy.nf` and change as follows: } ``` -Key changes: +You can see we made three changes. + +1. **In the `script:` block, we added the line `prefix = task.ext.prefix ?: "${meta.id}"`.** + That line uses the `?:` operator to determine the value of the `prefix` variable: the content of `task.ext.prefix` if it is not empty, or the identifier from the metamap (`meta.id`) if it is. + Note that while we generally refer to `ext.prefix`, this code must reference `task.ext.prefix` to pull out the module-level `ext.prefix` configuration. + +2. **In the command line, we replaced `cowpy-${input_file}` with `${prefix}.txt`.** + This is where Nextflow will inject the value of `prefix` determined by the line above. -1. **Added ext.prefix**: `prefix = task.ext.prefix ?: "${meta.id}"` provides a configurable prefix with a sensible default (the sample ID) -2. **Updated output**: Changed from hardcoded `cowpy-${input_file}` to `${prefix}.txt` -3. **Updated command**: Uses the configured prefix for the output filename +3. **In the `output:` block, we replaced `path("cowpy-${input_file}")` with `path("${prefix}.txt")`.\*\*** + This simply reiterates what the file path will be according to what is written in the command line. -Note that the local `publishDir` has already been removed in the previous step, so we're continuing with the centralized configuration approach. +As a result, the output file name is now constructed using a sensible default (the identifier from the metamap) combined with the appropriate file format extension. -#### 1.3.2. Configure ext.prefix +#### 1.3.2. Configure `ext.prefix` in the `modules.config` file -To maintain the same output file naming as before (`cowpy-.txt`), we can configure `ext.prefix` in modules.config. +In this case the sensible default is not sufficiently expressive for our taste; we want to use a custom naming pattern that includes the tool name, `cowpy-.txt`, like we had before. -Update `conf/modules.config`: +We'll do that by configuring `ext.prefix` in `modules.config`, just like we did for the `character` parameter with `ext.args`, except this time the `withName: 'cowpy' {}` block already exists, and we just need to add the following line: + +```groovy title="Code to add" +ext.prefix = { "cowpy-${meta.id}" } +``` + +This will compose the string we want. +Note that once again we use curly braces, this time to tell Nextflow to evaluate the value of `meta.id` at runtime. + +Let's add it in. + +Open `conf/modules.config` and add the configuration code inside the `process {}` block as shown below. === "After" @@ -547,50 +578,126 @@ Update `conf/modules.config`: } ``` -Note that we use a closure (`{ "cowpy-${meta.id}" }`) which has access to `meta` because it's evaluated in the context of the process execution. +In case you're wondering, the `ext.prefix` closure has access to the correct piece of metadata because the configuration is evaluated in the context of the process execution, where metadata is available. -!!! note +#### 1.3.3. Run the pipeline to test it - The `ext.prefix` closure has access to `meta` because the configuration is evaluated in the context of the process execution, where metadata is available. - -#### 1.3.3. Test and verify - -Test the workflow once more: +Let's test that the workflow still works as expected. ```bash -rm -rf core-hello-results nextflow run . --outdir core-hello-results -profile test,docker --validate_params false ``` + + Check the outputs: ```bash -ls core-hello-results/cowpy/ +ls results/ ``` -You should see the cowpy output files with the same naming as before: `cowpy-test.txt` (based on the batch name). This demonstrates how `ext.prefix` allows you to maintain your preferred naming convention while keeping the module interface flexible. +You should see the cowpy output file with the same naming as before: `cowpy-test.txt`, based on the default batch name. +Feel free to change the `ext.prefix` configuration to satisfy yourself that you can change the naming pattern without having to make any changes to the module or workflow code. + +Alternatively, you can also try running this again with a different `--batch` parameter specified on the command line to satisfy yourself that that part is still customizable on the fly. + +This demonstrates how `ext.prefix` allows you to maintain your preferred naming convention while keeping the module interface flexible. + +To summarize the benefits of this approach: -If you wanted to change the naming (for example, to just `test.txt`), you would only need to modify the `ext.prefix` configuration - no changes to the module or workflow code would be required. +- **Standardized naming**: Output files are typically named using sample IDs from metadata +- **Configurable**: Users can override the default naming if needed +- **Consistent**: All nf-core modules follow this pattern +- **Predictable**: Easy to know what output files will be called + +Pretty good, right? +Well, there's one more important change we need to make to improve our module to fit the nf-core guidelines. ### 1.4. Centralize the publishing configuration -For output publishing, nf-core pipelines centralize control at the workflow level by configuring `publishDir` in `conf/modules.config` rather than in individual modules. +You may have noticed that we've been publishing outputs to two different directories: + +- **`results`** — The original output directory we've been using from the beginning for our local modules, set individually using per-module `publishDir` directives; +- **`core-hello-results`** — The output directory set with `--outdir` on the command line, which has been receiving the nf-core logs and the results published by `CAT_CAT`. + +This is messy and suboptimal; it would be better to have one location for everything. +Of course, we could go into each of our local modules and update the `publishDir` directive manually to use the `core-hello-results` directory, but what about next time we decide to change the output directory? + +Having individual modules make publishing decisions is clearly not the way to go, especially in a world where the same module might be used in a lot of different pipelines, by people who have different needs or preferences. +We want to be able to control where outputs get published at the level of the workflow configuration. + +"Hey," you might say, "`CAT_CAT` is sending its outputs to the `--outdir`. Maybe we should copy its `publishDir` directive?" + +Yes, that's a great idea. + +Except it doesn't have a `publishDir` directive. (Go ahead, look at the module code.) + +That's because nf-core pipelines centralize control at the workflow level by configuring `publishDir` in `conf/modules.config` rather than in individual modules. +Specifically, the nf-core template declares a default `publishDir` directive (with a predefined directory structure) that applies to all modules unless an overriding directive is provide. + +Doesn't that sound awesome? Could it be that to take advantage of this default directive, all we need to do is remove the current `publishDir` directive from our local modules? -Currently, our `cowpy` module has `publishDir 'results', mode: 'copy'` which hardcodes the output location. -In nf-core pipelines, publishing is instead configured in `conf/modules.config`. +Let's try that out on `cowpy` to see what happens, then we'll look at the code for the default configuration to understand how it works. -#### 1.4.1. Update the module +Finally, we'll demonstrate how to override the default behavior if desired. - +#### 1.4.1. Remove the `publishDir` directive from `cowpy` + +Let's do this. +Open the `cowpy.nf` module file (under `core-hello/modules/local/`) and remove the `publishDir` directive as shown below. + +=== "After" + + ```groovy title="core-hello/modules/local/cowpy.nf (excerpt)" linenums="1" + #!/usr/bin/env nextflow + + // Generate ASCII art with cowpy (https://github.com/jeffbuttars/cowpy) + process cowpy { + + container 'community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273' + conda 'conda-forge::cowpy==1.1.5' + ``` + +=== "Before" + + ```groovy title="core-hello/modules/local/cowpy.nf (excerpt)" linenums="1" hl_lines="6" + #!/usr/bin/env nextflow + + // Generate ASCII art with cowpy (https://github.com/jeffbuttars/cowpy) + process cowpy { publishDir 'results', mode: 'copy' + container 'community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273' + conda 'conda-forge::cowpy==1.1.5' + + ``` + +That's it! + #### 1.4.2. Run the pipeline to see what happens - - +Let's have a look at what happens if we run the pipeline now. + +```bash +nextflow run . --outdir core-hello-results -profile test,docker --validate_params false +``` + + -The nf-core template includes a default `publishDir` configuration that applies to all processes: +Have a look at your current working directory. +Now the `core-hello-results` also contains the outputs of the `cowpy` module. + +```bash +tree core-hello-results/ +``` + + + +You can see that Nextflow created this hierarchy of directories based on the names of the workflow and of the module. + +The code responsible lives in the `conf/modules.config` file. +This is the default `publishDir` configuration that is part of the nf-core template and applies to all processes. ```groovy process { @@ -602,41 +709,72 @@ process { } ``` -This looks complicated, but it breaks down into three parts: + + +This may look complicated, so let's look at each of the three components: + +- **`path:`** Determines the output directory based on the process name. + The full name of a process contained in `task.process` includes the hierarchy of workflow and module imports (such as `CORE_HELLO:HELLO:CAT_CAT`). + The `tokenize` operations strip away that hierarchy to get just the process name, then take the first part before any underscore (if applicable), and convert it to lowercase. + This is what determines that the results of `CAT_CAT` get published to `${params.outdir}/cat/`. +- **`mode:`** Controls how files are published (copy, symlink, etc.). + This is configurable via the `params.publish_dir_mode` parameter. +- **`saveAs:`** Filters which files to publish. + This example excludes `versions.yml` files by returning `null` for them, preventing them from being published. + +This provides a consistent logic for organizing outputs. -- **path**: Determines the output directory based on the process name. When processes run, their full name includes the workflow hierarchy (like `CORE_HELLO:HELLO:SAMTOOLS_SORT`). The `tokenize` operations strip away that hierarchy to get just the process name, then take the first part before any underscore, and convert it to lowercase. So `SAMTOOLS_SORT` would publish to `${params.outdir}/samtools/`. -- **mode**: Controls how files are published (copy, symlink, etc.), configurable via the `params.publish_dir_mode` parameter. -- **saveAs**: Filters which files to publish. This example excludes `versions.yml` files by returning `null` for them, preventing them from being published. +The output looks even better when all the modules in a pipeline adopt this convention, so feel free to go delete the `publishDir` directives from the other modules in your pipeline. +This default will be applied even to modules that we didn't explicitly modify to follow nf-core guidelines. -Individual processes can override this default using `withName:` blocks in the same config file. +That being said, you may decide you want to organize your inputs differently, and the good news is that it's easy to do so. #### 1.4.3. Override the default - +To override the default `publishDir` directive, you can simply add your own directives to the `conf/modules.config` file. + +For example, you could override the default for a single process using the `withName:` selector, as in this example where we add a custom `publishDir` directive for the 'cowpy' process. + +```groovy title="core-hello/conf/modules.config" linenums="13" hl_lines="6-8" +process { + publishDir = [ + path: { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" }, + ] + + withName: 'cowpy' { + ext.args = { "-c ${params.character}" } + publishDir = [ + path: 'my_custom_results' + ] + } +} +``` + +We're not actually going to make that change, but feel free to play with this and see what logic you can implement. - +The point is that this system allows gives you the best of both worlds: consistency by default and the flexibility to customize the configuration on demand. -Benefits of this approach: +To summarize, you get: - **Single source of truth**: All publishing configuration lives in `modules.config` - **Useful default**: Processes work out-of-the-box without per-module configuration - **Easy customization**: Override publishing behavior in config, not in module code - **Portable modules**: Modules don't hardcode output locations -For more details, see the [nf-core modules specifications](https://nf-co.re/docs/guidelines/components/modules). +This completes the set of nf-core module features you should absolutely learn to use, but there are others which you can read about in the [nf-core modules specifications](https://nf-co.re/docs/guidelines/components/modules). ### Takeaway You now know how to adapt local modules to follow nf-core conventions: -- Update modules to accept and propagate metadata tuples -- Use `ext.args` to keep module interfaces minimal and portable -- Use `ext.prefix` for configurable, standardized output file naming -- Configure process-specific parameters through `modules.config` +- Design your modules to accept and propagate metadata tuples; +- Use `ext.args` to keep module interfaces minimal and portable; +- Use `ext.prefix` for configurable, standardized output file naming; +- Adopt the default centralized `publishDir` directive for a consistent results directory structure. ### What's next? - +Learn how to use nf-core's built-in template-based tools to create modules the easy way. --- @@ -657,13 +795,16 @@ nf-core modules create --empty-template cowpy The `--empty-template` flag creates a clean starter template without extra code, making it easier to see the essential structure. -The command runs interactively, guiding you through the setup. It automatically looks up tool information from package repositories like Bioconda and bio.tools to pre-populate metadata. +The command runs interactively, guiding you through the setup. +It automatically looks up tool information from package repositories like Bioconda and bio.tools to pre-populate metadata. You'll be prompted for several configuration options: - **Author information**: Your GitHub username for attribution -- **Resource label**: A predefined set of computational requirements. nf-core provides standard labels like `process_single` for lightweight tools and `process_high` for demanding ones. These labels help manage resource allocation across different execution environments. -- **Metadata requirement**: Whether the module needs sample-specific information via a `meta` map (usually yes for data processing modules) +- **Resource label**: A predefined set of computational requirements. + The nf-core project provides standard labels like `process_single` for lightweight tools and `process_high` for demanding ones. + These labels help manage resource allocation across different execution environments. +- **Metadata requirement**: Whether the module needs sample-specific information via a `meta` map (usually yes for data processing modules). The tool handles the complexity of finding package information and setting up the structure, allowing you to focus on implementing the tool's specific logic. @@ -744,9 +885,9 @@ process COWPY { } ``` -Notice how all three patterns you applied manually are already there! +Notice how all the patterns you applied manually above are already there! The template also includes several additional nf-core conventions. -Some of these work out of the box, while others are placeholders we'll need to fill in: +Some of these work out of the box, while others are placeholders we'll need to fill in, as described below. **Features that work as-is:** @@ -794,7 +935,7 @@ container "community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273" ### 2.4. Defining inputs and outputs The generated template includes generic input and output declarations that you'll need to customize for your specific tool. -Looking back at our manual cowpy module from section 1, we can use that as a guide. +Looking back at our manual `cowpy` module from section 1, we can use that as a guide. Update the input and output blocks: @@ -829,7 +970,7 @@ This specifies: ### 2.5. Writing the script block The template provides a comment placeholder where you add the actual tool command. -We can reference our manual module from section 1.3.2 for the command logic: +We can reference our manual module from earlier for the command logic: === "After" @@ -922,11 +1063,11 @@ Once you've completed the environment setup (section 2.1.2), inputs/outputs (sec ### Takeaway - +You now know how to use the built-in nf-core tooling to create modules efficiently using templates rather than writing everything from scratch. ### What's next? - +Learn what are the benefits of contributing modules to nf-core and what are the main steps and requirements involved. --- @@ -943,9 +1084,9 @@ Contributing your modules to nf-core: - Provides quality assurance through code review and automated testing - Gives your work visibility and recognition -### 3.2. Contributing workflow +### 3.2. Contributor's checklist -To contribute a module to nf-core: +To contribute a module to nf-core, you will need to go through the following steps: 1. Check if it already exists at [nf-co.re/modules](https://nf-co.re/modules) 2. Fork the [nf-core/modules](https://github.com/nf-core/modules) repository @@ -967,7 +1108,7 @@ For detailed instructions, see the [nf-core components tutorial](https://nf-co.r You now know how to create nf-core modules! You learned the four key patterns that make modules portable and maintainable: -- **Metadata tuples** track sample information through the workflow +- **Metadata tuples** propagate metadata through the workflow - **`ext.args`** simplifies module interfaces by handling optional arguments via configuration - **`ext.prefix`** standardizes output file naming - **Centralized publishing** via `publishDir` configured in `modules.config` rather than hardcoded in modules @@ -979,4 +1120,4 @@ Finally, you learned how to contribute modules to the nf-core community, making ## What's next? -Continue to [Part 5: Input validation](./05_input_validation.md) to learn how to add schema-based input validation to your pipeline, or explore other nf-core modules you might add to enhance your pipeline further. +When you're ready, continue to [Part 5: Input validation](./05_input_validation.md) to learn how to add schema-based input validation to your pipeline. From b38cf42f319c490ff7279a6b3ed209e086d1e2ab Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Thu, 13 Nov 2025 02:38:47 -0500 Subject: [PATCH 112/113] fix ghost lines --- docs/hello_nf-core/03_use_module.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/hello_nf-core/03_use_module.md b/docs/hello_nf-core/03_use_module.md index 25ddacbd4b..8a2547eea9 100644 --- a/docs/hello_nf-core/03_use_module.md +++ b/docs/hello_nf-core/03_use_module.md @@ -796,7 +796,3 @@ You now know how to: Learn to adapt your local modules to follow nf-core conventions. We'll also show you how to create new nf-core modules from a template using the nf-core tooling. - -``` - -``` From d924d013fc74cf2a7c989362f82efb3e6d6c9dfa Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Thu, 13 Nov 2025 02:52:35 -0500 Subject: [PATCH 113/113] Minor fixes Mostly consistency and also the box on plugins was way out of place --- docs/hello_nf-core/05_input_validation.md | 64 +++++++++-------------- 1 file changed, 26 insertions(+), 38 deletions(-) diff --git a/docs/hello_nf-core/05_input_validation.md b/docs/hello_nf-core/05_input_validation.md index ee5ec7e155..787c0a673f 100644 --- a/docs/hello_nf-core/05_input_validation.md +++ b/docs/hello_nf-core/05_input_validation.md @@ -47,6 +47,24 @@ nf-schema provides several key functions: nf-schema is the successor to the deprecated nf-validation plugin and uses standard [JSON Schema Draft 2020-12](https://json-schema.org/) for validation. +!!! note "What are Nextflow plugins?" + + Plugins are extensions that add new functionality to the Nextflow language itself. They're installed via a `plugins{}` block in `nextflow.config` and can provide: + + - New functions and classes that can be imported (like `samplesheetToList`) + - New DSL features and operators + - Integration with external services + + The nf-schema plugin is specified in `nextflow.config`: + + ```groovy + plugins { + id 'nf-schema@2.1.1' + } + ``` + + Once installed, you can import functions from plugins using `include { functionName } from 'plugin/plugin-name'` syntax. + ## Two schema files An nf-core pipeline uses two schema files for validation: @@ -273,7 +291,7 @@ Press `Ctrl+C` to exit the schema builder. The tool has now updated your `nextflow_schema.json` file with the new `batch` parameter, handling all the JSON Schema syntax correctly. -#### 2.2.1. Verify the changes +### 1.4. Verify the changes ```bash grep -A 25 '"input_output_options"' nextflow_schema.json @@ -307,7 +325,7 @@ grep -A 25 '"input_output_options"' nextflow_schema.json You should see that the `batch` parameter has been added to the schema with the "required" field now showing `["input", "outdir", "batch"]`. -### 1.4. Test parameter validation +### 1.5. Test parameter validation Now let's test that parameter validation works correctly. @@ -486,15 +504,9 @@ Add a header line to the greetings file: Now the CSV file has a header that matches the field name in our schema. -### Takeaway - -You've created a JSON schema for the greetings input file and added the required header to the CSV file. - -### What's next? - -Implement the validation in the pipeline code using `samplesheetToList`. +The final step is to implement the validation in the pipeline code using `samplesheetToList`. -### 2.5. Implement samplesheetToList in the pipeline +### 2.5. Implement `samplesheetToList` in the pipeline Now we need to replace our simple CSV parsing with nf-schema's `samplesheetToList` function, which validates and converts the sample sheet. @@ -515,24 +527,6 @@ We need to: 2. Validate and parse the input 3. Extract just the greeting strings for our workflow -!!! note "What are Nextflow plugins?" - - Plugins are extensions that add new functionality to the Nextflow language itself. They're installed via a `plugins{}` block in `nextflow.config` and can provide: - - - New functions and classes that can be imported (like `samplesheetToList`) - - New DSL features and operators - - Integration with external services - - The nf-schema plugin is specified in `nextflow.config`: - - ```groovy - plugins { - id 'nf-schema@2.1.1' - } - ``` - - Once installed, you can import functions from plugins using `include { functionName } from 'plugin/plugin-name'` syntax. - First, note that the `samplesheetToList` function is already imported at the top of the file (the nf-core template includes this by default): ```groovy title="core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf" linenums="1" hl_lines="13" @@ -591,18 +585,12 @@ Let's break down what changed: 1. **`samplesheetToList(params.input, "${projectDir}/assets/schema_input.json")`**: Validates the input file against our schema and returns a list 2. **`Channel.fromList(...)`**: Converts the list into a Nextflow channel -### Takeaway - -You've successfully implemented input data validation using `samplesheetToList` and JSON schemas. +This completes the implementation of input data validation using `samplesheetToList` and JSON schemas. -### What's next? - -Re-enable input validation in the config and test both parameter and input data validation to see them in action. +Now that we've configured the input data schema, we can remove the temporary ignore setting we added earlier. ### 2.6. Re-enable input validation -Now that we've configured the input data schema, we can remove the temporary ignore setting we added in section 1.4. - Open `nextflow.config` and remove the `ignoreParams` line from the `validation` block: === "After" @@ -630,7 +618,7 @@ Now nf-schema will validate both parameter types AND the input file contents. Let's verify that our validation works by testing both valid and invalid inputs. -#### 3.7.1. Test with valid input +#### 2.7.1. Test with valid input First, confirm the pipeline runs successfully with valid input: @@ -657,7 +645,7 @@ executor > local (10) Great! The pipeline runs successfully and validation passes silently. The warning about `--character` is just informational since it's not defined in the schema. If you want, use what you've learned to add validation for that parameter too! -#### 3.7.2. Test with invalid input +#### 2.7.2. Test with invalid input Now let's test that validation catches errors. Create a test file with an invalid column name: