From afe2fdd818df3570bda306db198b1cc3534648bf Mon Sep 17 00:00:00 2001 From: Jim Downie Date: Tue, 5 May 2026 14:01:50 +0100 Subject: [PATCH 1/4] feat(samtools/pipeline): add samtools/pipeline --- .../nf-core/samtools/pipeline/environment.yml | 10 ++ modules/nf-core/samtools/pipeline/main.nf | 170 ++++++++++++++++++ modules/nf-core/samtools/pipeline/meta.yml | 75 ++++++++ .../samtools/pipeline/tests/main.nf.test | 76 ++++++++ 4 files changed, 331 insertions(+) create mode 100644 modules/nf-core/samtools/pipeline/environment.yml create mode 100644 modules/nf-core/samtools/pipeline/main.nf create mode 100644 modules/nf-core/samtools/pipeline/meta.yml create mode 100644 modules/nf-core/samtools/pipeline/tests/main.nf.test diff --git a/modules/nf-core/samtools/pipeline/environment.yml b/modules/nf-core/samtools/pipeline/environment.yml new file mode 100644 index 000000000000..7ffa2d14cf20 --- /dev/null +++ b/modules/nf-core/samtools/pipeline/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. + - "bioconda::samtools=1.23.1" diff --git a/modules/nf-core/samtools/pipeline/main.nf b/modules/nf-core/samtools/pipeline/main.nf new file mode 100644 index 000000000000..5ad8a6357679 --- /dev/null +++ b/modules/nf-core/samtools/pipeline/main.nf @@ -0,0 +1,170 @@ +process SAMTOOLS_PIPELINE { + tag "$meta.id" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container + ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/8c/8c5d2818c8b9f58e1fba77ce219fdaf32087ae53e857c4a496402978af26e78c/data' + : 'community.wave.seqera.io/library/htslib_samtools:1.23.1--5b6bb4ede7e612e5'}" + + input: + tuple val(meta), path(bam) + val(pipeline) + + output: + // Alignment format outputs (view, sort, markdup, merge, cat, collate) + tuple val(meta), path("*.bam"), optional: true, emit: bam + tuple val(meta), path("*.cram"), optional: true, emit: cram + tuple val(meta), path("*.sam"), optional: true, emit: sam + tuple val(meta), path("*.{bai,csi,crai}"), optional: true, emit: index + + // Sequence outputs (fasta, fastq) + tuple val(meta), path("*.fasta.gz"), optional: true, emit: fasta + tuple val(meta), path("*.fastq.gz"), optional: true, emit: fastq + tuple val(meta), path("*_interleaved.*"), optional: true, emit: interleaved + + // Statistics outputs (coverage, depth, stats, idxstats, flagstat) + tuple val(meta), path("*.txt"), optional: true, emit: coverage + tuple val(meta), path("*.tsv"), optional: true, emit: depth + tuple val(meta), path("*.stats"), optional: true, emit: stats + tuple val(meta), path("*.idxstats"), optional: true, emit: idxstats + tuple val(meta), path("*.flagstat"), optional: true, emit: flagstat + + tuple val("${task.process}"), val('samtools'), eval("samtools --version"), topic: versions, emit: versions_samtools + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def n_commands = pipeline.size() + def final_command = pipeline[n_commands - 1] + + // Build output string based on final command + def output_string = "" + + if (final_command in ['view', 'sort', 'markdup', 'fixmate', 'merge', 'cat', 'collate']) { + // These produce alignment files + def argsLast = task.ext["args${n_commands - 1}"] ?: "" + def extension = argsLast.contains("--output-fmt sam") + ? "sam" + : argsLast.contains("--output-fmt cram") + ? "cram" + : "bam" + output_string = "-o ${prefix}.${extension}" + } else if (final_command == "fasta") { + // fasta produces multiple files with special output flags + output_string = "-0 ${prefix}_other.fasta.gz" + if (!meta.single_end) { + output_string = output_string + " -1 ${prefix}_1.fasta.gz -2 ${prefix}_2.fasta.gz -s ${prefix}_singleton.fasta.gz" + } else { + output_string = output_string + " -1 ${prefix}_1.fasta.gz -s ${prefix}_singleton.fasta.gz" + } + } else if (final_command == "fastq") { + // fastq produces multiple files with special output flags + output_string = "-0 ${prefix}_other.fastq.gz" + if (!meta.single_end) { + output_string = output_string + " -1 ${prefix}_1.fastq.gz -2 ${prefix}_2.fastq.gz -s ${prefix}_singleton.fastq.gz" + } else { + output_string = output_string + " -1 ${prefix}_1.fastq.gz -s ${prefix}_singleton.fastq.gz" + } + } else if (final_command == "coverage") { + // coverage produces a txt file + output_string = "-o ${prefix}.txt" + } else if (final_command == "depth") { + // depth produces a tsv file + output_string = "-o ${prefix}.tsv" + } else if (final_command == "stats") { + // stats produces a stats file + output_string = "> ${prefix}.stats" + } else if (final_command == "idxstats") { + // idxstats produces an idxstats file + output_string = "> ${prefix}.idxstats" + } else if (final_command == "flagstat") { + // flagstat produces a flagstat file + output_string = "> ${prefix}.flagstat" + } + + // Build the pipeline command + def pipeline_command = pipeline.withIndex().collect { subcommand, idx -> + def argsKey = "args${idx}" + def taskArgs = task.ext[argsKey] ?: "" + + def cmd = "" + if (idx == 0) { + // First command: read from input BAM + cmd = "samtools ${subcommand} ${taskArgs} ${bam}" + } else { + // Subsequent commands: read from stdin + cmd = "samtools ${subcommand} ${taskArgs}" + } + + // Last command: add output string + if (idx == n_commands - 1) { + cmd = cmd + " ${output_string}" + } + + cmd + }.join(" |\\\n") + """ + ${pipeline_command} + """ + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def n_commands = pipeline.size() + def final_command = pipeline[n_commands - 1] + + def stub_outputs = "" + + if (final_command in ['view', 'sort', 'markdup', 'fixmate', 'collate']) { + def argsLast = task.ext["args${n_commands - 1}"] ?: "" + def extension = argsLast.contains("--output-fmt sam") + ? "sam" + : argsLast.contains("--output-fmt cram") + ? "cram" + : "bam" + stub_outputs = "touch ${prefix}.${extension}" + } else if (final_command == "merge") { + def file_type = bam instanceof List ? bam[0].getExtension() : bam.getExtension() + stub_outputs = "touch ${prefix}.${file_type}" + } else if (final_command == "cat") { + def file_type = bam instanceof List ? bam[0].getExtension() : bam.getExtension() + stub_outputs = "touch ${prefix}.${file_type}" + } else if (final_command == "fasta") { + if (meta.single_end) { + stub_outputs = "echo > ${prefix}_1.fasta.gz" + stub_outputs = "echo > ${prefix}_singleton.fasta.gz" + } else { + stub_outputs = "echo > ${prefix}_1.fasta.gz" + stub_outputs = "echo > ${prefix}_2.fasta.gz" + stub_outputs = "echo > ${prefix}_singleton.fasta.gz" + } + } else if (final_command == "fastq") { + if (meta.single_end) { + stub_outputs = "echo > ${prefix}_1.fastq.gz" + stub_outputs = "echo > ${prefix}_singleton.fastq.gz" + } else { + stub_outputs = "echo > ${prefix}_1.fastq.gz" + stub_outputs = "echo > ${prefix}_2.fastq.gz" + stub_outputs = "echo > ${prefix}_singleton.fastq.gz" + } + stub_outputs = "touch ${prefix}_other.fastq.gz" + } else if (final_command == "coverage") { + stub_outputs = "touch ${prefix}.txt" + } else if (final_command == "depth") { + stub_outputs = "touch ${prefix}.tsv" + } else if (final_command == "stats") { + stub_outputs = "touch ${prefix}.stats" + } else if (final_command == "idxstats") { + stub_outputs = "touch ${prefix}.idxstats" + } else if (final_command == "flagstat") { + stub_outputs = "touch ${prefix}.flagstat" + } + + """ + ${stub_outputs} + """ +} diff --git a/modules/nf-core/samtools/pipeline/meta.yml b/modules/nf-core/samtools/pipeline/meta.yml new file mode 100644 index 000000000000..a861a381a7e9 --- /dev/null +++ b/modules/nf-core/samtools/pipeline/meta.yml @@ -0,0 +1,75 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +# # TODO nf-core: Add a description of the module and list keywords +name: "samtools_pipeline" +description: write your description here +keywords: + - sort + - example + - genomics +tools: + ## TODO nf-core: Add a description and other details for the software below + - "samtools": + description: "Tools for dealing with SAM, BAM and CRAM files" + homepage: "None" + documentation: "None" + tool_dev_url: "None" + doi: "" + licence: ["MIT"] + identifier: biotools:samtools + +input: + # TODO nf-core: Update the information obtained from bio.tools and make sure that it is correct + - - meta: + type: map + description: Groovy Map containing sample information. e.g. `[ + id:'sample1' ]` + - sequence_trace: + type: file + description: sequence_trace file + pattern: "*.{bam,cram,sam}" + ontologies: + - edam: http://edamontology.org/data_0924 # Sequence trace + - edam: http://edamontology.org/format_2572 # BAM + - edam: http://edamontology.org/format_3462 # CRAM + - edam: http://edamontology.org/format_2573 # SAM +output: + # TODO nf-core: Update the information obtained from bio.tools and make sure that it is correct + sequence_trace: + - - meta: + type: map + description: Groovy Map containing sample information. e.g. `[ + id:'sample1' ]` + - "*.{bam,cram,sam}": + type: file + description: sequence_trace file + pattern: "*.{bam,cram,sam}" + ontologies: + - edam: http://edamontology.org/data_0924 # Sequence trace + - edam: http://edamontology.org/format_2572 # BAM + - edam: http://edamontology.org/format_3462 # CRAM + - edam: http://edamontology.org/format_2573 # SAM + versions_samtools: + - - ${task.process}: + type: string + description: The name of the process + - samtools: + type: string + description: The name of the tool + - samtools --version: + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - samtools: + type: string + description: The name of the tool + - samtools --version: + type: eval + description: The expression to obtain the version of the tool +authors: + - "@prototaxites" +maintainers: + - "@prototaxites" diff --git a/modules/nf-core/samtools/pipeline/tests/main.nf.test b/modules/nf-core/samtools/pipeline/tests/main.nf.test new file mode 100644 index 000000000000..31768c933a80 --- /dev/null +++ b/modules/nf-core/samtools/pipeline/tests/main.nf.test @@ -0,0 +1,76 @@ +// TODO nf-core: Once you have added the required tests, please run the following command to build this file: +// nf-core modules test samtools/pipeline +nextflow_process { + + name "Test Process SAMTOOLS_PIPELINE" + script "../main.nf" + process "SAMTOOLS_PIPELINE" + + tag "modules" + tag "modules_nfcore" + tag "samtools" + tag "samtools/pipeline" + + // 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 nf-test docs (https://www.nf-test.com/docs/testcases/setup/). + + 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, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + //TODO nf-core: Add all required assertions to verify the test output. + // See https://nf-co.re/docs/developing/testing/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).match() } + ) + } + + } + +} From 116835e6da73693b125226efae7cd243017232e4 Mon Sep 17 00:00:00 2001 From: Jim Downie Date: Tue, 5 May 2026 14:14:12 +0100 Subject: [PATCH 2/4] fix: fix stub --- modules/nf-core/samtools/pipeline/main.nf | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/nf-core/samtools/pipeline/main.nf b/modules/nf-core/samtools/pipeline/main.nf index 5ad8a6357679..788376ed69b5 100644 --- a/modules/nf-core/samtools/pipeline/main.nf +++ b/modules/nf-core/samtools/pipeline/main.nf @@ -135,21 +135,21 @@ process SAMTOOLS_PIPELINE { stub_outputs = "touch ${prefix}.${file_type}" } else if (final_command == "fasta") { if (meta.single_end) { - stub_outputs = "echo > ${prefix}_1.fasta.gz" - stub_outputs = "echo > ${prefix}_singleton.fasta.gz" + stub_outputs = "echo | gzip > ${prefix}_1.fasta.gz" + stub_outputs = "echo | gzip > ${prefix}_singleton.fasta.gz" } else { - stub_outputs = "echo > ${prefix}_1.fasta.gz" - stub_outputs = "echo > ${prefix}_2.fasta.gz" - stub_outputs = "echo > ${prefix}_singleton.fasta.gz" + stub_outputs = "echo | gzip > ${prefix}_1.fasta.gz" + stub_outputs = "echo | gzip > ${prefix}_2.fasta.gz" + stub_outputs = "echo | gzip > ${prefix}_singleton.fasta.gz" } } else if (final_command == "fastq") { if (meta.single_end) { - stub_outputs = "echo > ${prefix}_1.fastq.gz" - stub_outputs = "echo > ${prefix}_singleton.fastq.gz" + stub_outputs = "echo | gzip > ${prefix}_1.fastq.gz" + stub_outputs = "echo | gzip > ${prefix}_singleton.fastq.gz" } else { - stub_outputs = "echo > ${prefix}_1.fastq.gz" - stub_outputs = "echo > ${prefix}_2.fastq.gz" - stub_outputs = "echo > ${prefix}_singleton.fastq.gz" + stub_outputs = "echo | gzip > ${prefix}_1.fastq.gz" + stub_outputs = "echo | gzip > ${prefix}_2.fastq.gz" + stub_outputs = "echo | gzip > ${prefix}_singleton.fastq.gz" } stub_outputs = "touch ${prefix}_other.fastq.gz" } else if (final_command == "coverage") { From 3197eacaccf981fa3646e530826b69cb4ded9355 Mon Sep 17 00:00:00 2001 From: Jim Downie Date: Thu, 7 May 2026 10:05:21 +0100 Subject: [PATCH 3/4] feat: working samtools_multicommand --- .../samtools/multicommand/environment.yml | 7 + modules/nf-core/samtools/multicommand/main.nf | 169 ++++++++ .../nf-core/samtools/multicommand/meta.yml | 185 ++++++++ .../samtools/multicommand/tests/main.nf.test | 343 +++++++++++++++ .../multicommand/tests/main.nf.test.snap | 396 ++++++++++++++++++ .../multicommand/tests/nextflow.config | 11 + .../nf-core/samtools/pipeline/environment.yml | 10 - modules/nf-core/samtools/pipeline/main.nf | 170 -------- modules/nf-core/samtools/pipeline/meta.yml | 75 ---- .../samtools/pipeline/tests/main.nf.test | 76 ---- 10 files changed, 1111 insertions(+), 331 deletions(-) create mode 100644 modules/nf-core/samtools/multicommand/environment.yml create mode 100644 modules/nf-core/samtools/multicommand/main.nf create mode 100644 modules/nf-core/samtools/multicommand/meta.yml create mode 100644 modules/nf-core/samtools/multicommand/tests/main.nf.test create mode 100644 modules/nf-core/samtools/multicommand/tests/main.nf.test.snap create mode 100644 modules/nf-core/samtools/multicommand/tests/nextflow.config delete mode 100644 modules/nf-core/samtools/pipeline/environment.yml delete mode 100644 modules/nf-core/samtools/pipeline/main.nf delete mode 100644 modules/nf-core/samtools/pipeline/meta.yml delete mode 100644 modules/nf-core/samtools/pipeline/tests/main.nf.test diff --git a/modules/nf-core/samtools/multicommand/environment.yml b/modules/nf-core/samtools/multicommand/environment.yml new file mode 100644 index 000000000000..dc6ea0f7e982 --- /dev/null +++ b/modules/nf-core/samtools/multicommand/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: + - "bioconda::samtools=1.23.1" diff --git a/modules/nf-core/samtools/multicommand/main.nf b/modules/nf-core/samtools/multicommand/main.nf new file mode 100644 index 000000000000..6e5d388ca7fb --- /dev/null +++ b/modules/nf-core/samtools/multicommand/main.nf @@ -0,0 +1,169 @@ +process SAMTOOLS_MULTICOMMAND { + tag "$meta.id" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container + ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/8c/8c5d2818c8b9f58e1fba77ce219fdaf32087ae53e857c4a496402978af26e78c/data' + : 'community.wave.seqera.io/library/htslib_samtools:1.23.1--5b6bb4ede7e612e5'}" + + input: + tuple val(meta), path(input), path(index) + tuple val(meta2), path(fasta), path(fai) + val(pipeline) + + output: + // Alignment format outputs (view, sort, markdup, merge, cat, collate) + tuple val(meta), path("*.bam"), optional: true, emit: bam + tuple val(meta), path("*.cram"), optional: true, emit: cram + tuple val(meta), path("*.sam"), optional: true, emit: sam + tuple val(meta), path("*.{bai,csi,crai}"), optional: true, emit: index + + // Sequence outputs (fasta, fastq) + tuple val(meta), path("*.fasta.gz"), optional: true, emit: fasta + tuple val(meta), path("*.fastq.gz"), optional: true, emit: fastq + + tuple val("${task.process}"), val('samtools'), eval('samtools version | sed "1!d;s/.* //"'), emit: versions_samtools, topic: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + + def valid_options = ['view', 'sort', 'markdup', 'fixmate', 'merge', 'cat', 'collate', 'fastq', 'fasta'] + pipeline.collect { tool -> + if (!(tool in valid_options)) { + error("Error: ${tool} not a valid pipeline argument for SAMTOOLS_PIPELINE! Valid options are: ${valid_options.join(", ")}") + } + } + + def n_commands = pipeline.size() + def final_command = pipeline[n_commands - 1] + + // Build output string based on final command + def output_string = "" + def input_reference = (fasta && input.getExtension() == "cram") ? "--reference ${fasta}" : "" + def output_reference = "" + + if (final_command in ['view', 'sort', 'merge', 'cat', 'markdup', 'fixmate', 'merge', 'cat', 'collate']) { + // These produce alignment files + def argsKey = n_commands == 1 ? "args" : "args${n_commands}" + def argsLast = task.ext[argsKey] ?: "" + def extension = argsLast.contains("--output-fmt sam") + ? "sam" + : argsLast.contains("--output-fmt cram") + ? "cram" + : "bam" + output_reference = (fasta && input.getExtension() == "cram") ? "--reference ${fasta}" : "" + output_string = "-o ${prefix}.${extension}" + } else if (final_command == "fasta") { + // fasta produces multiple files with special output flags + output_string = "-0 ${prefix}_other.fasta.gz" + if (!meta.single_end) { + output_string = output_string + " -1 ${prefix}_1.fasta.gz -2 ${prefix}_2.fasta.gz -s ${prefix}_singleton.fasta.gz" + } else { + output_string = output_string + " -1 ${prefix}_1.fasta.gz -s ${prefix}_singleton.fasta.gz" + } + } else if (final_command == "fastq") { + // fastq produces multiple files with special output flags + output_string = "-0 ${prefix}_other.fastq.gz" + if (!meta.single_end) { + output_string = output_string + " -1 ${prefix}_1.fastq.gz -2 ${prefix}_2.fastq.gz -s ${prefix}_singleton.fastq.gz" + } else { + output_string = output_string + " -1 ${prefix}_1.fastq.gz -s ${prefix}_singleton.fastq.gz" + } + } + + // Build the pipeline command + def pipeline_command = pipeline.withIndex().collect { subcommand, idx -> + def argsKey = idx == 0 ? "args" : "args${idx + 1}" + def taskArgs = task.ext[argsKey] ?: "" + + def cmd_parts = ["samtools", subcommand] + if (taskArgs) { + cmd_parts << taskArgs + } + if (idx == 0) { + if (input_reference) { + cmd_parts << input_reference + } + cmd_parts << (input instanceof List ? input.join(" ") : input) + } + if (idx == n_commands - 1) { + if (output_reference) { + cmd_parts << output_reference + } + cmd_parts << output_string + } + + return cmd_parts.join(" ") + }.join(" |\\\n") + + // EXAMPLE: + // + // This module will construct a samtools pipeline command from an input list of + // subtools, such as [view, sort, markdup]: + // + // samtools view ${args} input.bam |\ + // samtools sort ${args2} |\ + // samtools markdup ${args3} -o output.bam + // + // The args are numbered sequenctially for each tool in the sequence and CRAM references + // are automatically applied if needed. FASTA and FASTQ outputs are also available. + """ + ${pipeline_command} + """ + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + + def valid_options = ['view', 'sort', 'markdup', 'fixmate', 'merge', 'cat', 'collate', 'fastq', 'fasta'] + pipeline.collect { tool -> + if (!(tool in valid_options)) { + error("Error: ${tool} not a valid pipeline argument for SAMTOOLS_PIPELINE! Valid options are: ${valid_options.join(", ")}") + } + } + + def n_commands = pipeline.size() + def final_command = pipeline[n_commands - 1] + + def stub_outputs = [] + + if (final_command in ['view', 'sort', 'merge', 'cat', 'markdup', 'fixmate', 'collate']) { + def argsKey = n_commands == 1 ? "args" : "args${n_commands}" + def argsLast = task.ext[argsKey] ?: "" + def extension = argsLast.contains("--output-fmt sam") + ? "sam" + : argsLast.contains("--output-fmt cram") + ? "cram" + : "bam" + stub_outputs << "touch ${prefix}.${extension}" + } else if (final_command == "fasta") { + if (meta.single_end) { + stub_outputs << "echo | gzip > ${prefix}_1.fasta.gz" + stub_outputs << "echo | gzip > ${prefix}_singleton.fasta.gz" + } else { + stub_outputs << "echo | gzip > ${prefix}_1.fasta.gz" + stub_outputs << "echo | gzip > ${prefix}_2.fasta.gz" + stub_outputs << "echo | gzip > ${prefix}_singleton.fasta.gz" + } + stub_outputs << "echo | gzip > ${prefix}_other.fasta.gz" + } else if (final_command == "fastq") { + if (meta.single_end) { + stub_outputs << "echo | gzip > ${prefix}_1.fastq.gz" + stub_outputs << "echo | gzip > ${prefix}_singleton.fastq.gz" + } else { + stub_outputs << "echo | gzip > ${prefix}_1.fastq.gz" + stub_outputs << "echo | gzip > ${prefix}_2.fastq.gz" + stub_outputs << "echo | gzip > ${prefix}_singleton.fastq.gz" + } + stub_outputs << "echo | gzip > ${prefix}_other.fastq.gz" + } + + """ + ${stub_outputs.join("\n")} + """ +} diff --git a/modules/nf-core/samtools/multicommand/meta.yml b/modules/nf-core/samtools/multicommand/meta.yml new file mode 100644 index 000000000000..d5fd06672793 --- /dev/null +++ b/modules/nf-core/samtools/multicommand/meta.yml @@ -0,0 +1,185 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "samtools_multicommand" +description: | + Execute a series of samtools commands in a pipeline, allowing flexible composition of + samtools operations such as view, sort, markdup, merge, fastq conversion and more. + + The exact order of operations is specified by the pipeline input, which takes a list such as + [view, sort, markdup]. This is then interpolated to a script where the output of each command + is streamed to the next before being written to disk. The script can handle references being passed, + as well as writing indexes, outputting in CRAM format, and conversion to FASTA and FASTQ. + + samtools view ${args} input.bam |\ + samtools sort ${args2} |\ + samtools markdup ${args3} -o output.bam +keywords: + - view + - sort + - markdup + - fixmate + - merge + - cat + - collate + - fastq + - fasta + - bam + - sam + - cram +tools: + - samtools: + description: | + SAMtools is a set of utilities for interacting with and post-processing + short DNA sequence read alignments in the SAM, BAM and CRAM formats, written by Heng Li. + These files are generated as output by short read aligners like BWA. + homepage: http://www.htslib.org/ + documentation: http://www.htslib.org/doc/samtools.html + doi: 10.1093/bioinformatics/btp352 + licence: + - "MIT" + identifier: biotools:samtools +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - input: + type: file + description: BAM/CRAM/SAM file + pattern: "*.{bam,cram,sam}" + ontologies: + - edam: http://edamontology.org/data_0924 # sequence_trace + - edam: http://edamontology.org/format_2572 # BAM + - edam: http://edamontology.org/format_3462 # CRAM + - edam: http://edamontology.org/format_2573 # SAM + - index: + type: file + description: BAM.BAI/BAM.CSI/CRAM.CRAI file (optional) + pattern: "*.{bai,csi,crai}" + ontologies: + - edam: http://edamontology.org/format_3326 # Index + - - meta2: + type: map + description: | + Groovy Map containing reference information + e.g. [ id:'genome' ] + - fasta: + type: file + description: Fasta reference file + pattern: "*.{fasta,fa}" + ontologies: + - edam: http://edamontology.org/format_1929 # FASTA + - fai: + type: file + description: Fasta reference file index + pattern: "*.{fai}" + ontologies: + - edam: http://edamontology.org/format_3326 # Index + - pipeline: + type: list + description: | + List of samtools commands to execute in sequence. + Valid options: view, sort, markdup, fixmate, merge, cat, collate, fastq, fasta +output: + bam: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.bam": + type: file + description: optional BAM file output + pattern: "*.{bam}" + ontologies: + - edam: http://edamontology.org/data_0924 # sequence_trace + - edam: http://edamontology.org/format_2572 # BAM + cram: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.cram": + type: file + description: optional CRAM file output + pattern: "*.{cram}" + ontologies: + - edam: http://edamontology.org/data_0924 # sequence_trace + - edam: http://edamontology.org/format_3462 # CRAM + sam: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.sam": + type: file + description: optional SAM file output + pattern: "*.{sam}" + ontologies: + - edam: http://edamontology.org/data_0924 # sequence_trace + - edam: http://edamontology.org/format_2573 # SAM + index: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.{bai,csi,crai}": + type: file + description: optional index file for alignment output + pattern: "*.{bai,csi,crai}" + ontologies: + - edam: http://edamontology.org/format_3326 # Index + fasta: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.fasta.gz": + type: file + description: FASTA files output when final command is "fasta". + pattern: "*.fasta.gz" + ontologies: + - edam: http://edamontology.org/format_1929 # FASTA + - edam: http://edamontology.org/format_3989 # GZIP + fastq: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.fastq.gz": + type: file + description: FASTQ file output when final command is "fastq". + pattern: "*.fastq.gz" + ontologies: + - edam: http://edamontology.org/format_1930 # FASTQ + - edam: http://edamontology.org/format_3989 # GZIP + versions_samtools: + - - ${task.process}: + type: string + description: Name of the process + - samtools: + type: string + description: Name of the tool + - samtools version | sed "1!d;s/.* //": + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: Name of the process + - samtools: + type: string + description: Name of the tool + - samtools version | sed "1!d;s/.* //": + type: eval + description: The expression to obtain the version of the tool +authors: + - "@prototaxites" +maintainers: + - "@prototaxites" diff --git a/modules/nf-core/samtools/multicommand/tests/main.nf.test b/modules/nf-core/samtools/multicommand/tests/main.nf.test new file mode 100644 index 000000000000..404dfff02911 --- /dev/null +++ b/modules/nf-core/samtools/multicommand/tests/main.nf.test @@ -0,0 +1,343 @@ +nextflow_process { + + name "Test Process SAMTOOLS_MULTICOMMAND" + script "../main.nf" + config "./nextflow.config" + process "SAMTOOLS_MULTICOMMAND" + + tag "modules" + tag "modules_nfcore" + tag "samtools" + tag "samtools/multicommand" + + test("bam - view, sort") { + + when { + + params { + pipeline_args = "-h -F4" + pipeline_args2 = "--write-index" + } + + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.bam', checkIfExists: true), + [] + ] + input[1] = [[],[],[]] + input[2] = ["view", "sort"] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out, unstableKeys: ["bam", "cram", "index"])).match()} + ) + } + } + + test("bam - view, sort - to cram") { + + when { + + params { + pipeline_args = "-h -F4" + pipeline_args2 = "-n --output-fmt cram" + } + + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.bam', checkIfExists: true), + [] + ] + input[1] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.fai', checkIfExists: true) + ] + input[2] = ["view", "sort"] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out, unstableKeys: ["bam", "cram"])).match()} + ) + } + } + + test("bam - view, fastq") { + + when { + + params { + pipeline_args = "-h -F4" + pipeline_args2 = "" + } + + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.bam', checkIfExists: true), + [] + ] + input[1] = [[],[],[]] + input[2] = ["view", "fastq"] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out, unstableKeys: ["fastq"])).match()} + ) + } + } + + test("cram - view, fasta") { + + when { + + params { + pipeline_args = "-h -F4" + pipeline_args2 = "" + } + + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.sorted.cram', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.sorted.cram.crai', checkIfExists: true) + ] + input[1] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.fai', checkIfExists: true) + ] + input[2] = ["view", "fasta"] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out, unstableKeys: ["fasta"])).match()} + ) + } + } + + test("bam - cat, view, sort") { + + when { + + params { + pipeline_args = "" + pipeline_args2 = "-h -F4" + pipeline_args3 = "-n" + } + + process { + """ + input[0] = [ + [ id:'test' ], + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true) + ], + [] + ] + input[1] = [[],[],[]] + input[2] = ["cat", "view", "sort"] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out, unstableKeys: ["bam", "cram", "index"])).match()} + ) + } + } + + test("cram - view, stats - invalid arg") { + + when { + + params { + pipeline_args = "-h -F4" + pipeline_args2 = "" + pipeline_args3 = "" + } + + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.sorted.cram', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.sorted.cram.crai', checkIfExists: true) + ] + input[1] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.fai', checkIfExists: true) + ] + input[2] = ["view", "stats"] + """ + } + } + + then { + { assert process.success == false } + } + } + + test("bam - view, sort - stub") { + + options "-stub" + + when { + + params { + pipeline_args = "-h -F4" + pipeline_args2 = "-n" + } + + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.bam', checkIfExists: true), + [] + ] + input[1] = [[],[],[]] + input[2] = ["view", "sort"] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out)).match()} + ) + } + } + + test("bam - view, sort - to cram - stub") { + + options "-stub" + + when { + + params { + pipeline_args = "-h -F4" + pipeline_args2 = "-n --output-fmt cram" + } + + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.bam', checkIfExists: true), + [] + ] + input[1] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.fai', checkIfExists: true) + ] + input[2] = ["view", "sort"] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out)).match()} + ) + } + } + + test("cram - view, fasta - stub") { + + options "-stub" + + when { + + params { + pipeline_args = "-h -F4" + pipeline_args2 = "" + } + + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.sorted.cram', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.sorted.cram.crai', checkIfExists: true) + ] + input[1] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.fai', checkIfExists: true) + ] + input[2] = ["view", "fasta"] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out)).match()} + ) + } + } + + test("bam - view, fastq - stub") { + + options "-stub" + + when { + + params { + pipeline_args = "-h -F4" + pipeline_args2 = "" + } + + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.bam', checkIfExists: true), + [] + ] + input[1] = [[],[],[]] + input[2] = ["view", "fastq"] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out)).match()} + ) + } + } + +} diff --git a/modules/nf-core/samtools/multicommand/tests/main.nf.test.snap b/modules/nf-core/samtools/multicommand/tests/main.nf.test.snap new file mode 100644 index 000000000000..c529b6abe940 --- /dev/null +++ b/modules/nf-core/samtools/multicommand/tests/main.nf.test.snap @@ -0,0 +1,396 @@ +{ + "cram - view, fasta": { + "content": [ + { + "bam": [ + + ], + "cram": [ + + ], + "fasta": [ + [ + { + "id": "test" + }, + [ + "test_1.fasta.gz", + "test_2.fasta.gz", + "test_other.fasta.gz", + "test_singleton.fasta.gz" + ] + ] + ], + "fastq": [ + + ], + "index": [ + + ], + "sam": [ + + ], + "versions_samtools": [ + [ + "SAMTOOLS_MULTICOMMAND", + "samtools", + "1.23.1" + ] + ] + } + ], + "timestamp": "2026-05-07T09:48:26.530571", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.4" + } + }, + "bam - view, sort - stub": { + "content": [ + { + "bam": [ + [ + { + "id": "test" + }, + "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "cram": [ + + ], + "fasta": [ + + ], + "fastq": [ + + ], + "index": [ + + ], + "sam": [ + + ], + "versions_samtools": [ + [ + "SAMTOOLS_MULTICOMMAND", + "samtools", + "1.23.1" + ] + ] + } + ], + "timestamp": "2026-05-07T09:39:57.361267", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.4" + } + }, + "cram - view, fasta - stub": { + "content": [ + { + "bam": [ + + ], + "cram": [ + + ], + "fasta": [ + [ + { + "id": "test" + }, + [ + "test_1.fasta.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_2.fasta.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_other.fasta.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_singleton.fasta.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "fastq": [ + + ], + "index": [ + + ], + "sam": [ + + ], + "versions_samtools": [ + [ + "SAMTOOLS_MULTICOMMAND", + "samtools", + "1.23.1" + ] + ] + } + ], + "timestamp": "2026-05-07T09:40:08.222457", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.4" + } + }, + "bam - view, sort - to cram - stub": { + "content": [ + { + "bam": [ + + ], + "cram": [ + [ + { + "id": "test" + }, + "test.cram:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "fasta": [ + + ], + "fastq": [ + + ], + "index": [ + + ], + "sam": [ + + ], + "versions_samtools": [ + [ + "SAMTOOLS_MULTICOMMAND", + "samtools", + "1.23.1" + ] + ] + } + ], + "timestamp": "2026-05-07T09:40:02.924175", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.4" + } + }, + "bam - view, sort - to cram": { + "content": [ + { + "bam": [ + + ], + "cram": [ + [ + { + "id": "test" + }, + "test.cram" + ] + ], + "fasta": [ + + ], + "fastq": [ + + ], + "index": [ + + ], + "sam": [ + + ], + "versions_samtools": [ + [ + "SAMTOOLS_MULTICOMMAND", + "samtools", + "1.23.1" + ] + ] + } + ], + "timestamp": "2026-05-07T09:38:28.15403", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.4" + } + }, + "bam - view, fastq": { + "content": [ + { + "bam": [ + + ], + "cram": [ + + ], + "fasta": [ + + ], + "fastq": [ + [ + { + "id": "test" + }, + [ + "test_1.fastq.gz", + "test_2.fastq.gz", + "test_other.fastq.gz", + "test_singleton.fastq.gz" + ] + ] + ], + "index": [ + + ], + "sam": [ + + ], + "versions_samtools": [ + [ + "SAMTOOLS_MULTICOMMAND", + "samtools", + "1.23.1" + ] + ] + } + ], + "timestamp": "2026-05-07T09:48:20.5407", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.4" + } + }, + "bam - cat, view, sort": { + "content": [ + { + "bam": [ + [ + { + "id": "test" + }, + "test.bam" + ] + ], + "cram": [ + + ], + "fasta": [ + + ], + "fastq": [ + + ], + "index": [ + + ], + "sam": [ + + ], + "versions_samtools": [ + [ + "SAMTOOLS_MULTICOMMAND", + "samtools", + "1.23.1" + ] + ] + } + ], + "timestamp": "2026-05-07T09:59:25.276955", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.4" + } + }, + "bam - view, sort": { + "content": [ + { + "bam": [ + [ + { + "id": "test" + }, + "test.bam" + ] + ], + "cram": [ + + ], + "fasta": [ + + ], + "fastq": [ + + ], + "index": [ + [ + { + "id": "test" + }, + "test.bam.csi" + ] + ], + "sam": [ + + ], + "versions_samtools": [ + [ + "SAMTOOLS_MULTICOMMAND", + "samtools", + "1.23.1" + ] + ] + } + ], + "timestamp": "2026-05-07T09:59:06.114481", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.4" + } + }, + "bam - view, fastq - stub": { + "content": [ + { + "bam": [ + + ], + "cram": [ + + ], + "fasta": [ + + ], + "fastq": [ + [ + { + "id": "test" + }, + [ + "test_1.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_2.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_other.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_singleton.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "index": [ + + ], + "sam": [ + + ], + "versions_samtools": [ + [ + "SAMTOOLS_MULTICOMMAND", + "samtools", + "1.23.1" + ] + ] + } + ], + "timestamp": "2026-05-07T09:40:12.805288", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.4" + } + } +} \ No newline at end of file diff --git a/modules/nf-core/samtools/multicommand/tests/nextflow.config b/modules/nf-core/samtools/multicommand/tests/nextflow.config new file mode 100644 index 000000000000..bf95f4409cb2 --- /dev/null +++ b/modules/nf-core/samtools/multicommand/tests/nextflow.config @@ -0,0 +1,11 @@ +process { + withName: SAMTOOLS_MULTICOMMAND { + ext.args = { params.pipeline_args ?: "" } + ext.args2 = { params.pipeline_args2 ?: "" } + ext.args3 = { params.pipeline_args3 ?: "" } + ext.args4 = { params.pipeline_args4 ?: "" } + ext.args5 = { params.pipeline_args5 ?: "" } + ext.args6 = { params.pipeline_args6 ?: "" } + ext.args7 = { params.pipeline_args7 ?: "" } + } +} diff --git a/modules/nf-core/samtools/pipeline/environment.yml b/modules/nf-core/samtools/pipeline/environment.yml deleted file mode 100644 index 7ffa2d14cf20..000000000000 --- a/modules/nf-core/samtools/pipeline/environment.yml +++ /dev/null @@ -1,10 +0,0 @@ ---- -# 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. - - "bioconda::samtools=1.23.1" diff --git a/modules/nf-core/samtools/pipeline/main.nf b/modules/nf-core/samtools/pipeline/main.nf deleted file mode 100644 index 788376ed69b5..000000000000 --- a/modules/nf-core/samtools/pipeline/main.nf +++ /dev/null @@ -1,170 +0,0 @@ -process SAMTOOLS_PIPELINE { - tag "$meta.id" - label 'process_medium' - - conda "${moduleDir}/environment.yml" - container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container - ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/8c/8c5d2818c8b9f58e1fba77ce219fdaf32087ae53e857c4a496402978af26e78c/data' - : 'community.wave.seqera.io/library/htslib_samtools:1.23.1--5b6bb4ede7e612e5'}" - - input: - tuple val(meta), path(bam) - val(pipeline) - - output: - // Alignment format outputs (view, sort, markdup, merge, cat, collate) - tuple val(meta), path("*.bam"), optional: true, emit: bam - tuple val(meta), path("*.cram"), optional: true, emit: cram - tuple val(meta), path("*.sam"), optional: true, emit: sam - tuple val(meta), path("*.{bai,csi,crai}"), optional: true, emit: index - - // Sequence outputs (fasta, fastq) - tuple val(meta), path("*.fasta.gz"), optional: true, emit: fasta - tuple val(meta), path("*.fastq.gz"), optional: true, emit: fastq - tuple val(meta), path("*_interleaved.*"), optional: true, emit: interleaved - - // Statistics outputs (coverage, depth, stats, idxstats, flagstat) - tuple val(meta), path("*.txt"), optional: true, emit: coverage - tuple val(meta), path("*.tsv"), optional: true, emit: depth - tuple val(meta), path("*.stats"), optional: true, emit: stats - tuple val(meta), path("*.idxstats"), optional: true, emit: idxstats - tuple val(meta), path("*.flagstat"), optional: true, emit: flagstat - - tuple val("${task.process}"), val('samtools'), eval("samtools --version"), topic: versions, emit: versions_samtools - - when: - task.ext.when == null || task.ext.when - - script: - def args = task.ext.args ?: '' - def prefix = task.ext.prefix ?: "${meta.id}" - def n_commands = pipeline.size() - def final_command = pipeline[n_commands - 1] - - // Build output string based on final command - def output_string = "" - - if (final_command in ['view', 'sort', 'markdup', 'fixmate', 'merge', 'cat', 'collate']) { - // These produce alignment files - def argsLast = task.ext["args${n_commands - 1}"] ?: "" - def extension = argsLast.contains("--output-fmt sam") - ? "sam" - : argsLast.contains("--output-fmt cram") - ? "cram" - : "bam" - output_string = "-o ${prefix}.${extension}" - } else if (final_command == "fasta") { - // fasta produces multiple files with special output flags - output_string = "-0 ${prefix}_other.fasta.gz" - if (!meta.single_end) { - output_string = output_string + " -1 ${prefix}_1.fasta.gz -2 ${prefix}_2.fasta.gz -s ${prefix}_singleton.fasta.gz" - } else { - output_string = output_string + " -1 ${prefix}_1.fasta.gz -s ${prefix}_singleton.fasta.gz" - } - } else if (final_command == "fastq") { - // fastq produces multiple files with special output flags - output_string = "-0 ${prefix}_other.fastq.gz" - if (!meta.single_end) { - output_string = output_string + " -1 ${prefix}_1.fastq.gz -2 ${prefix}_2.fastq.gz -s ${prefix}_singleton.fastq.gz" - } else { - output_string = output_string + " -1 ${prefix}_1.fastq.gz -s ${prefix}_singleton.fastq.gz" - } - } else if (final_command == "coverage") { - // coverage produces a txt file - output_string = "-o ${prefix}.txt" - } else if (final_command == "depth") { - // depth produces a tsv file - output_string = "-o ${prefix}.tsv" - } else if (final_command == "stats") { - // stats produces a stats file - output_string = "> ${prefix}.stats" - } else if (final_command == "idxstats") { - // idxstats produces an idxstats file - output_string = "> ${prefix}.idxstats" - } else if (final_command == "flagstat") { - // flagstat produces a flagstat file - output_string = "> ${prefix}.flagstat" - } - - // Build the pipeline command - def pipeline_command = pipeline.withIndex().collect { subcommand, idx -> - def argsKey = "args${idx}" - def taskArgs = task.ext[argsKey] ?: "" - - def cmd = "" - if (idx == 0) { - // First command: read from input BAM - cmd = "samtools ${subcommand} ${taskArgs} ${bam}" - } else { - // Subsequent commands: read from stdin - cmd = "samtools ${subcommand} ${taskArgs}" - } - - // Last command: add output string - if (idx == n_commands - 1) { - cmd = cmd + " ${output_string}" - } - - cmd - }.join(" |\\\n") - """ - ${pipeline_command} - """ - - stub: - def args = task.ext.args ?: '' - def prefix = task.ext.prefix ?: "${meta.id}" - def n_commands = pipeline.size() - def final_command = pipeline[n_commands - 1] - - def stub_outputs = "" - - if (final_command in ['view', 'sort', 'markdup', 'fixmate', 'collate']) { - def argsLast = task.ext["args${n_commands - 1}"] ?: "" - def extension = argsLast.contains("--output-fmt sam") - ? "sam" - : argsLast.contains("--output-fmt cram") - ? "cram" - : "bam" - stub_outputs = "touch ${prefix}.${extension}" - } else if (final_command == "merge") { - def file_type = bam instanceof List ? bam[0].getExtension() : bam.getExtension() - stub_outputs = "touch ${prefix}.${file_type}" - } else if (final_command == "cat") { - def file_type = bam instanceof List ? bam[0].getExtension() : bam.getExtension() - stub_outputs = "touch ${prefix}.${file_type}" - } else if (final_command == "fasta") { - if (meta.single_end) { - stub_outputs = "echo | gzip > ${prefix}_1.fasta.gz" - stub_outputs = "echo | gzip > ${prefix}_singleton.fasta.gz" - } else { - stub_outputs = "echo | gzip > ${prefix}_1.fasta.gz" - stub_outputs = "echo | gzip > ${prefix}_2.fasta.gz" - stub_outputs = "echo | gzip > ${prefix}_singleton.fasta.gz" - } - } else if (final_command == "fastq") { - if (meta.single_end) { - stub_outputs = "echo | gzip > ${prefix}_1.fastq.gz" - stub_outputs = "echo | gzip > ${prefix}_singleton.fastq.gz" - } else { - stub_outputs = "echo | gzip > ${prefix}_1.fastq.gz" - stub_outputs = "echo | gzip > ${prefix}_2.fastq.gz" - stub_outputs = "echo | gzip > ${prefix}_singleton.fastq.gz" - } - stub_outputs = "touch ${prefix}_other.fastq.gz" - } else if (final_command == "coverage") { - stub_outputs = "touch ${prefix}.txt" - } else if (final_command == "depth") { - stub_outputs = "touch ${prefix}.tsv" - } else if (final_command == "stats") { - stub_outputs = "touch ${prefix}.stats" - } else if (final_command == "idxstats") { - stub_outputs = "touch ${prefix}.idxstats" - } else if (final_command == "flagstat") { - stub_outputs = "touch ${prefix}.flagstat" - } - - """ - ${stub_outputs} - """ -} diff --git a/modules/nf-core/samtools/pipeline/meta.yml b/modules/nf-core/samtools/pipeline/meta.yml deleted file mode 100644 index a861a381a7e9..000000000000 --- a/modules/nf-core/samtools/pipeline/meta.yml +++ /dev/null @@ -1,75 +0,0 @@ -# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json -# # TODO nf-core: Add a description of the module and list keywords -name: "samtools_pipeline" -description: write your description here -keywords: - - sort - - example - - genomics -tools: - ## TODO nf-core: Add a description and other details for the software below - - "samtools": - description: "Tools for dealing with SAM, BAM and CRAM files" - homepage: "None" - documentation: "None" - tool_dev_url: "None" - doi: "" - licence: ["MIT"] - identifier: biotools:samtools - -input: - # TODO nf-core: Update the information obtained from bio.tools and make sure that it is correct - - - meta: - type: map - description: Groovy Map containing sample information. e.g. `[ - id:'sample1' ]` - - sequence_trace: - type: file - description: sequence_trace file - pattern: "*.{bam,cram,sam}" - ontologies: - - edam: http://edamontology.org/data_0924 # Sequence trace - - edam: http://edamontology.org/format_2572 # BAM - - edam: http://edamontology.org/format_3462 # CRAM - - edam: http://edamontology.org/format_2573 # SAM -output: - # TODO nf-core: Update the information obtained from bio.tools and make sure that it is correct - sequence_trace: - - - meta: - type: map - description: Groovy Map containing sample information. e.g. `[ - id:'sample1' ]` - - "*.{bam,cram,sam}": - type: file - description: sequence_trace file - pattern: "*.{bam,cram,sam}" - ontologies: - - edam: http://edamontology.org/data_0924 # Sequence trace - - edam: http://edamontology.org/format_2572 # BAM - - edam: http://edamontology.org/format_3462 # CRAM - - edam: http://edamontology.org/format_2573 # SAM - versions_samtools: - - - ${task.process}: - type: string - description: The name of the process - - samtools: - type: string - description: The name of the tool - - samtools --version: - type: eval - description: The expression to obtain the version of the tool -topics: - versions: - - - ${task.process}: - type: string - description: The name of the process - - samtools: - type: string - description: The name of the tool - - samtools --version: - type: eval - description: The expression to obtain the version of the tool -authors: - - "@prototaxites" -maintainers: - - "@prototaxites" diff --git a/modules/nf-core/samtools/pipeline/tests/main.nf.test b/modules/nf-core/samtools/pipeline/tests/main.nf.test deleted file mode 100644 index 31768c933a80..000000000000 --- a/modules/nf-core/samtools/pipeline/tests/main.nf.test +++ /dev/null @@ -1,76 +0,0 @@ -// TODO nf-core: Once you have added the required tests, please run the following command to build this file: -// nf-core modules test samtools/pipeline -nextflow_process { - - name "Test Process SAMTOOLS_PIPELINE" - script "../main.nf" - process "SAMTOOLS_PIPELINE" - - tag "modules" - tag "modules_nfcore" - tag "samtools" - tag "samtools/pipeline" - - // 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 nf-test docs (https://www.nf-test.com/docs/testcases/setup/). - - 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, - process.out.findAll { key, val -> key.startsWith('versions') } - ).match() } - //TODO nf-core: Add all required assertions to verify the test output. - // See https://nf-co.re/docs/developing/testing/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).match() } - ) - } - - } - -} From cb89c20163af71ad27a7e1e3a989d86fdd6ca02b Mon Sep 17 00:00:00 2001 From: Jim Downie Date: Thu, 7 May 2026 13:33:32 +0100 Subject: [PATCH 4/4] fix: comments from reviews --- modules/nf-core/samtools/multicommand/main.nf | 24 +- .../nf-core/samtools/multicommand/meta.yml | 137 ++++++-- .../samtools/multicommand/tests/main.nf.test | 4 +- .../multicommand/tests/main.nf.test.snap | 306 +++++++++++++++++- 4 files changed, 437 insertions(+), 34 deletions(-) diff --git a/modules/nf-core/samtools/multicommand/main.nf b/modules/nf-core/samtools/multicommand/main.nf index 6e5d388ca7fb..b80ccafbbe51 100644 --- a/modules/nf-core/samtools/multicommand/main.nf +++ b/modules/nf-core/samtools/multicommand/main.nf @@ -20,8 +20,16 @@ process SAMTOOLS_MULTICOMMAND { tuple val(meta), path("*.{bai,csi,crai}"), optional: true, emit: index // Sequence outputs (fasta, fastq) - tuple val(meta), path("*.fasta.gz"), optional: true, emit: fasta - tuple val(meta), path("*.fastq.gz"), optional: true, emit: fastq + tuple val(meta), path("*.fasta.gz") , emit: fasta , optional: true + tuple val(meta), path("*.fastq.gz") , emit: fastq , optional: true + tuple val(meta), path("*_{1,2}.fasta.gz") , emit: fasta_pair , optional: true + tuple val(meta), path("*_interleaved.fasta.gz"), emit: fasta_interleaved, optional: true + tuple val(meta), path("*_singleton.fasta.gz") , emit: fasta_singleton , optional: true + tuple val(meta), path("*_other.fasta.gz") , emit: fasta_other , optional: true + tuple val(meta), path("*_{1,2}.fastq.gz") , emit: fastq_pair , optional: true + tuple val(meta), path("*_interleaved.fastq") , emit: fastq_interleaved, optional: true + tuple val(meta), path("*_singleton.fastq.gz") , emit: fastq_singleton , optional: true + tuple val(meta), path("*_other.fastq.gz") , emit: fastq_other , optional: true tuple val("${task.process}"), val('samtools'), eval('samtools version | sed "1!d;s/.* //"'), emit: versions_samtools, topic: versions @@ -32,10 +40,14 @@ process SAMTOOLS_MULTICOMMAND { def args = task.ext.args ?: '' def prefix = task.ext.prefix ?: "${meta.id}" + if (pipeline.size() <=1) { + error("Error: SAMTOOLS_MULTICOMMAND requires at least two commands!") + } + def valid_options = ['view', 'sort', 'markdup', 'fixmate', 'merge', 'cat', 'collate', 'fastq', 'fasta'] pipeline.collect { tool -> if (!(tool in valid_options)) { - error("Error: ${tool} not a valid pipeline argument for SAMTOOLS_PIPELINE! Valid options are: ${valid_options.join(", ")}") + error("Error: ${tool} not a valid pipeline argument for SAMTOOLS_MULTICOMMAND! Valid options are: ${valid_options.join(", ")}") } } @@ -80,8 +92,10 @@ process SAMTOOLS_MULTICOMMAND { def pipeline_command = pipeline.withIndex().collect { subcommand, idx -> def argsKey = idx == 0 ? "args" : "args${idx + 1}" def taskArgs = task.ext[argsKey] ?: "" + def lastCommand = (idx == n_commands - 1) def cmd_parts = ["samtools", subcommand] + if(subcommand != "cat") { cmd_parts << "-@ ${task.cpus}" } if (taskArgs) { cmd_parts << taskArgs } @@ -91,7 +105,9 @@ process SAMTOOLS_MULTICOMMAND { } cmd_parts << (input instanceof List ? input.join(" ") : input) } - if (idx == n_commands - 1) { + if (!lastCommand) { + if (subcommand != "cat") { cmd_parts << "-u" } + } else { if (output_reference) { cmd_parts << output_reference } diff --git a/modules/nf-core/samtools/multicommand/meta.yml b/modules/nf-core/samtools/multicommand/meta.yml index d5fd06672793..56192ed85778 100644 --- a/modules/nf-core/samtools/multicommand/meta.yml +++ b/modules/nf-core/samtools/multicommand/meta.yml @@ -1,4 +1,3 @@ -# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json name: "samtools_multicommand" description: | Execute a series of samtools commands in a pipeline, allowing flexible composition of @@ -48,16 +47,16 @@ input: description: BAM/CRAM/SAM file pattern: "*.{bam,cram,sam}" ontologies: - - edam: http://edamontology.org/data_0924 # sequence_trace - - edam: http://edamontology.org/format_2572 # BAM - - edam: http://edamontology.org/format_3462 # CRAM - - edam: http://edamontology.org/format_2573 # SAM + - edam: http://edamontology.org/data_0924 + - edam: http://edamontology.org/format_2572 + - edam: http://edamontology.org/format_3462 + - edam: http://edamontology.org/format_2573 - index: type: file description: BAM.BAI/BAM.CSI/CRAM.CRAI file (optional) pattern: "*.{bai,csi,crai}" ontologies: - - edam: http://edamontology.org/format_3326 # Index + - edam: http://edamontology.org/format_3326 - - meta2: type: map description: | @@ -68,13 +67,13 @@ input: description: Fasta reference file pattern: "*.{fasta,fa}" ontologies: - - edam: http://edamontology.org/format_1929 # FASTA + - edam: http://edamontology.org/format_1929 - fai: type: file description: Fasta reference file index pattern: "*.{fai}" ontologies: - - edam: http://edamontology.org/format_3326 # Index + - edam: http://edamontology.org/format_3326 - pipeline: type: list description: | @@ -92,8 +91,8 @@ output: description: optional BAM file output pattern: "*.{bam}" ontologies: - - edam: http://edamontology.org/data_0924 # sequence_trace - - edam: http://edamontology.org/format_2572 # BAM + - edam: http://edamontology.org/data_0924 + - edam: http://edamontology.org/format_2572 cram: - - meta: type: map @@ -105,8 +104,8 @@ output: description: optional CRAM file output pattern: "*.{cram}" ontologies: - - edam: http://edamontology.org/data_0924 # sequence_trace - - edam: http://edamontology.org/format_3462 # CRAM + - edam: http://edamontology.org/data_0924 + - edam: http://edamontology.org/format_3462 sam: - - meta: type: map @@ -118,8 +117,8 @@ output: description: optional SAM file output pattern: "*.{sam}" ontologies: - - edam: http://edamontology.org/data_0924 # sequence_trace - - edam: http://edamontology.org/format_2573 # SAM + - edam: http://edamontology.org/data_0924 + - edam: http://edamontology.org/format_2573 index: - - meta: type: map @@ -131,7 +130,7 @@ output: description: optional index file for alignment output pattern: "*.{bai,csi,crai}" ontologies: - - edam: http://edamontology.org/format_3326 # Index + - edam: http://edamontology.org/format_3326 fasta: - - meta: type: map @@ -143,8 +142,8 @@ output: description: FASTA files output when final command is "fasta". pattern: "*.fasta.gz" ontologies: - - edam: http://edamontology.org/format_1929 # FASTA - - edam: http://edamontology.org/format_3989 # GZIP + - edam: http://edamontology.org/format_1929 + - edam: http://edamontology.org/format_3989 fastq: - - meta: type: map @@ -156,8 +155,108 @@ output: description: FASTQ file output when final command is "fastq". pattern: "*.fastq.gz" ontologies: - - edam: http://edamontology.org/format_1930 # FASTQ - - edam: http://edamontology.org/format_3989 # GZIP + - edam: http://edamontology.org/format_1930 + - edam: http://edamontology.org/format_3989 + fasta_pair: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*_{1,2}.fasta.gz": + type: file + description: Compressed FASTA file(s) with reads with either the READ1 or + READ2 flag set in separate files. + pattern: "*_{1,2}.fasta.gz" + ontologies: [] + fasta_interleaved: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*_interleaved.fasta.gz": + type: file + description: Compressed FASTA file with reads with either the READ1 or READ2 + flag set in a combined file. Needs collated input file. + pattern: "*_interleaved.fasta.gz" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + fasta_singleton: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*_singleton.fasta.gz": + type: file + description: Compressed FASTA file with singleton reads + pattern: "*_singleton.fasta.gz" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + fasta_other: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*_other.fasta.gz": + type: file + description: Compressed FASTA file with reads with either both READ1 and READ2 + flags set or unset + pattern: "*_other.fasta.gz" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + fastq_pair: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*_{1,2}.fastq.gz": + type: file + description: Compressed FASTQ file(s) with reads with either the READ1 + or READ2 flag set in separate files. + pattern: "*_{1,2}.fastq.gz" + ontologies: [] + fastq_interleaved: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*_interleaved.fastq": + type: file + description: Compressed FASTQ file with reads with either the READ1 or + READ2 flag set in a combined file. Needs collated input file. + pattern: "*_interleaved.fastq.gz" + ontologies: + - edam: http://edamontology.org/format_3989 + fastq_singleton: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*_singleton.fastq.gz": + type: file + description: Compressed FASTQ file with singleton reads + pattern: "*_singleton.fastq.gz" + ontologies: + - edam: http://edamontology.org/format_3989 + fastq_other: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*_other.fastq.gz": + type: file + description: Compressed FASTQ file with reads with either both READ1 and + READ2 flags set or unset + pattern: "*_other.fastq.gz" + ontologies: + - edam: http://edamontology.org/format_3989 versions_samtools: - - ${task.process}: type: string diff --git a/modules/nf-core/samtools/multicommand/tests/main.nf.test b/modules/nf-core/samtools/multicommand/tests/main.nf.test index 404dfff02911..1b6f2300c000 100644 --- a/modules/nf-core/samtools/multicommand/tests/main.nf.test +++ b/modules/nf-core/samtools/multicommand/tests/main.nf.test @@ -99,7 +99,7 @@ nextflow_process { then { assertAll( { assert process.success }, - { assert snapshot(sanitizeOutput(process.out, unstableKeys: ["fastq"])).match()} + { assert snapshot(sanitizeOutput(process.out, unstableKeys: ["fastq", "fastq_other"])).match()} ) } } @@ -133,7 +133,7 @@ nextflow_process { then { assertAll( { assert process.success }, - { assert snapshot(sanitizeOutput(process.out, unstableKeys: ["fasta"])).match()} + { assert snapshot(sanitizeOutput(process.out, unstableKeys: ["fasta", "fasta_other"])).match()} ) } } diff --git a/modules/nf-core/samtools/multicommand/tests/main.nf.test.snap b/modules/nf-core/samtools/multicommand/tests/main.nf.test.snap index c529b6abe940..488c982c72d7 100644 --- a/modules/nf-core/samtools/multicommand/tests/main.nf.test.snap +++ b/modules/nf-core/samtools/multicommand/tests/main.nf.test.snap @@ -21,8 +21,50 @@ ] ] ], + "fasta_interleaved": [ + + ], + "fasta_other": [ + [ + { + "id": "test" + }, + "test_other.fasta.gz" + ] + ], + "fasta_pair": [ + [ + { + "id": "test" + }, + [ + "test_1.fasta.gz:md5,1ec036ad586c3f7ed822526dcede9a86", + "test_2.fasta.gz:md5,2ee17b7f212ade40541b3c74a53786b5" + ] + ] + ], + "fasta_singleton": [ + [ + { + "id": "test" + }, + "test_singleton.fasta.gz:md5,eb5ffa2e84dd8eff7dc1096e07439ec2" + ] + ], "fastq": [ + ], + "fastq_interleaved": [ + + ], + "fastq_other": [ + + ], + "fastq_pair": [ + + ], + "fastq_singleton": [ + ], "index": [ @@ -39,7 +81,7 @@ ] } ], - "timestamp": "2026-05-07T09:48:26.530571", + "timestamp": "2026-05-07T13:31:17.920686", "meta": { "nf-test": "0.9.5", "nextflow": "25.10.4" @@ -61,9 +103,33 @@ ], "fasta": [ + ], + "fasta_interleaved": [ + + ], + "fasta_other": [ + + ], + "fasta_pair": [ + + ], + "fasta_singleton": [ + ], "fastq": [ + ], + "fastq_interleaved": [ + + ], + "fastq_other": [ + + ], + "fastq_pair": [ + + ], + "fastq_singleton": [ + ], "index": [ @@ -80,7 +146,7 @@ ] } ], - "timestamp": "2026-05-07T09:39:57.361267", + "timestamp": "2026-05-07T13:13:20.400143", "meta": { "nf-test": "0.9.5", "nextflow": "25.10.4" @@ -108,8 +174,50 @@ ] ] ], + "fasta_interleaved": [ + + ], + "fasta_other": [ + [ + { + "id": "test" + }, + "test_other.fasta.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "fasta_pair": [ + [ + { + "id": "test" + }, + [ + "test_1.fasta.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_2.fasta.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "fasta_singleton": [ + [ + { + "id": "test" + }, + "test_singleton.fasta.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], "fastq": [ + ], + "fastq_interleaved": [ + + ], + "fastq_other": [ + + ], + "fastq_pair": [ + + ], + "fastq_singleton": [ + ], "index": [ @@ -126,7 +234,7 @@ ] } ], - "timestamp": "2026-05-07T09:40:08.222457", + "timestamp": "2026-05-07T13:13:30.82469", "meta": { "nf-test": "0.9.5", "nextflow": "25.10.4" @@ -148,9 +256,33 @@ ], "fasta": [ + ], + "fasta_interleaved": [ + + ], + "fasta_other": [ + + ], + "fasta_pair": [ + + ], + "fasta_singleton": [ + ], "fastq": [ + ], + "fastq_interleaved": [ + + ], + "fastq_other": [ + + ], + "fastq_pair": [ + + ], + "fastq_singleton": [ + ], "index": [ @@ -167,7 +299,7 @@ ] } ], - "timestamp": "2026-05-07T09:40:02.924175", + "timestamp": "2026-05-07T13:13:25.789164", "meta": { "nf-test": "0.9.5", "nextflow": "25.10.4" @@ -189,9 +321,33 @@ ], "fasta": [ + ], + "fasta_interleaved": [ + + ], + "fasta_other": [ + + ], + "fasta_pair": [ + + ], + "fasta_singleton": [ + ], "fastq": [ + ], + "fastq_interleaved": [ + + ], + "fastq_other": [ + + ], + "fastq_pair": [ + + ], + "fastq_singleton": [ + ], "index": [ @@ -208,7 +364,7 @@ ] } ], - "timestamp": "2026-05-07T09:38:28.15403", + "timestamp": "2026-05-07T13:21:52.709644", "meta": { "nf-test": "0.9.5", "nextflow": "25.10.4" @@ -225,6 +381,18 @@ ], "fasta": [ + ], + "fasta_interleaved": [ + + ], + "fasta_other": [ + + ], + "fasta_pair": [ + + ], + "fasta_singleton": [ + ], "fastq": [ [ @@ -239,6 +407,36 @@ ] ] ], + "fastq_interleaved": [ + + ], + "fastq_other": [ + [ + { + "id": "test" + }, + "test_other.fastq.gz" + ] + ], + "fastq_pair": [ + [ + { + "id": "test" + }, + [ + "test_1.fastq.gz:md5,84747fd2f74f1c69e26b2a2700b9bd12", + "test_2.fastq.gz:md5,1f7475e8cc710e158d7b1469b5f29483" + ] + ] + ], + "fastq_singleton": [ + [ + { + "id": "test" + }, + "test_singleton.fastq.gz:md5,09643fe0c4a8bf5a0650452ac758bf2f" + ] + ], "index": [ ], @@ -254,7 +452,7 @@ ] } ], - "timestamp": "2026-05-07T09:48:20.5407", + "timestamp": "2026-05-07T13:31:12.50357", "meta": { "nf-test": "0.9.5", "nextflow": "25.10.4" @@ -276,9 +474,33 @@ ], "fasta": [ + ], + "fasta_interleaved": [ + + ], + "fasta_other": [ + + ], + "fasta_pair": [ + + ], + "fasta_singleton": [ + ], "fastq": [ + ], + "fastq_interleaved": [ + + ], + "fastq_other": [ + + ], + "fastq_pair": [ + + ], + "fastq_singleton": [ + ], "index": [ @@ -295,7 +517,7 @@ ] } ], - "timestamp": "2026-05-07T09:59:25.276955", + "timestamp": "2026-05-07T13:24:39.736586", "meta": { "nf-test": "0.9.5", "nextflow": "25.10.4" @@ -317,9 +539,33 @@ ], "fasta": [ + ], + "fasta_interleaved": [ + + ], + "fasta_other": [ + + ], + "fasta_pair": [ + + ], + "fasta_singleton": [ + ], "fastq": [ + ], + "fastq_interleaved": [ + + ], + "fastq_other": [ + + ], + "fastq_pair": [ + + ], + "fastq_singleton": [ + ], "index": [ [ @@ -341,7 +587,7 @@ ] } ], - "timestamp": "2026-05-07T09:59:06.114481", + "timestamp": "2026-05-07T13:21:47.787828", "meta": { "nf-test": "0.9.5", "nextflow": "25.10.4" @@ -358,6 +604,18 @@ ], "fasta": [ + ], + "fasta_interleaved": [ + + ], + "fasta_other": [ + + ], + "fasta_pair": [ + + ], + "fasta_singleton": [ + ], "fastq": [ [ @@ -372,6 +630,36 @@ ] ] ], + "fastq_interleaved": [ + + ], + "fastq_other": [ + [ + { + "id": "test" + }, + "test_other.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "fastq_pair": [ + [ + { + "id": "test" + }, + [ + "test_1.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_2.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "fastq_singleton": [ + [ + { + "id": "test" + }, + "test_singleton.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], "index": [ ], @@ -387,7 +675,7 @@ ] } ], - "timestamp": "2026-05-07T09:40:12.805288", + "timestamp": "2026-05-07T13:13:35.285053", "meta": { "nf-test": "0.9.5", "nextflow": "25.10.4"