") },
+ { assert path(process.out.log.get(0).get(1)).getText().contains("total bases: 13683") },
+ { assert snapshot(
+ process.out.reads,
+ process.out.reads_fail,
+ process.out.reads_merged,
+ process.out.findAll { key, val -> key.startsWith('versions') }).match() }
+ )
+ }
+ }
+
+ test("test_fastp_single_end_qc_only") {
+
+ when {
+ process {
+ """
+ adapter_fasta = []
+ discard_trimmed_pass = true
+ save_trimmed_fail = false
+ save_merged = false
+
+ input[0] = Channel.of([
+ [ id:'test', single_end:true ],
+ [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) ],
+ adapter_fasta
+ ])
+
+ input[1] = discard_trimmed_pass
+ input[2] = save_trimmed_fail
+ input[3] = save_merged
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert process.success },
+ { assert path(process.out.html.get(0).get(1)).getText().contains("single end (151 cycles)") },
+ { assert path(process.out.log.get(0).get(1)).getText().contains("reads passed filter: 99") },
+ { assert snapshot(
+ process.out.reads,
+ process.out.reads,
+ process.out.reads_fail,
+ process.out.reads_fail,
+ process.out.reads_merged,
+ process.out.reads_merged,
+ process.out.findAll { key, val -> key.startsWith('versions') }).match() }
+ )
+ }
+ }
+
+ test("test_fastp_paired_end_qc_only") {
+
+ when {
+ process {
+ """
+ adapter_fasta = []
+ discard_trimmed_pass = true
+ save_trimmed_fail = false
+ save_merged = false
+
+ input[0] = Channel.of([
+ [ id:'test', single_end:false ], // meta map
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ ],
+ adapter_fasta
+ ])
+ input[1] = discard_trimmed_pass
+ input[2] = save_trimmed_fail
+ input[3] = save_merged
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert process.success },
+ { assert path(process.out.html.get(0).get(1)).getText().contains("The input has little adapter percentage (~0.000000%), probably it's trimmed before.") },
+ { assert path(process.out.log.get(0).get(1)).getText().contains("Q30 bases: 12281(88.3716%)") },
+ { assert snapshot(
+ process.out.reads,
+ process.out.reads,
+ process.out.reads_fail,
+ process.out.reads_fail,
+ process.out.reads_merged,
+ process.out.reads_merged,
+ process.out.findAll { key, val -> key.startsWith('versions') }).match() }
+ )
+ }
+ }
+
+ test("test_fastp_single_end - stub") {
+
+ options "-stub"
+
+ when {
+
+ process {
+ """
+ adapter_fasta = []
+ discard_trimmed_pass = false
+ save_trimmed_fail = false
+ save_merged = false
+
+ input[0] = Channel.of([
+ [ id:'test', single_end:true ],
+ [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) ],
+ adapter_fasta
+ ])
+ input[1] = discard_trimmed_pass
+ input[2] = save_trimmed_fail
+ input[3] = save_merged
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert process.success },
+ { assert snapshot(process.out).match() }
+ )
+ }
+ }
+
+ test("test_fastp_paired_end - stub") {
+
+ options "-stub"
+
+ when {
+
+ process {
+ """
+ adapter_fasta = []
+ discard_trimmed_pass = false
+ save_trimmed_fail = false
+ save_merged = false
+
+ input[0] = Channel.of([
+ [ id:'test', single_end:false ], // meta map
+ [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) ],
+ adapter_fasta
+ ])
+ input[1] = discard_trimmed_pass
+ input[2] = save_trimmed_fail
+ input[3] = save_merged
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert process.success },
+ { assert snapshot(process.out).match() }
+ )
+ }
+ }
+
+ test("fastp - stub test_fastp_interleaved") {
+
+ options "-stub"
+
+ config './nextflow.interleaved.config'
+ when {
+ process {
+ """
+ adapter_fasta = []
+ discard_trimmed_pass = false
+ save_trimmed_fail = false
+ save_merged = false
+
+ input[0] = Channel.of([
+ [ id:'test', single_end:true ], // meta map
+ [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_interleaved.fastq.gz', checkIfExists: true) ],
+ adapter_fasta
+ ])
+ input[1] = discard_trimmed_pass
+ input[2] = save_trimmed_fail
+ input[3] = save_merged
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert process.success },
+ { assert snapshot(process.out).match() }
+ )
+ }
+ }
+
+ test("test_fastp_single_end_trim_fail - stub") {
+
+ options "-stub"
+
+ when {
+
+ process {
+ """
+ adapter_fasta = []
+ discard_trimmed_pass = false
+ save_trimmed_fail = true
+ save_merged = false
+
+ input[0] = Channel.of([
+ [ id:'test', single_end:true ], // meta map
+ [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) ],
+ adapter_fasta
+ ])
+ input[1] = discard_trimmed_pass
+ input[2] = save_trimmed_fail
+ input[3] = save_merged
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert process.success },
+ { assert snapshot(process.out).match() }
+ )
+ }
+ }
+
+ test("test_fastp_paired_end_trim_fail - stub") {
+
+ options "-stub"
+
+ config './nextflow.save_failed.config'
+ when {
+ process {
+ """
+ adapter_fasta = []
+ discard_trimmed_pass = false
+ save_trimmed_fail = true
+ save_merged = false
+
+ input[0] = Channel.of([
+ [ id:'test', single_end:false ], // meta map
+ [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)],
+ adapter_fasta
+ ])
+ input[1] = discard_trimmed_pass
+ input[2] = save_trimmed_fail
+ input[3] = save_merged
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert process.success },
+ { assert snapshot(process.out).match() }
+ )
+ }
+ }
+
+ test("test_fastp_paired_end_merged - stub") {
+
+ options "-stub"
+
+ when {
+ process {
+ """
+ adapter_fasta = []
+ discard_trimmed_pass = false
+ save_trimmed_fail = false
+ save_merged = true
+
+ input[0] = Channel.of([
+ [ id:'test', single_end:false ], // meta map
+ [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) ],
+ adapter_fasta
+ ])
+ input[1] = discard_trimmed_pass
+ input[2] = save_trimmed_fail
+ input[3] = save_merged
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert process.success },
+ { assert snapshot(process.out).match() }
+ )
+ }
+ }
+
+ test("test_fastp_paired_end_merged_adapterlist - stub") {
+
+ options "-stub"
+
+ when {
+ process {
+ """
+ adapter_fasta = file(params.modules_testdata_base_path + 'delete_me/fastp/adapters.fasta', checkIfExists: true)
+ discard_trimmed_pass = false
+ save_trimmed_fail = false
+ save_merged = true
+
+ input[0] = Channel.of([
+ [ id:'test', single_end:false ], // meta map
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ ],
+ adapter_fasta
+ ])
+ input[1] = discard_trimmed_pass
+ input[2] = save_trimmed_fail
+ input[3] = save_merged
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert process.success },
+ { assert snapshot(process.out).match() }
+ )
+ }
+ }
+
+ test("test_fastp_single_end_qc_only - stub") {
+
+ options "-stub"
+
+ when {
+ process {
+ """
+ adapter_fasta = []
+ discard_trimmed_pass = true
+ save_trimmed_fail = false
+ save_merged = false
+
+ input[0] = Channel.of([
+ [ id:'test', single_end:true ],
+ [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) ],
+ adapter_fasta
+ ])
+ input[1] = discard_trimmed_pass
+ input[2] = save_trimmed_fail
+ input[3] = save_merged
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert process.success },
+ { assert snapshot(process.out).match() }
+ )
+ }
+ }
+
+ test("test_fastp_paired_end_qc_only - stub") {
+
+ options "-stub"
+
+ when {
+ process {
+ """
+ adapter_fasta = []
+ discard_trimmed_pass = true
+ save_trimmed_fail = false
+ save_merged = false
+
+ input[0] = Channel.of([
+ [ id:'test', single_end:false ], // meta map
+ [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) ],
+ adapter_fasta
+ ])
+ input[1] = discard_trimmed_pass
+ input[2] = save_trimmed_fail
+ input[3] = save_merged
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert process.success },
+ { assert snapshot(process.out).match() }
+ )
+ }
+ }
+}
diff --git a/modules/nf-core/fastp/tests/main.nf.test.snap b/modules/nf-core/fastp/tests/main.nf.test.snap
new file mode 100644
index 0000000..77b9ec2
--- /dev/null
+++ b/modules/nf-core/fastp/tests/main.nf.test.snap
@@ -0,0 +1,1376 @@
+{
+ "test_fastp_single_end_qc_only - stub": {
+ "content": [
+ {
+ "0": [
+
+ ],
+ "1": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "2": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "3": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "4": [
+
+ ],
+ "5": [
+
+ ],
+ "6": [
+ [
+ "FASTP",
+ "fastp",
+ "1.3.6"
+ ]
+ ],
+ "html": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "json": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "log": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "reads": [
+
+ ],
+ "reads_fail": [
+
+ ],
+ "reads_merged": [
+
+ ],
+ "versions_fastp": [
+ [
+ "FASTP",
+ "fastp",
+ "1.3.6"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-07-31T10:06:56.864151",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.6"
+ }
+ },
+ "test_fastp_paired_end": {
+ "content": [
+ [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test_R1.fastp.fastq.gz:md5,67b2bbae47f073e05a97a9c2edce23c7",
+ "test_R2.fastp.fastq.gz:md5,25cbdca08e2083dbd4f0502de6b62f39"
+ ]
+ ]
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ {
+ "versions_fastp": [
+ [
+ "FASTP",
+ "fastp",
+ "1.3.6"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-07-31T10:06:04.967568",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.6"
+ }
+ },
+ "test_fastp_paired_end_merged_adapterlist": {
+ "content": [
+ [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test_R1.fastp.fastq.gz:md5,54b726a55e992a869fd3fa778afe1672",
+ "test_R2.fastp.fastq.gz:md5,29d3b33b869f7b63417b8ff07bb128ba"
+ ]
+ ]
+ ],
+ [
+
+ ],
+ [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.merged.fastq.gz:md5,c873bb1ab3fa859dcc47306465e749d5"
+ ]
+ ],
+ {
+ "versions_fastp": [
+ [
+ "FASTP",
+ "fastp",
+ "1.3.6"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-07-31T10:06:24.635687",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.6"
+ }
+ },
+ "test_fastp_single_end_qc_only": {
+ "content": [
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ {
+ "versions_fastp": [
+ [
+ "FASTP",
+ "fastp",
+ "1.3.6"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-07-31T10:06:28.028482",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.6"
+ }
+ },
+ "test_fastp_paired_end_trim_fail": {
+ "content": [
+ [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test_R1.fastp.fastq.gz:md5,6ff32a64c5188b9a9192be1398c262c7",
+ "test_R2.fastp.fastq.gz:md5,db0cb7c9977e94ac2b4b446ebd017a8a"
+ ]
+ ]
+ ],
+ [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test.paired.fail.fastq.gz:md5,409b687c734cedd7a1fec14d316e1366",
+ "test_R1.fail.fastq.gz:md5,4f273cf3159c13f79e8ffae12f5661f6",
+ "test_R2.fail.fastq.gz:md5,f97b9edefb5649aab661fbc9e71fc995"
+ ]
+ ]
+ ],
+ [
+
+ ],
+ {
+ "versions_fastp": [
+ [
+ "FASTP",
+ "fastp",
+ "1.3.6"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-07-31T10:06:17.244064",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.6"
+ }
+ },
+ "fastp - stub test_fastp_interleaved": {
+ "content": [
+ {
+ "0": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "1": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "2": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "3": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "4": [
+
+ ],
+ "5": [
+
+ ],
+ "6": [
+ [
+ "FASTP",
+ "fastp",
+ "1.3.6"
+ ]
+ ],
+ "html": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "json": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "log": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "reads": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "reads_fail": [
+
+ ],
+ "reads_merged": [
+
+ ],
+ "versions_fastp": [
+ [
+ "FASTP",
+ "fastp",
+ "1.3.6"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-07-31T10:06:40.981576",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.6"
+ }
+ },
+ "test_fastp_single_end - stub": {
+ "content": [
+ {
+ "0": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "1": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "2": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "3": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "4": [
+
+ ],
+ "5": [
+
+ ],
+ "6": [
+ [
+ "FASTP",
+ "fastp",
+ "1.3.6"
+ ]
+ ],
+ "html": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "json": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "log": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "reads": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "reads_fail": [
+
+ ],
+ "reads_merged": [
+
+ ],
+ "versions_fastp": [
+ [
+ "FASTP",
+ "fastp",
+ "1.3.6"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-07-31T10:06:34.535562",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.6"
+ }
+ },
+ "test_fastp_paired_end_merged_adapterlist - stub": {
+ "content": [
+ {
+ "0": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test_R1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940",
+ "test_R2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ]
+ ],
+ "1": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "2": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "3": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "4": [
+
+ ],
+ "5": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.merged.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "6": [
+ [
+ "FASTP",
+ "fastp",
+ "1.3.6"
+ ]
+ ],
+ "html": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "json": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "log": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "reads": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test_R1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940",
+ "test_R2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ]
+ ],
+ "reads_fail": [
+
+ ],
+ "reads_merged": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.merged.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "versions_fastp": [
+ [
+ "FASTP",
+ "fastp",
+ "1.3.6"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-07-31T10:06:53.745838",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.6"
+ }
+ },
+ "test_fastp_paired_end_merged - stub": {
+ "content": [
+ {
+ "0": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test_R1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940",
+ "test_R2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ]
+ ],
+ "1": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "2": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "3": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "4": [
+
+ ],
+ "5": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.merged.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "6": [
+ [
+ "FASTP",
+ "fastp",
+ "1.3.6"
+ ]
+ ],
+ "html": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "json": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "log": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "reads": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test_R1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940",
+ "test_R2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ]
+ ],
+ "reads_fail": [
+
+ ],
+ "reads_merged": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.merged.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "versions_fastp": [
+ [
+ "FASTP",
+ "fastp",
+ "1.3.6"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-07-31T10:06:50.440269",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.6"
+ }
+ },
+ "test_fastp_paired_end_merged": {
+ "content": [
+ [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test_R1.fastp.fastq.gz:md5,54b726a55e992a869fd3fa778afe1672",
+ "test_R2.fastp.fastq.gz:md5,29d3b33b869f7b63417b8ff07bb128ba"
+ ]
+ ]
+ ],
+ [
+
+ ],
+ [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.merged.fastq.gz:md5,c873bb1ab3fa859dcc47306465e749d5"
+ ]
+ ],
+ {
+ "versions_fastp": [
+ [
+ "FASTP",
+ "fastp",
+ "1.3.6"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-07-31T10:06:21.026574",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.6"
+ }
+ },
+ "test_fastp_paired_end - stub": {
+ "content": [
+ {
+ "0": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test_R1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940",
+ "test_R2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ]
+ ],
+ "1": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "2": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "3": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "4": [
+
+ ],
+ "5": [
+
+ ],
+ "6": [
+ [
+ "FASTP",
+ "fastp",
+ "1.3.6"
+ ]
+ ],
+ "html": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "json": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "log": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "reads": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test_R1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940",
+ "test_R2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ]
+ ],
+ "reads_fail": [
+
+ ],
+ "reads_merged": [
+
+ ],
+ "versions_fastp": [
+ [
+ "FASTP",
+ "fastp",
+ "1.3.6"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-07-31T10:06:37.802435",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.6"
+ }
+ },
+ "test_fastp_single_end": {
+ "content": [
+ [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.fastq.gz:md5,67b2bbae47f073e05a97a9c2edce23c7"
+ ]
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ {
+ "versions_fastp": [
+ [
+ "FASTP",
+ "fastp",
+ "1.3.6"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-07-31T10:06:01.184475",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.6"
+ }
+ },
+ "test_fastp_single_end_trim_fail - stub": {
+ "content": [
+ {
+ "0": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "1": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "2": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "3": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "4": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "5": [
+
+ ],
+ "6": [
+ [
+ "FASTP",
+ "fastp",
+ "1.3.6"
+ ]
+ ],
+ "html": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "json": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "log": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "reads": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "reads_fail": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "reads_merged": [
+
+ ],
+ "versions_fastp": [
+ [
+ "FASTP",
+ "fastp",
+ "1.3.6"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-07-31T10:06:44.158414",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.6"
+ }
+ },
+ "test_fastp_paired_end_trim_fail - stub": {
+ "content": [
+ {
+ "0": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test_R1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940",
+ "test_R2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ]
+ ],
+ "1": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "2": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "3": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "4": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test.paired.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940",
+ "test_R1.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940",
+ "test_R2.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ]
+ ],
+ "5": [
+
+ ],
+ "6": [
+ [
+ "FASTP",
+ "fastp",
+ "1.3.6"
+ ]
+ ],
+ "html": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "json": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "log": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "reads": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test_R1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940",
+ "test_R2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ]
+ ],
+ "reads_fail": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test.paired.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940",
+ "test_R1.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940",
+ "test_R2.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ]
+ ],
+ "reads_merged": [
+
+ ],
+ "versions_fastp": [
+ [
+ "FASTP",
+ "fastp",
+ "1.3.6"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-07-31T10:06:47.329995",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.6"
+ }
+ },
+ "fastp test_fastp_interleaved": {
+ "content": [
+ [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.fastq.gz:md5,217d62dc13a23e92513a1bd8e1bcea39"
+ ]
+ ],
+ {
+ "versions_fastp": [
+ [
+ "FASTP",
+ "fastp",
+ "1.3.6"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-07-31T10:06:09.066984",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.6"
+ }
+ },
+ "test_fastp_single_end_trim_fail": {
+ "content": [
+ [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.fastq.gz:md5,67b2bbae47f073e05a97a9c2edce23c7"
+ ]
+ ],
+ [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fail.fastq.gz:md5,3e4aaadb66a5b8fc9b881bf39c227abd"
+ ]
+ ],
+ [
+
+ ],
+ {
+ "versions_fastp": [
+ [
+ "FASTP",
+ "fastp",
+ "1.3.6"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-07-31T10:06:13.177593",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.6"
+ }
+ },
+ "test_fastp_paired_end_qc_only": {
+ "content": [
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ [
+
+ ],
+ {
+ "versions_fastp": [
+ [
+ "FASTP",
+ "fastp",
+ "1.3.6"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-07-31T10:06:31.289427",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.6"
+ }
+ },
+ "test_fastp_paired_end_qc_only - stub": {
+ "content": [
+ {
+ "0": [
+
+ ],
+ "1": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "2": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "3": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "4": [
+
+ ],
+ "5": [
+
+ ],
+ "6": [
+ [
+ "FASTP",
+ "fastp",
+ "1.3.6"
+ ]
+ ],
+ "html": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "json": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "log": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "reads": [
+
+ ],
+ "reads_fail": [
+
+ ],
+ "reads_merged": [
+
+ ],
+ "versions_fastp": [
+ [
+ "FASTP",
+ "fastp",
+ "1.3.6"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-07-31T10:07:00.16477",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.6"
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/nf-core/fastp/tests/nextflow.interleaved.config b/modules/nf-core/fastp/tests/nextflow.interleaved.config
new file mode 100644
index 0000000..4be8dbd
--- /dev/null
+++ b/modules/nf-core/fastp/tests/nextflow.interleaved.config
@@ -0,0 +1,5 @@
+process {
+ withName: FASTP {
+ ext.args = "--interleaved_in -e 30"
+ }
+}
diff --git a/modules/nf-core/fastp/tests/nextflow.save_failed.config b/modules/nf-core/fastp/tests/nextflow.save_failed.config
new file mode 100644
index 0000000..53b61b0
--- /dev/null
+++ b/modules/nf-core/fastp/tests/nextflow.save_failed.config
@@ -0,0 +1,5 @@
+process {
+ withName: FASTP {
+ ext.args = "-e 30"
+ }
+}
diff --git a/modules/nf-core/hostile/clean/environment.yml b/modules/nf-core/hostile/clean/environment.yml
new file mode 100644
index 0000000..55afd60
--- /dev/null
+++ b/modules/nf-core/hostile/clean/environment.yml
@@ -0,0 +1,8 @@
+---
+# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json
+channels:
+ - conda-forge
+ - bioconda
+dependencies:
+ # renovate: datasource=conda depName=bioconda/hostile
+ - bioconda::hostile=2.0.2
diff --git a/modules/nf-core/hostile/clean/main.nf b/modules/nf-core/hostile/clean/main.nf
new file mode 100644
index 0000000..aa087d2
--- /dev/null
+++ b/modules/nf-core/hostile/clean/main.nf
@@ -0,0 +1,54 @@
+process HOSTILE_CLEAN {
+ tag "${meta.id}"
+ label 'process_low'
+
+ 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/7c/7caca3a47606de8e3460b35823193a471272aa6ab7cfafbf9aabf4615c9fa181/data'
+ : 'community.wave.seqera.io/library/hostile:2.0.2--a7f5e5d341b6b94b'}"
+
+ input:
+ tuple val(meta) , path(reads, stageAs: "input_reads/")
+ tuple val(reference_name), path(reference_dir)
+
+ output:
+ tuple val(meta), path('*.fastq.gz'), emit: fastq
+ tuple val(meta), path('*.json') , emit: json
+ tuple val("${task.process}"), val('hostile'), eval("hostile --version"), emit: versions_hostile, topic: versions
+
+ when:
+ task.ext.when == null || task.ext.when
+
+ script:
+ def args = task.ext.args ?: ''
+ def prefix = task.ext.prefix ?: "${meta.id}"
+ def sorted_reads = meta.single_end ? [reads].flatten() : reads.sort { read -> read.simpleName }
+ def reads_cmd = meta.single_end ? "--fastq1 ${sorted_reads[0]}" : "--fastq1 ${sorted_reads[0]} --fastq2 ${sorted_reads[1]}"
+ """
+ export HOSTILE_CACHE_DIR=${reference_dir}
+
+ ## Reorder the reads for reproducibility
+ ## Set offline as we never want this process to auto-download reference files as required input channel
+ hostile \\
+ clean \\
+ ${args} \\
+ --threads ${task.cpus} \\
+ ${reads_cmd} \\
+ --index ${reference_name} \\
+ --output . \\
+ --reorder \\
+ --airplane \\
+ | tee > ${prefix}.json
+ """
+
+ stub:
+ def prefix = task.ext.prefix ?: "${meta.id}"
+ def fake_read2 = !meta.single_end ? "echo '' | gzip > ${prefix}.clean_2.fastq.gz" : ""
+ """
+ export HOSTILE_CACHE_DIR=${reference_dir}
+ echo "" | gzip > ${prefix}.clean_1.fastq.gz
+ ${fake_read2}
+
+ touch ${prefix}.json
+ """
+}
diff --git a/modules/nf-core/hostile/clean/meta.yml b/modules/nf-core/hostile/clean/meta.yml
new file mode 100644
index 0000000..89220c2
--- /dev/null
+++ b/modules/nf-core/hostile/clean/meta.yml
@@ -0,0 +1,94 @@
+name: "hostile_clean"
+description: Removes host reads from short- and long-read FASTQ sequencing files
+keywords:
+ - hostile
+ - decontamination
+ - human removal
+ - host removal
+ - clean
+tools:
+ - "hostile":
+ description: "Hostile: accurate host decontamination"
+ homepage: "https://github.com/bede/hostile"
+ documentation: "https://github.com/bede/hostile"
+ tool_dev_url: "https://github.com/bede/hostile"
+ doi: "10.1093/bioinformatics/btad728"
+ licence:
+ - "MIT"
+ identifier: biotools:hostile
+input:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. `[ id:'sample1', single_end:false ]`
+ - reads:
+ type: file
+ description: Paired or single end FASTQ files
+ pattern: "*.{fastq,fq,fastq.gz,fq.gz}"
+ ontologies:
+ - edam: "http://edamontology.org/format_1930"
+ - - reference_name:
+ type: string
+ description: |
+ Name of the reference to align against and thus remove mapped reads to.
+ - reference_dir:
+ type: directory
+ description: |
+ Directory containing index file(s) corresponding to the preferred aligner (bowtie2 short reads or minimap for long reads).
+ Note that single end data is assumed to be long reads. If you have single-end short read you must supply both the BowTie2
+ indices AND explicitly specify `--aligner bowtie2`
+ ontologies:
+ - edam: "http://edamontology.org/data_1049"
+output:
+ fastq:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. `[ id:'sample1', single_end:false ]`
+ - "*.fastq.gz":
+ type: file
+ description: |
+ Cleaned FASTQ files with host reads removed
+ pattern: "*.{fastq,fq,fastq.gz,fq.gz}"
+ ontologies:
+ - edam: "http://edamontology.org/format_1930"
+ json:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. `[ id:'sample1', single_end:false ]`
+ - "*.json":
+ type: file
+ description: |
+ JSON report containing statistics from hostile cleaning
+ pattern: "*.json"
+ ontologies:
+ - edam: "http://edamontology.org/format_3464"
+ versions_hostile:
+ - - ${task.process}:
+ type: string
+ description: The name of the process
+ - hostile:
+ type: string
+ description: The name of the tool
+ - hostile --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
+ - hostile:
+ type: string
+ description: The name of the tool
+ - hostile --version:
+ type: eval
+ description: The expression to obtain the version of the tool
+authors:
+ - "@jfy133"
+maintainers:
+ - "@jfy133"
diff --git a/modules/nf-core/hostile/clean/tests/main.nf.test b/modules/nf-core/hostile/clean/tests/main.nf.test
new file mode 100644
index 0000000..116f28f
--- /dev/null
+++ b/modules/nf-core/hostile/clean/tests/main.nf.test
@@ -0,0 +1,114 @@
+nextflow_process {
+
+ name "Test Process HOSTILE_CLEAN"
+ script "../main.nf"
+ process "HOSTILE_CLEAN"
+ config './nextflow.config'
+
+ tag "modules"
+ tag "modules_nfcore"
+ tag "hostile"
+ tag "hostile/clean"
+ tag "bowtie2/build"
+
+ setup {
+ run("BOWTIE2_BUILD") {
+ script "../../../bowtie2/build/main.nf"
+ process {
+ """
+ input[0] = [
+ [ id:'test' ],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)
+ ]
+ """
+ }
+ }
+ }
+
+ test("fastq - single-end") {
+ when {
+ params {
+ module_args = "--aligner bowtie2"
+ }
+ process {
+ """
+ input[0] = [
+ [ id:'test', single_end:true ], // meta map
+ [
+ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ ],
+ ]
+ input[1] = BOWTIE2_BUILD.out.index.map { ['genome', it[1]] }
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert process.success },
+ { assert path(process.out.json[0][1]).readLines().any{ it.contains('\"reads_removed\": 0')} },
+ { assert snapshot(sanitizeOutput(process.out, unstableKeys:["json"])).match() }
+ )
+ }
+
+ }
+
+
+ test("fastq - paired-end") {
+ when {
+ params {
+ module_args = ""
+ }
+ process {
+ """
+ input[0] = [
+ [ id:'test', single_end:false ], // meta map
+ [
+ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/fastq/test_2.fastq.gz', checkIfExists: true),
+ ],
+ ]
+ input[1] = BOWTIE2_BUILD.out.index.map { ['genome', it[1]] }
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert process.success },
+ { assert path(process.out.json[0][1]).readLines().any{ it.contains('\"reads_removed\": 0')} },
+ { assert snapshot(sanitizeOutput(process.out, unstableKeys:["json"])).match() }
+ )
+ }
+
+ }
+
+ test("fastq - single-end - stub") {
+
+ options "-stub"
+
+ when {
+ params {
+ module_args = ""
+ }
+ process {
+ """
+ input[0] = [
+ [ id:'test', single_end:true ], // meta map
+ [file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/fastq/test_1.fastq.gz', checkIfExists: true)],
+ ]
+ input[1] = BOWTIE2_BUILD.out.index.map { ['genome', it[1]] }
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert process.success },
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+
+ }
+
+}
diff --git a/modules/nf-core/hostile/clean/tests/main.nf.test.snap b/modules/nf-core/hostile/clean/tests/main.nf.test.snap
new file mode 100644
index 0000000..42d1589
--- /dev/null
+++ b/modules/nf-core/hostile/clean/tests/main.nf.test.snap
@@ -0,0 +1,113 @@
+{
+ "fastq - single-end - stub": {
+ "content": [
+ {
+ "fastq": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.clean_1.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "json": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.json:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "versions_hostile": [
+ [
+ "HOSTILE_CLEAN",
+ "hostile",
+ "2.0.2"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-05-22T10:10:32.316335997",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.0"
+ }
+ },
+ "fastq - paired-end": {
+ "content": [
+ {
+ "fastq": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test_1.clean_1.fastq.gz:md5,6de988b85909e6529bad0022703bcab2",
+ "test_2.clean_2.fastq.gz:md5,539acd65e93bf16a5ace7d6034e704c7"
+ ]
+ ]
+ ],
+ "json": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.json"
+ ]
+ ],
+ "versions_hostile": [
+ [
+ "HOSTILE_CLEAN",
+ "hostile",
+ "2.0.2"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-05-22T10:13:45.740888243",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.0"
+ }
+ },
+ "fastq - single-end": {
+ "content": [
+ {
+ "fastq": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test_1.clean.fastq.gz:md5,9224ada21765cb3ad5fc05f40df78f17"
+ ]
+ ],
+ "json": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.json"
+ ]
+ ],
+ "versions_hostile": [
+ [
+ "HOSTILE_CLEAN",
+ "hostile",
+ "2.0.2"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-05-22T10:13:22.374303854",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.0"
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/nf-core/hostile/clean/tests/nextflow.config b/modules/nf-core/hostile/clean/tests/nextflow.config
new file mode 100644
index 0000000..864528a
--- /dev/null
+++ b/modules/nf-core/hostile/clean/tests/nextflow.config
@@ -0,0 +1,5 @@
+process {
+ withName: 'HOSTILE_CLEAN' {
+ ext.args = params.module_args
+ }
+}
diff --git a/modules/nf-core/hostile/fetch/environment.yml b/modules/nf-core/hostile/fetch/environment.yml
new file mode 100644
index 0000000..f1f048b
--- /dev/null
+++ b/modules/nf-core/hostile/fetch/environment.yml
@@ -0,0 +1,9 @@
+---
+# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json
+channels:
+ - conda-forge
+ - bioconda
+
+dependencies:
+ # renovate: datasource=conda depName=bioconda/hostile
+ - bioconda::hostile=2.0.2
diff --git a/modules/nf-core/hostile/fetch/main.nf b/modules/nf-core/hostile/fetch/main.nf
new file mode 100644
index 0000000..b0aa695
--- /dev/null
+++ b/modules/nf-core/hostile/fetch/main.nf
@@ -0,0 +1,44 @@
+process HOSTILE_FETCH {
+ label 'process_single'
+
+ 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/7c/7caca3a47606de8e3460b35823193a471272aa6ab7cfafbf9aabf4615c9fa181/data'
+ : 'community.wave.seqera.io/library/hostile:2.0.2--a7f5e5d341b6b94b'}"
+
+ input:
+ val index_name
+
+ output:
+ tuple val(index_name), path('reference/'), emit: reference
+ tuple val("${task.process}"), val('hostile'), eval("hostile --version"), emit: versions_hostile, topic: versions
+
+ when:
+ task.ext.when == null || task.ext.when
+
+ script:
+ def args = task.ext.args ?: ''
+ """
+ mkdir reference/
+ export HOSTILE_CACHE_DIR=./reference
+
+ hostile \\
+ index \\
+ fetch \\
+ --name ${index_name} \\
+ ${args}
+ """
+
+ stub:
+ """
+ mkdir reference/
+ export HOSTILE_CACHE_DIR=./reference
+ touch reference/human-t2t-hla.1.bt2
+ touch reference/human-t2t-hla.2.bt2
+ touch reference/human-t2t-hla.3.bt2
+ touch reference/human-t2t-hla.4.bt2
+ touch reference/human-t2t-hla.rev.1.bt2
+ touch reference/human-t2t-hla.rev.2.bt2
+ touch reference/human-t2t-hla.mmi
+ """
+}
diff --git a/modules/nf-core/hostile/fetch/meta.yml b/modules/nf-core/hostile/fetch/meta.yml
new file mode 100644
index 0000000..339974d
--- /dev/null
+++ b/modules/nf-core/hostile/fetch/meta.yml
@@ -0,0 +1,58 @@
+name: "hostile_fetch"
+description: Downloads required reference genomes for Hostile
+keywords:
+ - hostile
+ - decontamination
+ - human removal
+ - download
+tools:
+ - "hostile":
+ description: "Hostile: accurate host decontamination"
+ homepage: "https://github.com/bede/hostile"
+ documentation: "https://github.com/bede/hostile"
+ tool_dev_url: "https://github.com/bede/hostile"
+ doi: "10.1093/bioinformatics/btad728"
+ licence:
+ - "MIT"
+ identifier: biotools:hostile
+input:
+ - index_name:
+ type: string
+ description: Name of the reference genome index to download
+output:
+ reference:
+ - - index_name:
+ type: directory
+ description: Name of the reference genome index downloaded
+ - reference/:
+ type: directory
+ description: Directory containing required reference genome files for
+ hostile clean
+ pattern: "reference/"
+ ontologies:
+ - edam: "http://edamontology.org/data_1049"
+ versions_hostile:
+ - - ${task.process}:
+ type: string
+ description: The name of the process
+ - hostile:
+ type: string
+ description: The name of the tool
+ - hostile --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
+ - hostile:
+ type: string
+ description: The name of the tool
+ - hostile --version:
+ type: eval
+ description: The expression to obtain the version of the tool
+authors:
+ - "@jfy133"
+maintainers:
+ - "@jfy133"
diff --git a/modules/nf-core/hostile/fetch/tests/main.nf.test b/modules/nf-core/hostile/fetch/tests/main.nf.test
new file mode 100644
index 0000000..c2ea911
--- /dev/null
+++ b/modules/nf-core/hostile/fetch/tests/main.nf.test
@@ -0,0 +1,59 @@
+nextflow_process {
+
+ name "Test Process HOSTILE_FETCH"
+ script "../main.nf"
+ process "HOSTILE_FETCH"
+ config './nextflow.config'
+
+ tag "modules"
+ tag "modules_nfcore"
+ tag "hostile"
+ tag "hostile/fetch"
+
+ test("human-t2t-hla - bowtie2") {
+
+ when {
+ params {
+ module_args = "--bowtie2"
+ }
+ process {
+ """
+ input[0] = 'human-t2t-hla'
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert process.success },
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+
+ }
+
+ test("human-t2t-hla - stub") {
+
+ options "-stub"
+
+ when {
+ params {
+ module_args = ""
+ }
+ process {
+ """
+ input[0] = 'human-t2t-hla'
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert process.success },
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+
+ }
+
+}
diff --git a/modules/nf-core/hostile/fetch/tests/main.nf.test.snap b/modules/nf-core/hostile/fetch/tests/main.nf.test.snap
new file mode 100644
index 0000000..66284f0
--- /dev/null
+++ b/modules/nf-core/hostile/fetch/tests/main.nf.test.snap
@@ -0,0 +1,65 @@
+{
+ "human-t2t-hla - bowtie2": {
+ "content": [
+ {
+ "reference": [
+ [
+ "human-t2t-hla",
+ [
+ "human-t2t-hla.1.bt2:md5,b481b623a14908f29c5dce42077f8763",
+ "human-t2t-hla.2.bt2:md5,8df357276c7c3a4e768ecd70ca951b53",
+ "human-t2t-hla.3.bt2:md5,78289afb436f8d0a9526ecfa95e3fa24",
+ "human-t2t-hla.4.bt2:md5,198badf146cd0b3886b674424250d419",
+ "human-t2t-hla.rev.1.bt2:md5,34c65db94aba9cc49cb31e14d2698c1c",
+ "human-t2t-hla.rev.2.bt2:md5,53c9901609915475a2e1b009046d8371"
+ ]
+ ]
+ ],
+ "versions_hostile": [
+ [
+ "HOSTILE_FETCH",
+ "hostile",
+ "2.0.2"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-05-22T10:17:43.280616381",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.0"
+ }
+ },
+ "human-t2t-hla - stub": {
+ "content": [
+ {
+ "reference": [
+ [
+ "human-t2t-hla",
+ [
+ "human-t2t-hla.1.bt2:md5,d41d8cd98f00b204e9800998ecf8427e",
+ "human-t2t-hla.2.bt2:md5,d41d8cd98f00b204e9800998ecf8427e",
+ "human-t2t-hla.3.bt2:md5,d41d8cd98f00b204e9800998ecf8427e",
+ "human-t2t-hla.4.bt2:md5,d41d8cd98f00b204e9800998ecf8427e",
+ "human-t2t-hla.mmi:md5,d41d8cd98f00b204e9800998ecf8427e",
+ "human-t2t-hla.rev.1.bt2:md5,d41d8cd98f00b204e9800998ecf8427e",
+ "human-t2t-hla.rev.2.bt2:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ]
+ ],
+ "versions_hostile": [
+ [
+ "HOSTILE_FETCH",
+ "hostile",
+ "2.0.2"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-05-22T10:45:40.756531067",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.0"
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/nf-core/hostile/fetch/tests/nextflow.config b/modules/nf-core/hostile/fetch/tests/nextflow.config
new file mode 100644
index 0000000..8bbf469
--- /dev/null
+++ b/modules/nf-core/hostile/fetch/tests/nextflow.config
@@ -0,0 +1,5 @@
+process {
+ withName: 'HOSTILE_FETCH' {
+ ext.args = params.module_args
+ }
+}
diff --git a/modules/nf-core/leehom/environment.yml b/modules/nf-core/leehom/environment.yml
new file mode 100644
index 0000000..cc93ca3
--- /dev/null
+++ b/modules/nf-core/leehom/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::leehom=1.2.15
diff --git a/modules/nf-core/leehom/main.nf b/modules/nf-core/leehom/main.nf
new file mode 100644
index 0000000..610d206
--- /dev/null
+++ b/modules/nf-core/leehom/main.nf
@@ -0,0 +1,83 @@
+process LEEHOM {
+ tag "$meta.id"
+ label 'process_low'
+
+ // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions.
+ conda "${moduleDir}/environment.yml"
+ container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ?
+ 'https://depot.galaxyproject.org/singularity/leehom:1.2.15--h29e30f7_1' :
+ 'quay.io/biocontainers/leehom:1.2.15--h29e30f7_1' }"
+
+ input:
+ tuple val(meta), path(reads)
+
+ output:
+ tuple val(meta), path("${prefix}.bam") , emit: bam , optional: true
+ tuple val(meta), path("${prefix}.fq.gz") , emit: fq_pass , optional: true
+ tuple val(meta), path("${prefix}.fail.fq.gz") , emit: fq_fail , optional: true
+ tuple val(meta), path("${prefix}_r1.fq.gz") , emit: unmerged_r1_fq_pass, optional: true
+ tuple val(meta), path("${prefix}_r1.fail.fq.gz"), emit: unmerged_r1_fq_fail, optional: true
+ tuple val(meta), path("${prefix}_r2.fq.gz") , emit: unmerged_r2_fq_pass, optional: true
+ tuple val(meta), path("${prefix}_r2.fail.fq.gz"), emit: unmerged_r2_fq_fail, optional: true
+ tuple val(meta), path("*.log") , emit: log
+ // WARN: Version information not provided by tool on CLI. Please update this string when bumping container versions.
+ tuple val("${task.process}"), val('last'), val("1.2.15"), emit: versions_last, topic: versions
+
+ when:
+ task.ext.when == null || task.ext.when
+
+ script:
+ def args = task.ext.args ?: ''
+ prefix = task.ext.prefix ?: "${meta.id}"
+ if (reads.toString().endsWith('.bam')) {
+ """
+ leeHom \\
+ ${args} \\
+ -t ${task.cpus} \\
+ -o ${prefix}.bam \\
+ --log ${prefix}.log \\
+ ${reads}
+ """
+ } else if (meta.single_end) {
+ """
+ leeHom \\
+ ${args} \\
+ -t ${task.cpus} \\
+ -fq1 ${reads} \\
+ -fqo ${prefix} \\
+ --log ${prefix}.log
+ """
+ } else {
+ """
+ leeHom \\
+ ${args} \\
+ -t ${task.cpus} \\
+ -fq1 ${reads[0]} \\
+ -fq2 ${reads[1]} \\
+ -fqo ${prefix} \\
+ --log ${prefix}.log
+ """
+ }
+
+ stub:
+ prefix = task.ext.prefix ?: "${meta.id}"
+ is_bam = reads.toString().endsWith('.bam')
+ is_single_end = meta.single_end
+
+ """
+ if [[ "${is_bam}" == "true" ]]; then
+ touch ${prefix}.bam
+ else
+ echo "" | gzip > ${prefix}.fq.gz
+ echo "" | gzip > ${prefix}.fail.fq.gz
+ if [[ "${is_single_end}" == "false" ]]; then
+ echo "" | gzip > ${prefix}_r1.fq.gz
+ echo "" | gzip > ${prefix}_r1.fail.fq.gz
+ echo "" | gzip > ${prefix}_r2.fq.gz
+ echo "" | gzip > ${prefix}_r2.fail.fq.gz
+ fi
+ fi
+ touch ${prefix}.log
+ """
+
+}
diff --git a/modules/nf-core/leehom/meta.yml b/modules/nf-core/leehom/meta.yml
new file mode 100644
index 0000000..378b125
--- /dev/null
+++ b/modules/nf-core/leehom/meta.yml
@@ -0,0 +1,152 @@
+name: leehom
+description: Bayesian reconstruction of ancient DNA fragments
+keywords:
+ - ancient DNA
+ - adapter removal
+ - clipping
+ - trimming
+ - merging
+ - collapsing
+ - preprocessing
+ - bayesian
+tools:
+ - leehom:
+ description: Bayesian reconstruction of ancient DNA fragments
+ homepage: "https://grenaud.github.io/leeHom/"
+ documentation: "https://github.com/grenaud/leeHom"
+ tool_dev_url: "https://github.com/grenaud/leeHom"
+ doi: "10.1093/nar/gku699"
+ licence:
+ - "GPL v3"
+ identifier: biotools:leehom
+input:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - reads:
+ type: file
+ description: Unaligned BAM or one or two gzipped FASTQ file(s)
+ pattern: "*.{bam,fq.gz,fastq.gz}"
+ ontologies: []
+output:
+ bam:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - ${prefix}.bam:
+ type: file
+ description: BAM file
+ pattern: "*.bam"
+ ontologies: []
+ fq_pass:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - ${prefix}.fq.gz:
+ type: file
+ description: Trimmed and merged FASTQ
+ pattern: "*.fq.gz"
+ ontologies:
+ - edam: http://edamontology.org/format_3989 # GZIP format
+ fq_fail:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - ${prefix}.fail.fq.gz:
+ type: file
+ description: Failed trimmed and merged FASTQs
+ pattern: "*.fail.fq.gz"
+ ontologies:
+ - edam: http://edamontology.org/format_3989 # GZIP format
+ unmerged_r1_fq_pass:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - ${prefix}_r1.fq.gz:
+ type: file
+ description: Passed unmerged R1 FASTQs
+ pattern: "*.r1.fq.gz"
+ ontologies:
+ - edam: http://edamontology.org/format_3989 # GZIP format
+ unmerged_r1_fq_fail:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - ${prefix}_r1.fail.fq.gz:
+ type: file
+ description: Failed unmerged R1 FASTQs
+ pattern: "*.r1.fail.fq.gz"
+ ontologies:
+ - edam: http://edamontology.org/format_3989 # GZIP format
+ unmerged_r2_fq_pass:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - ${prefix}_r2.fq.gz:
+ type: file
+ description: Passed unmerged R2 FASTQs
+ pattern: "*.r2.fq.gz"
+ ontologies:
+ - edam: http://edamontology.org/format_3989 # GZIP format
+ unmerged_r2_fq_fail:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - ${prefix}_r2.fail.fq.gz:
+ type: file
+ description: Failed unmerged R2 FASTQs
+ pattern: "*.r2.fail.fq.gz"
+ ontologies:
+ - edam: http://edamontology.org/format_3989 # GZIP format
+ log:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - "*.log":
+ type: file
+ description: Log file of command
+ pattern: "*.log"
+ ontologies: []
+ versions_last:
+ - - ${task.process}:
+ type: string
+ description: The name of the process
+ - last:
+ type: string
+ description: The name of the tool
+ - 1.2.15:
+ type: string
+ description: The expression to obtain the version of the tool
+topics:
+ versions:
+ - - ${task.process}:
+ type: string
+ description: The name of the process
+ - last:
+ type: string
+ description: The name of the tool
+ - 1.2.15:
+ type: string
+ description: The expression to obtain the version of the tool
+authors:
+ - "@jfy133"
+maintainers:
+ - "@jfy133"
diff --git a/modules/nf-core/leehom/tests/main.nf.test b/modules/nf-core/leehom/tests/main.nf.test
new file mode 100644
index 0000000..50cd8c5
--- /dev/null
+++ b/modules/nf-core/leehom/tests/main.nf.test
@@ -0,0 +1,166 @@
+nextflow_process {
+
+ name "Test Process LEEHOM"
+ config "./nextflow.config"
+ script "../main.nf"
+ process "LEEHOM"
+
+ tag "modules"
+ tag "modules_nfcore"
+ tag "leehom"
+ tag "samtools"
+ tag "samtools/view"
+
+ test("homo_sapiens - bam") {
+ setup {
+ run("SAMTOOLS_VIEW") {
+ script "../../samtools/view/main.nf"
+ process {
+ """
+ input[0] = [
+ [id:'test', single_end:false],
+ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.sorted.bam', checkIfExists:true),
+ []
+ ]
+ input[1] = [[],[],[]]
+ input[2] = [[], []]
+ input[3] = [[], []]
+ input[4] = []
+ """
+ }
+ }
+ }
+ when {
+ params {
+ samtools_arg = '-f4 -b'
+ }
+ process {
+ """
+ input[0] = SAMTOOLS_VIEW.out.bam
+ """
+ }
+ }
+ then {
+ assertAll(
+ { assert process.success },
+ { assert snapshot(
+ process.out.bam.collect{meta, bam_ -> file(bam_).name + ':readsMD5,' + bam(bam_).getReadsMD5()},
+ sanitizeOutput(process.out, unstableKeys:["bam"])
+ ).match() }
+ )
+ }
+ }
+ test("sarscov2 - fastq single end") {
+ when {
+ params {
+ samtools_arg = ''
+ }
+ process {
+ """
+ input[0] = [
+ [id:'test', single_end:true],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists:true)]
+ """
+ }
+ }
+ then {
+ assertAll(
+ { assert process.success },
+ { assert snapshot(sanitizeOutput(process.out, unstableKeys:["fq_fail", "unmerged_r1_fq_fail"])).match() } // Fail files are empty
+ )
+ }
+ }
+ test("sarscov2 - fastq paired end") {
+ when {
+ params {
+ samtools_arg = ''
+ }
+ process {
+ """
+ input[0] = [
+ [id:'test', single_end:false],
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists:true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists:true)
+ ]
+ ]
+ """
+ }
+ }
+ then {
+ assertAll(
+ { assert process.success },
+ { assert snapshot(sanitizeOutput(process.out, unstableKeys:["fq_fail", "unmerged_r1_fq_fail", "unmerged_r2_fq_fail"])).match() } // Fail files are empty
+ )
+ }
+ }
+
+ test("homo_sapiens - bam - stub") {
+ options "-stub"
+ when {
+ params {
+ samtools_arg = ''
+ }
+ process {
+ """
+ input[0] = [
+ [id:'test', single_end:false],
+ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.sorted.bam', checkIfExists:true),
+ ]
+ """
+ }
+ }
+ then {
+ assertAll(
+ { assert process.success },
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+ }
+ test("sarscov2 - fastq single end - stub") {
+ options "-stub"
+ when {
+ params {
+ samtools_arg = ''
+ }
+ process {
+ """
+ input[0] = [
+ [id:'test', single_end:true],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists:true)]
+ """
+ }
+ }
+ then {
+ assertAll(
+ { assert process.success },
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+ }
+ test("sarscov2 - fastq paired end - stub") {
+ options "-stub"
+ when {
+ params {
+ samtools_arg = ''
+ }
+ process {
+ """
+ input[0] = [
+ [id:'test', single_end:false],
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists:true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists:true)
+ ]
+ ]
+ """
+ }
+ }
+ then {
+ assertAll(
+ { assert process.success },
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+ }
+}
diff --git a/modules/nf-core/leehom/tests/main.nf.test.snap b/modules/nf-core/leehom/tests/main.nf.test.snap
new file mode 100644
index 0000000..f90abd5
--- /dev/null
+++ b/modules/nf-core/leehom/tests/main.nf.test.snap
@@ -0,0 +1,401 @@
+{
+ "sarscov2 - fastq paired end": {
+ "content": [
+ {
+ "bam": [
+
+ ],
+ "fq_fail": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fail.fq.gz"
+ ]
+ ],
+ "fq_pass": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fq.gz:md5,5bd6d0b0d35f1b690d90f9bc803703fd"
+ ]
+ ],
+ "log": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.log:md5,800b5a88dc0822886bfbb271029e2a4a"
+ ]
+ ],
+ "unmerged_r1_fq_fail": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test_r1.fail.fq.gz"
+ ]
+ ],
+ "unmerged_r1_fq_pass": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test_r1.fq.gz:md5,aca8e68e644127a910bf209dc933c6cb"
+ ]
+ ],
+ "unmerged_r2_fq_fail": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test_r2.fail.fq.gz"
+ ]
+ ],
+ "unmerged_r2_fq_pass": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test_r2.fq.gz:md5,8429b111dbb880ada2a4e48e333423e2"
+ ]
+ ],
+ "versions_last": [
+ [
+ "LEEHOM",
+ "last",
+ "1.2.15"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-08T17:41:19.220071452",
+ "meta": {
+ "nf-test": "0.9.4",
+ "nextflow": "26.03.0"
+ }
+ },
+ "sarscov2 - fastq paired end - stub": {
+ "content": [
+ {
+ "bam": [
+
+ ],
+ "fq_fail": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fail.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "fq_pass": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "log": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "unmerged_r1_fq_fail": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test_r1.fail.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "unmerged_r1_fq_pass": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test_r1.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "unmerged_r2_fq_fail": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test_r2.fail.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "unmerged_r2_fq_pass": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test_r2.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "versions_last": [
+ [
+ "LEEHOM",
+ "last",
+ "1.2.15"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-08T17:36:35.255477128",
+ "meta": {
+ "nf-test": "0.9.4",
+ "nextflow": "26.03.0"
+ }
+ },
+ "sarscov2 - fastq single end": {
+ "content": [
+ {
+ "bam": [
+
+ ],
+ "fq_fail": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fail.fq.gz"
+ ]
+ ],
+ "fq_pass": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fq.gz:md5,304af6f5f6bb58c70abf7924eacfa175"
+ ]
+ ],
+ "log": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.log:md5,59aa280cb72dfbea05ba913cb89db143"
+ ]
+ ],
+ "unmerged_r1_fq_fail": [
+
+ ],
+ "unmerged_r1_fq_pass": [
+
+ ],
+ "unmerged_r2_fq_fail": [
+
+ ],
+ "unmerged_r2_fq_pass": [
+
+ ],
+ "versions_last": [
+ [
+ "LEEHOM",
+ "last",
+ "1.2.15"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-08T17:39:00.72056377",
+ "meta": {
+ "nf-test": "0.9.4",
+ "nextflow": "26.03.0"
+ }
+ },
+ "homo_sapiens - bam": {
+ "content": [
+ [
+ "test.bam:readsMD5,ef014d5b0cd0903595da255cdce79662"
+ ],
+ {
+ "bam": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.bam"
+ ]
+ ],
+ "fq_fail": [
+
+ ],
+ "fq_pass": [
+
+ ],
+ "log": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.log:md5,d1f5da273eb69f41babda510797c7671"
+ ]
+ ],
+ "unmerged_r1_fq_fail": [
+
+ ],
+ "unmerged_r1_fq_pass": [
+
+ ],
+ "unmerged_r2_fq_fail": [
+
+ ],
+ "unmerged_r2_fq_pass": [
+
+ ],
+ "versions_last": [
+ [
+ "LEEHOM",
+ "last",
+ "1.2.15"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-07-10T14:34:56.225495163",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.4"
+ }
+ },
+ "sarscov2 - fastq single end - stub": {
+ "content": [
+ {
+ "bam": [
+
+ ],
+ "fq_fail": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fail.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "fq_pass": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "log": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "unmerged_r1_fq_fail": [
+
+ ],
+ "unmerged_r1_fq_pass": [
+
+ ],
+ "unmerged_r2_fq_fail": [
+
+ ],
+ "unmerged_r2_fq_pass": [
+
+ ],
+ "versions_last": [
+ [
+ "LEEHOM",
+ "last",
+ "1.2.15"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-08T17:36:30.144467799",
+ "meta": {
+ "nf-test": "0.9.4",
+ "nextflow": "26.03.0"
+ }
+ },
+ "homo_sapiens - bam - stub": {
+ "content": [
+ {
+ "bam": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "fq_fail": [
+
+ ],
+ "fq_pass": [
+
+ ],
+ "log": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "unmerged_r1_fq_fail": [
+
+ ],
+ "unmerged_r1_fq_pass": [
+
+ ],
+ "unmerged_r2_fq_fail": [
+
+ ],
+ "unmerged_r2_fq_pass": [
+
+ ],
+ "versions_last": [
+ [
+ "LEEHOM",
+ "last",
+ "1.2.15"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-08T17:36:25.069263319",
+ "meta": {
+ "nf-test": "0.9.4",
+ "nextflow": "26.03.0"
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/nf-core/leehom/tests/nextflow.config b/modules/nf-core/leehom/tests/nextflow.config
new file mode 100644
index 0000000..b38abbd
--- /dev/null
+++ b/modules/nf-core/leehom/tests/nextflow.config
@@ -0,0 +1,5 @@
+process {
+ withName: "SAMTOOLS_VIEW" {
+ ext.args = { params.samtools_arg }
+ }
+}
diff --git a/modules/nf-core/prinseqplusplus/environment.yml b/modules/nf-core/prinseqplusplus/environment.yml
new file mode 100644
index 0000000..4656e94
--- /dev/null
+++ b/modules/nf-core/prinseqplusplus/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::prinseq-plus-plus=1.2.3
diff --git a/modules/nf-core/prinseqplusplus/main.nf b/modules/nf-core/prinseqplusplus/main.nf
new file mode 100644
index 0000000..67b3e7c
--- /dev/null
+++ b/modules/nf-core/prinseqplusplus/main.nf
@@ -0,0 +1,58 @@
+process PRINSEQPLUSPLUS {
+ tag "$meta.id"
+ label 'process_low'
+
+ conda "${moduleDir}/environment.yml"
+ container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ?
+ 'https://depot.galaxyproject.org/singularity/prinseq-plus-plus:1.2.3--hc90279e_1':
+ 'quay.io/biocontainers/prinseq-plus-plus:1.2.3--hc90279e_1' }"
+
+ input:
+ tuple val(meta), path(reads)
+
+ output:
+ tuple val(meta), path("*_good_out*.fastq.gz") , emit: good_reads
+ tuple val(meta), path("*_single_out*.fastq.gz"), optional: true, emit: single_reads
+ tuple val(meta), path("*_bad_out*.fastq.gz") , optional: true, emit: bad_reads
+ tuple val(meta), path("*.log") , emit: log
+ tuple val("${task.process}"), val('prinseqplusplus'), eval("prinseq++ --version | cut -f 2 -d ' '"), topic: versions, emit: versions_prinseqplusplus
+
+ when:
+ task.ext.when == null || task.ext.when
+
+ script:
+ def args = task.ext.args ?: ''
+ def prefix = task.ext.prefix ?: "${meta.id}"
+
+ if (meta.single_end) {
+ """
+ prinseq++ \\
+ -threads $task.cpus \\
+ -fastq ${reads} \\
+ -out_name ${prefix} \\
+ -out_gz \\
+ -VERBOSE 1 \\
+ $args \\
+ | tee ${prefix}.log
+ """
+ } else {
+ """
+ prinseq++ \\
+ -threads $task.cpus \\
+ -fastq ${reads[0]} \\
+ -fastq2 ${reads[1]} \\
+ -out_name ${prefix} \\
+ -out_gz \\
+ -VERBOSE 1 \\
+ $args \\
+ | tee ${prefix}.log
+ """
+ }
+
+ stub:
+ def prefix = task.ext.prefix ?: "${meta.id}"
+ """
+ echo "" | gzip > ${prefix}_good_out.fastq.gz
+ touch ${prefix}.log
+ """
+}
diff --git a/modules/nf-core/prinseqplusplus/meta.yml b/modules/nf-core/prinseqplusplus/meta.yml
new file mode 100644
index 0000000..bc156e0
--- /dev/null
+++ b/modules/nf-core/prinseqplusplus/meta.yml
@@ -0,0 +1,104 @@
+name: "prinseqplusplus"
+description: PRINSEQ++ is a C++ implementation of the prinseq-lite.pl program.
+ It can be used to filter, reformat or trim genomic and metagenomic sequence
+ data
+keywords:
+ - fastq
+ - fasta
+ - filter
+ - trim
+tools:
+ - "prinseqplusplus":
+ description: "PRINSEQ++ - Multi-threaded C++ sequence cleaning"
+ homepage: "https://github.com/Adrian-Cantu/PRINSEQ-plus-plus"
+ documentation: "https://github.com/Adrian-Cantu/PRINSEQ-plus-plus"
+ tool_dev_url: "https://github.com/Adrian-Cantu/PRINSEQ-plus-plus"
+ doi: "10.7287/peerj.preprints.27553v1"
+ licence:
+ - "GPL v2"
+ identifier: ""
+input:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - reads:
+ type: file
+ description: |
+ List of input FastQ files of size 1 and 2 for single-end and paired-end
+ data, respectively.
+ ontologies: []
+output:
+ good_reads:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - "*_good_out*.fastq.gz":
+ type: file
+ description: Reads passing filter(s) in gzipped FASTQ format
+ pattern: "*_good_out_{R1,R2}.fastq.gz"
+ ontologies: []
+ single_reads:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - "*_single_out*.fastq.gz":
+ type: file
+ description: |
+ Single reads without the pair passing filter(s) in gzipped FASTQ format
+ pattern: "*_single_out_{R1,R2}.fastq.gz"
+ ontologies: []
+ bad_reads:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - "*_bad_out*.fastq.gz":
+ type: file
+ description: |
+ Reads without not passing filter(s) in gzipped FASTQ format
+ pattern: "*_bad_out_{R1,R2}.fastq.gz"
+ ontologies: []
+ log:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - "*.log":
+ type: file
+ description: |
+ Verbose level 2 STDOUT information in a log file
+ pattern: "*.log"
+ ontologies: []
+ versions_prinseqplusplus:
+ - - ${task.process}:
+ type: string
+ description: The name of the process
+ - prinseqplusplus:
+ type: string
+ description: The name of the tool
+ - prinseq++ --version | cut -f 2 -d ' ':
+ type: eval
+ description: The expression to obtain the version of the tool
+topics:
+ versions:
+ - - ${task.process}:
+ type: string
+ description: The name of the process
+ - prinseqplusplus:
+ type: string
+ description: The name of the tool
+ - prinseq++ --version | cut -f 2 -d ' ':
+ type: eval
+ description: The expression to obtain the version of the tool
+authors:
+ - "@jfy133"
+maintainers:
+ - "@jfy133"
diff --git a/modules/nf-core/prinseqplusplus/tests/main.nf.test b/modules/nf-core/prinseqplusplus/tests/main.nf.test
new file mode 100644
index 0000000..7aadfb0
--- /dev/null
+++ b/modules/nf-core/prinseqplusplus/tests/main.nf.test
@@ -0,0 +1,86 @@
+
+nextflow_process {
+
+ name "Test Process PRINSEQPLUSPLUS"
+ script "../main.nf"
+ process "PRINSEQPLUSPLUS"
+ config "./nextflow.config"
+
+ tag "modules"
+ tag "modules_nfcore"
+ tag "prinseqplusplus"
+
+ test("test-prinseqplusplus-single-end") {
+
+ when {
+ process {
+ """
+ input[0] = [
+ [ id:'test', single_end:true ],
+ [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) ]
+ ]
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert process.success },
+ { assert snapshot(
+ sanitizeOutput(process.out, unstableKeys: ["good_reads", "bad_reads", "log"])
+ ).match()
+ }
+ )
+ }
+ }
+
+ test("test-prinseqplusplus-paired-end") {
+
+ when {
+ process {
+ """
+ input[0] = [
+ [ id:'test', single_end:false ],
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ ]
+ ]
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert process.success },
+ { assert snapshot(
+ sanitizeOutput(process.out, unstableKeys: ["good_reads", "single_reads", "bad_reads", "log"])
+ ).match()
+ }
+ )
+ }
+ }
+
+ test("test-prinseqplusplus-stub") {
+ options "-stub"
+
+ when {
+ process {
+ """
+ input[0] = [
+ [ id:'test', single_end:true ],
+ [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) ]
+ ]
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert process.success },
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+ }
+
+}
diff --git a/modules/nf-core/prinseqplusplus/tests/main.nf.test.snap b/modules/nf-core/prinseqplusplus/tests/main.nf.test.snap
new file mode 100644
index 0000000..907c656
--- /dev/null
+++ b/modules/nf-core/prinseqplusplus/tests/main.nf.test.snap
@@ -0,0 +1,155 @@
+{
+ "test-prinseqplusplus-stub": {
+ "content": [
+ {
+ "bad_reads": [
+
+ ],
+ "good_reads": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test_good_out.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "log": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "single_reads": [
+
+ ],
+ "versions_prinseqplusplus": [
+ [
+ "PRINSEQPLUSPLUS",
+ "prinseqplusplus",
+ "1.2"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-05-15T07:13:35.013113132",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "25.10.2"
+ }
+ },
+ "test-prinseqplusplus-single-end": {
+ "content": [
+ {
+ "bad_reads": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test_bad_out.fastq.gz"
+ ]
+ ],
+ "good_reads": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test_good_out.fastq.gz"
+ ]
+ ],
+ "log": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.log"
+ ]
+ ],
+ "single_reads": [
+
+ ],
+ "versions_prinseqplusplus": [
+ [
+ "PRINSEQPLUSPLUS",
+ "prinseqplusplus",
+ "1.2"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-05-15T07:13:24.142656202",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "25.10.2"
+ }
+ },
+ "test-prinseqplusplus-paired-end": {
+ "content": [
+ {
+ "bad_reads": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test_bad_out_R1.fastq.gz",
+ "test_bad_out_R2.fastq.gz"
+ ]
+ ]
+ ],
+ "good_reads": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test_good_out_R1.fastq.gz",
+ "test_good_out_R2.fastq.gz"
+ ]
+ ]
+ ],
+ "log": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.log"
+ ]
+ ],
+ "single_reads": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test_single_out_R1.fastq.gz",
+ "test_single_out_R2.fastq.gz"
+ ]
+ ]
+ ],
+ "versions_prinseqplusplus": [
+ [
+ "PRINSEQPLUSPLUS",
+ "prinseqplusplus",
+ "1.2"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-05-15T07:13:29.589626825",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "25.10.2"
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/nf-core/prinseqplusplus/tests/nextflow.config b/modules/nf-core/prinseqplusplus/tests/nextflow.config
new file mode 100644
index 0000000..14bae89
--- /dev/null
+++ b/modules/nf-core/prinseqplusplus/tests/nextflow.config
@@ -0,0 +1,5 @@
+process {
+ withName: PRINSEQPLUSPLUS {
+ ext.args = "-lc_entropy=0.8"
+ }
+}
diff --git a/modules/nf-core/seqfu/check/environment.yml b/modules/nf-core/seqfu/check/environment.yml
new file mode 100644
index 0000000..210e292
--- /dev/null
+++ b/modules/nf-core/seqfu/check/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::seqfu=1.22.3
diff --git a/modules/nf-core/seqfu/check/main.nf b/modules/nf-core/seqfu/check/main.nf
new file mode 100644
index 0000000..45f4c09
--- /dev/null
+++ b/modules/nf-core/seqfu/check/main.nf
@@ -0,0 +1,36 @@
+process SEQFU_CHECK {
+ tag "${meta.id}"
+ label 'process_low'
+
+ conda "${moduleDir}/environment.yml"
+ container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container
+ ? 'https://depot.galaxyproject.org/singularity/seqfu:1.22.3--hc29b5fc_1'
+ : 'quay.io/biocontainers/seqfu:1.22.3--hc29b5fc_1'}"
+
+ input:
+ tuple val(meta), path(reads)
+
+ output:
+ tuple val(meta), path("${prefix}.tsv"), emit: check
+ tuple val("${task.process}"), val('seqfu'), eval('seqfu version'), emit: versions_seqfu, topic: versions
+
+ when:
+ task.ext.when == null || task.ext.when
+
+ script:
+ def args = task.ext.args ?: ''
+ prefix = task.ext.prefix ?: "${meta.id}"
+ def dirFlag = (reads instanceof List ? reads.every { read -> read.isDirectory() } : reads.isDirectory()) ? "--dir" : ""
+ """
+ seqfu \\
+ check \\
+ ${args} \\
+ ${dirFlag} ${reads} > ${prefix}.tsv
+ """
+
+ stub:
+ prefix = task.ext.prefix ?: "${meta.id}"
+ """
+ touch ${prefix}.tsv
+ """
+}
diff --git a/modules/nf-core/seqfu/check/meta.yml b/modules/nf-core/seqfu/check/meta.yml
new file mode 100644
index 0000000..73db1c9
--- /dev/null
+++ b/modules/nf-core/seqfu/check/meta.yml
@@ -0,0 +1,74 @@
+name: "seqfu_check"
+description: Evaluates the integrity of DNA FASTQ files
+keywords:
+ - check
+ - seqfu
+ - fastq
+tools:
+ - "seqfu":
+ description: "A general-purpose program to manipulate and parse information from FASTQ files"
+ homepage: https://telatin.github.io/seqfu2/
+ documentation: https://telatin.github.io/seqfu2/tools/check.html#usage
+ tool_dev_url: https://github.com/telatin/seqfu2
+ doi: "10.3390/bioengineering8050059"
+ publication:
+ author: "Telatin A., Fariselli P. & Birolo G."
+ year: 2021
+ title: "SeqFu: A Suite of Utilities for the Robust and Reproducible Manipulation of Sequence Files"
+ source: "Bioengineering"
+ licence: ["MIT"]
+ identifier: biotools:seqfu
+
+input:
+ - - meta:
+ type: map
+ description: Groovy Map containing sample information. e.g. `[
+ id:'sample1' ]`
+ - reads:
+ type: file
+ description: Either fastq file(s) or directory containing fastq files to be checked
+ pattern: "*.{fq,fastq}[.gz]"
+ ontologies:
+ - edam: http://edamontology.org/format_1931 # FASTQ-illumina format
+
+output:
+ check:
+ - - meta:
+ type: map
+ description: Groovy Map containing sample information. e.g. `[
+ id:'sample1' ]`
+ - "${prefix}.tsv":
+ type: file
+ description: >
+ Tab-separated output file with basic sequence statistics.
+ pattern: "*.tsv"
+ ontologies:
+ - edam: http://edamontology.org/format_3475 # TSV
+ versions_seqfu:
+ - - ${task.process}:
+ type: string
+ description: The name of the process
+ - seqfu:
+ type: string
+ description: The name of the tool
+ - seqfu 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
+ - seqfu:
+ type: string
+ description: The name of the tool
+ - seqfu version:
+ type: eval
+ description: The expression to obtain the version of the tool
+authors:
+ - "@kornkv"
+ - "@mrozon-zip"
+maintainers:
+ - "@kornkv"
+ - "@mrozon-zip"
+ - "@vagkaratzas"
diff --git a/modules/nf-core/seqfu/check/tests/main.nf.test b/modules/nf-core/seqfu/check/tests/main.nf.test
new file mode 100644
index 0000000..3d660d7
--- /dev/null
+++ b/modules/nf-core/seqfu/check/tests/main.nf.test
@@ -0,0 +1,107 @@
+nextflow_process {
+
+ name "Test Process SEQFU_CHECK"
+ script "../main.nf"
+ process "SEQFU_CHECK"
+
+ tag "modules"
+ tag "modules_nfcore"
+ tag "seqfu"
+ tag "seqfu/check"
+
+ test("sarscov2 - fastq - single-end") {
+
+ when {
+ process {
+ """
+ input[0] = [[ id:'test', single_end:true ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ]
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert process.success },
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - paired-end") {
+
+ when {
+ process {
+ """
+ input[0] = [
+ [ id:'test_paired', single_end:false ], // meta map
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ ]
+ ]
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert process.success },
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - directory") {
+ when {
+ process {
+ """
+ // stage input fastq files in a folder
+ tmpdir = file("tmpdir", type: 'dir')
+ tmpdir.mkdir()
+ fastq_file_1 = file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ fastq_file_2 = file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ fastq_file_1.copyTo(tmpdir)
+ fastq_file_2.copyTo(tmpdir)
+
+ // define inputs of the process here. Example:
+ input[0] = [
+ [ id:'test' ],
+ [
+ file(tmpdir, type:'dir', checkIfExists: true)
+ ]
+ ]
+ """
+ }
+ }
+ then {
+ assertAll(
+ { assert process.success },
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - single_end - stub") {
+
+ options "-stub"
+
+ when {
+ process {
+ """
+ input[0] = [[ id:'test', single_end:true ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ]
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert process.success },
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+ }
+}
diff --git a/modules/nf-core/seqfu/check/tests/main.nf.test.snap b/modules/nf-core/seqfu/check/tests/main.nf.test.snap
new file mode 100644
index 0000000..47c455f
--- /dev/null
+++ b/modules/nf-core/seqfu/check/tests/main.nf.test.snap
@@ -0,0 +1,109 @@
+{
+ "sarscov2 - fastq - single_end - stub": {
+ "content": [
+ {
+ "check": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.tsv:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "versions_seqfu": [
+ [
+ "SEQFU_CHECK",
+ "seqfu",
+ "1.22.3"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-02-20T11:31:38.98527329",
+ "meta": {
+ "nf-test": "0.9.4",
+ "nextflow": "25.10.4"
+ }
+ },
+ "sarscov2 - fastq - directory": {
+ "content": [
+ {
+ "check": [
+ [
+ {
+ "id": "test"
+ },
+ "test.tsv:md5,cd149b0f08b698981591be4a25729d7e"
+ ]
+ ],
+ "versions_seqfu": [
+ [
+ "SEQFU_CHECK",
+ "seqfu",
+ "1.22.3"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-02-20T11:31:23.018342252",
+ "meta": {
+ "nf-test": "0.9.4",
+ "nextflow": "25.10.4"
+ }
+ },
+ "sarscov2 - fastq - paired-end": {
+ "content": [
+ {
+ "check": [
+ [
+ {
+ "id": "test_paired",
+ "single_end": false
+ },
+ "test_paired.tsv:md5,c3e6eae028f23041bd09cac9c371f318"
+ ]
+ ],
+ "versions_seqfu": [
+ [
+ "SEQFU_CHECK",
+ "seqfu",
+ "1.22.3"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-02-20T12:23:18.115807113",
+ "meta": {
+ "nf-test": "0.9.4",
+ "nextflow": "25.10.4"
+ }
+ },
+ "sarscov2 - fastq - single-end": {
+ "content": [
+ {
+ "check": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.tsv:md5,4c6409169772005cfb06be9e41f2c1e2"
+ ]
+ ],
+ "versions_seqfu": [
+ [
+ "SEQFU_CHECK",
+ "seqfu",
+ "1.22.3"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-02-20T11:30:55.596299312",
+ "meta": {
+ "nf-test": "0.9.4",
+ "nextflow": "25.10.4"
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/nf-core/seqfu/stats/environment.yml b/modules/nf-core/seqfu/stats/environment.yml
new file mode 100644
index 0000000..210e292
--- /dev/null
+++ b/modules/nf-core/seqfu/stats/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::seqfu=1.22.3
diff --git a/modules/nf-core/seqfu/stats/main.nf b/modules/nf-core/seqfu/stats/main.nf
new file mode 100644
index 0000000..5872e04
--- /dev/null
+++ b/modules/nf-core/seqfu/stats/main.nf
@@ -0,0 +1,42 @@
+process SEQFU_STATS {
+ tag "${meta.id}"
+ label 'process_single'
+
+ conda "${moduleDir}/environment.yml"
+ container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container
+ ? 'https://depot.galaxyproject.org/singularity/seqfu:1.22.3--hfd12232_2'
+ : 'quay.io/biocontainers/seqfu:1.22.3--hfd12232_2'}"
+
+ input:
+ // stats can get one or more fasta or fastq files
+ tuple val(meta), path(files)
+
+ output:
+ tuple val(meta), path("*.tsv"), emit: stats
+ tuple val(meta), path("*_mqc.txt"), emit: multiqc
+ tuple val("${task.process}"), val('seqfu'), eval('seqfu version'), emit: versions_seqfu, topic: versions
+
+ when:
+ task.ext.when == null || task.ext.when
+
+ script:
+ def args = task.ext.args ?: ''
+ def prefix = task.ext.prefix ?: "${meta.id}"
+ """
+ seqfu \\
+ stats \\
+ ${args} \\
+ --multiqc ${prefix}_mqc.txt \\
+ ${files} > ${prefix}.tsv
+ """
+
+ stub:
+ def args = task.ext.args ?: ''
+ def prefix = task.ext.prefix ?: "${meta.id}"
+ """
+ echo ${args}
+
+ touch ${prefix}.tsv
+ touch ${prefix}_mqc.txt
+ """
+}
diff --git a/modules/nf-core/seqfu/stats/meta.yml b/modules/nf-core/seqfu/stats/meta.yml
new file mode 100644
index 0000000..a172cc5
--- /dev/null
+++ b/modules/nf-core/seqfu/stats/meta.yml
@@ -0,0 +1,83 @@
+name: "seqfu_stats"
+description: Statistics for FASTA or FASTQ files
+keywords:
+ - seqfu
+ - stats
+ - n50
+tools:
+ - "seqfu":
+ description: "Cross-platform compiled suite of tools to manipulate and inspect
+ FASTA and FASTQ files"
+ homepage: "https://telatin.github.io/seqfu2/"
+ documentation: "https://telatin.github.io/seqfu2/"
+ tool_dev_url: "https://github.com/telatin/seqfu2"
+ doi: "10.3390/bioengineering8050059"
+ publication:
+ author: "Telatin A., Fariselli P. & Birolo G."
+ year: 2021
+ title: "SeqFu: A Suite of Utilities for the Robust and Reproducible Manipulation of Sequence Files"
+ source: "Bioengineering"
+ licence:
+ - "GPL v3"
+ identifier: biotools:seqfu
+input:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. `[ id:'sample1', single_end:false ]`
+ - files:
+ type: file
+ description: One or more FASTA or FASTQ files
+ pattern: "*.{fasta,fastq,fasta.gz,fastq.gz,fq,fq.gz}"
+ ontologies:
+ - edam: http://edamontology.org/format_1930
+output:
+ stats:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. `[ id:'sample1', single_end:false ]`
+ - "*.tsv":
+ type: file
+ description: Tab-separated output file with basic sequence statistics.
+ pattern: "*.{tsv}"
+ ontologies:
+ - edam: http://edamontology.org/format_3475
+ multiqc:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. `[ id:'sample1', single_end:false ]`
+ - "*_mqc.txt":
+ type: file
+ description: MultiQC ready table
+ pattern: "*.{_mqc.txt}"
+ ontologies: []
+ versions_seqfu:
+ - - ${task.process}:
+ type: string
+ description: The name of the process
+ - seqfu:
+ type: string
+ description: The name of the tool
+ - seqfu 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
+ - seqfu:
+ type: string
+ description: The name of the tool
+ - seqfu version:
+ type: eval
+ description: The expression to obtain the version of the tool
+authors:
+ - "@telatin"
+maintainers:
+ - "@telatin"
diff --git a/modules/nf-core/seqfu/stats/tests/main.nf.test b/modules/nf-core/seqfu/stats/tests/main.nf.test
new file mode 100644
index 0000000..c52781c
--- /dev/null
+++ b/modules/nf-core/seqfu/stats/tests/main.nf.test
@@ -0,0 +1,81 @@
+nextflow_process {
+
+ name "Test Process SEQFU_STATS"
+ script "../main.nf"
+ process "SEQFU_STATS"
+
+ tag "modules"
+ tag "modules_nfcore"
+ tag "seqfu"
+ tag "seqfu/stats"
+
+ // test with 1 FAA file (with multiple sequences of different length)
+ test("seqfu stats - faa") {
+ when {
+ process {
+ """
+ input[0] = [
+ [ id:'test_faa' ],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/proteome.fasta', checkIfExists: true)
+ ]
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert process.success },
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+ }
+
+ // test feeding a mix of files including compressed
+ test("seqfu stats - multiple files") {
+ when {
+ process {
+ """
+ input[0] = [
+ [ id:'test_multiple' ],
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/proteome.fasta', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ]
+ ]
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert process.success },
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+ }
+
+ test("seqfu stats - faa - stub") {
+
+ options "-stub"
+
+ when {
+ process {
+ """
+ input[0] = [
+ [ id:'test_stub' ],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/proteome.fasta', checkIfExists: true)
+ ]
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert process.success},
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+ }
+}
diff --git a/modules/nf-core/seqfu/stats/tests/main.nf.test.snap b/modules/nf-core/seqfu/stats/tests/main.nf.test.snap
new file mode 100644
index 0000000..e6ac1e3
--- /dev/null
+++ b/modules/nf-core/seqfu/stats/tests/main.nf.test.snap
@@ -0,0 +1,104 @@
+{
+ "seqfu stats - faa - stub": {
+ "content": [
+ {
+ "multiqc": [
+ [
+ {
+ "id": "test_stub"
+ },
+ "test_stub_mqc.txt:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "stats": [
+ [
+ {
+ "id": "test_stub"
+ },
+ "test_stub.tsv:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "versions_seqfu": [
+ [
+ "SEQFU_STATS",
+ "seqfu",
+ "1.22.3"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-02-20T11:32:51.572529816",
+ "meta": {
+ "nf-test": "0.9.4",
+ "nextflow": "25.10.4"
+ }
+ },
+ "seqfu stats - faa": {
+ "content": [
+ {
+ "multiqc": [
+ [
+ {
+ "id": "test_faa"
+ },
+ "test_faa_mqc.txt:md5,8f3c2edaf1ea5be912c9f99b21b2856c"
+ ]
+ ],
+ "stats": [
+ [
+ {
+ "id": "test_faa"
+ },
+ "test_faa.tsv:md5,0d6bf2cc788f7828761440a1689cac04"
+ ]
+ ],
+ "versions_seqfu": [
+ [
+ "SEQFU_STATS",
+ "seqfu",
+ "1.22.3"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-02-20T11:32:28.645841762",
+ "meta": {
+ "nf-test": "0.9.4",
+ "nextflow": "25.10.4"
+ }
+ },
+ "seqfu stats - multiple files": {
+ "content": [
+ {
+ "multiqc": [
+ [
+ {
+ "id": "test_multiple"
+ },
+ "test_multiple_mqc.txt:md5,dbc6e762eebbf756cd0687807de60445"
+ ]
+ ],
+ "stats": [
+ [
+ {
+ "id": "test_multiple"
+ },
+ "test_multiple.tsv:md5,d016de3d84187a06c8e19b8dabccb3ae"
+ ]
+ ],
+ "versions_seqfu": [
+ [
+ "SEQFU_STATS",
+ "seqfu",
+ "1.22.3"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-02-20T11:32:36.800156393",
+ "meta": {
+ "nf-test": "0.9.4",
+ "nextflow": "25.10.4"
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/nf-core/seqkit/pair/environment.yml b/modules/nf-core/seqkit/pair/environment.yml
new file mode 100644
index 0000000..09abb79
--- /dev/null
+++ b/modules/nf-core/seqkit/pair/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::seqkit=2.13.0
diff --git a/modules/nf-core/seqkit/pair/main.nf b/modules/nf-core/seqkit/pair/main.nf
new file mode 100644
index 0000000..dd0a8ca
--- /dev/null
+++ b/modules/nf-core/seqkit/pair/main.nf
@@ -0,0 +1,44 @@
+process SEQKIT_PAIR {
+ 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/4f/4fe272ab9a519cf418160471a485b5ef50ea3f571a8e4555a826f70a4d8243ae/data'
+ : 'community.wave.seqera.io/library/seqkit:2.13.0--05c0a96bf9fb2751'}"
+
+ input:
+ tuple val(meta), path(reads)
+
+ output:
+ tuple val(meta), path("*.paired.fastq.gz"), emit: reads
+ tuple val(meta), path("*.unpaired.fastq.gz"), optional: true, emit: unpaired_reads
+ tuple val("${task.process}"), val('seqkit'), eval("seqkit version | sed 's/^.*v//'"), emit: versions_seqkit, topic: versions
+
+ when:
+ task.ext.when == null || task.ext.when
+
+ script:
+ def args = task.ext.args ?: ''
+ """
+ seqkit \\
+ pair \\
+ -1 ${reads[0]} \\
+ -2 ${reads[1]} \\
+ ${args} \\
+ --threads ${task.cpus}
+
+ # gzip fastq
+ find . -maxdepth 1 -name "*.fastq" -exec gzip {} \\;
+ """
+
+ stub:
+ def args = task.ext.args ?: ''
+ def prefix = task.ext.prefix ?: "${meta.id}"
+ """
+ echo ${args}
+
+ echo "" | gzip > ${prefix}_1.paired.fastq.gz
+ echo "" | gzip > ${prefix}_2.paired.fastq.gz
+ """
+}
diff --git a/modules/nf-core/seqkit/pair/meta.yml b/modules/nf-core/seqkit/pair/meta.yml
new file mode 100644
index 0000000..40a62c3
--- /dev/null
+++ b/modules/nf-core/seqkit/pair/meta.yml
@@ -0,0 +1,81 @@
+name: seqkit_pair
+description: match up paired-end reads from two fastq files
+keywords:
+ - seqkit
+ - pair
+ - fastq
+tools:
+ - seqkit:
+ description: Cross-platform and ultrafast toolkit for FASTA/Q file manipulation,
+ written by Wei Shen.
+ homepage: https://bioinf.shenwei.me/seqkit/usage/
+ documentation: https://bioinf.shenwei.me/seqkit/usage/
+ tool_dev_url: https://github.com/shenwei356/seqkit/
+ doi: "10.1371/journal.pone.0163962"
+ licence: ["MIT"]
+ identifier: biotools:seqkit
+input:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - reads:
+ type: file
+ description: |
+ List of input paired-end FastQ files.
+ ontologies: []
+output:
+ reads:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - "*.paired.fastq.gz":
+ type: file
+ description: Paired fastq reads
+ pattern: "*.paired.fastq.gz"
+ ontologies:
+ - edam: http://edamontology.org/format_3989 # GZIP format
+ unpaired_reads:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - "*.unpaired.fastq.gz":
+ type: file
+ description: Unpaired reads (optional)
+ pattern: "*.unpaired.fastq.gz"
+ ontologies:
+ - edam: http://edamontology.org/format_3989 # GZIP format
+ versions_seqkit:
+ - - ${task.process}:
+ type: string
+ description: The name of the process
+ - seqkit:
+ type: string
+ description: The name of the tool
+ - seqkit version | sed 's/^.*v//':
+ type: eval
+ description: The expression to obtain the version of the tool
+topics:
+ versions:
+ - - ${task.process}:
+ type: string
+ description: The name of the process
+ - seqkit:
+ type: string
+ description: The name of the tool
+ - seqkit version | sed 's/^.*v//':
+ type: eval
+ description: The expression to obtain the version of the tool
+authors:
+ - "@sateeshperi"
+ - "@mjcipriano"
+ - "@hseabolt"
+maintainers:
+ - "@sateeshperi"
+ - "@mjcipriano"
+ - "@hseabolt"
diff --git a/modules/nf-core/seqkit/pair/tests/main.nf.test b/modules/nf-core/seqkit/pair/tests/main.nf.test
new file mode 100644
index 0000000..535ff00
--- /dev/null
+++ b/modules/nf-core/seqkit/pair/tests/main.nf.test
@@ -0,0 +1,66 @@
+nextflow_process {
+
+ name "Test Process SEQKIT_PAIR"
+ script "../main.nf"
+ process "SEQKIT_PAIR"
+ config "./nextflow.config"
+
+ tag "modules"
+ tag "modules_nfcore"
+ tag "seqkit"
+ tag "seqkit/pair"
+
+ test("sarscov2 - bam") {
+ when {
+ params {
+ modules_args = '-u'
+ }
+ process {
+ """
+ input[0] = [
+ [ id:'test' ], // meta map
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ ]
+ ]
+ """
+ }
+ }
+
+ then {
+ assert process.success
+ assertAll(
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - bam - stub") {
+ options '-stub'
+
+ when {
+ params {
+ modules_args = '-u'
+ }
+ process {
+ """
+ input[0] = [
+ [ id:'test' ], // meta map
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ ]
+ ]
+ """
+ }
+ }
+
+ then {
+ assert process.success
+ assertAll(
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+ }
+}
diff --git a/modules/nf-core/seqkit/pair/tests/main.nf.test.snap b/modules/nf-core/seqkit/pair/tests/main.nf.test.snap
new file mode 100644
index 0000000..9542638
--- /dev/null
+++ b/modules/nf-core/seqkit/pair/tests/main.nf.test.snap
@@ -0,0 +1,66 @@
+{
+ "sarscov2 - bam - stub": {
+ "content": [
+ {
+ "reads": [
+ [
+ {
+ "id": "test"
+ },
+ [
+ "test_1.paired.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940",
+ "test_2.paired.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ]
+ ],
+ "unpaired_reads": [
+
+ ],
+ "versions_seqkit": [
+ [
+ "SEQKIT_PAIR",
+ "seqkit",
+ "2.13.0"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-15T10:20:15.265249096",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "sarscov2 - bam": {
+ "content": [
+ {
+ "reads": [
+ [
+ {
+ "id": "test"
+ },
+ [
+ "test_1.paired.fastq.gz:md5,4161df271f9bfcd25d5845a1e220dbec",
+ "test_2.paired.fastq.gz:md5,2ebae722295ea66d84075a3b042e2b42"
+ ]
+ ]
+ ],
+ "unpaired_reads": [
+
+ ],
+ "versions_seqkit": [
+ [
+ "SEQKIT_PAIR",
+ "seqkit",
+ "2.13.0"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-12T17:25:41.233583224",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/nf-core/seqkit/pair/tests/nextflow.config b/modules/nf-core/seqkit/pair/tests/nextflow.config
new file mode 100644
index 0000000..10b96f3
--- /dev/null
+++ b/modules/nf-core/seqkit/pair/tests/nextflow.config
@@ -0,0 +1,5 @@
+process {
+ withName: SEQKIT_PAIR {
+ ext.args = params.modules_args ?: ''
+ }
+}
diff --git a/modules/nf-core/seqkit/replace/environment.yml b/modules/nf-core/seqkit/replace/environment.yml
new file mode 100644
index 0000000..09abb79
--- /dev/null
+++ b/modules/nf-core/seqkit/replace/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::seqkit=2.13.0
diff --git a/modules/nf-core/seqkit/replace/main.nf b/modules/nf-core/seqkit/replace/main.nf
new file mode 100644
index 0000000..18a1b99
--- /dev/null
+++ b/modules/nf-core/seqkit/replace/main.nf
@@ -0,0 +1,61 @@
+process SEQKIT_REPLACE {
+ tag "${meta.id}"
+ label 'process_low'
+
+ 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/4f/4fe272ab9a519cf418160471a485b5ef50ea3f571a8e4555a826f70a4d8243ae/data'
+ : 'community.wave.seqera.io/library/seqkit:2.13.0--05c0a96bf9fb2751'}"
+
+ input:
+ tuple val(meta), path(fastx)
+ val out_ext
+
+ output:
+ tuple val(meta), path("*.fast*"), emit: fastx
+ tuple val("${task.process}"), val('seqkit'), eval("seqkit version | sed 's/^.*v//'"), emit: versions_seqkit, topic: versions
+
+ when:
+ task.ext.when == null || task.ext.when
+
+ script:
+ def args = task.ext.args ?: ''
+ def prefix = task.ext.prefix ?: "${meta.id}"
+ def extension = "fastq"
+ if ("${fastx}" ==~ /.+\.fasta|.+\.fasta.gz|.+\.fa|.+\.fa.gz|.+\.fas|.+\.fas.gz|.+\.fna|.+\.fna.gz|.+\.faa|.+\.faa.gz/) {
+ extension = "fasta"
+ }
+ def isgz = ""
+ if ("${fastx}" ==~ /.+\.gz/) {
+ isgz = ".gz"
+ }
+ def endswith = out_ext ?: "${extension}${isgz}"
+ """
+ seqkit \\
+ replace \\
+ ${args} \\
+ --threads ${task.cpus} \\
+ -i ${fastx} \\
+ -o ${prefix}.${endswith}
+ """
+
+ stub:
+ def args = task.ext.args ?: ''
+ def prefix = task.ext.prefix ?: "${meta.id}"
+ def extension = "fastq"
+ if ("${fastx}" ==~ /.+\.fasta|.+\.fasta.gz|.+\.fa|.+\.fa.gz|.+\.fas|.+\.fas.gz|.+\.fna|.+\.fna.gz|.+\.faa|.+\.faa.gz/) {
+ extension = "fasta"
+ }
+ def isgz = ""
+ if ("${fastx}" ==~ /.+\.gz/) {
+ isgz = ".gz"
+ }
+ def endswith = out_ext ?: "${extension}${isgz}"
+
+ def create_cmd = endswith.endsWith('gz') ? "echo '' | gzip >" : "touch"
+ """
+ echo ${args}
+
+ ${create_cmd} ${prefix}.${endswith}
+ """
+}
diff --git a/modules/nf-core/seqkit/replace/meta.yml b/modules/nf-core/seqkit/replace/meta.yml
new file mode 100644
index 0000000..e8ef679
--- /dev/null
+++ b/modules/nf-core/seqkit/replace/meta.yml
@@ -0,0 +1,74 @@
+name: seqkit_replace
+description: Use seqkit to find/replace strings within sequences and sequence headers
+keywords:
+ - seqkit
+ - replace
+ - sequence
+ - sequence headers
+ - fasta
+tools:
+ - seqkit:
+ description: Cross-platform and ultrafast toolkit for FASTA/Q file manipulation,
+ written by Wei Shen.
+ homepage: https://bioinf.shenwei.me/seqkit/usage/
+ documentation: https://bioinf.shenwei.me/seqkit/usage/
+ tool_dev_url: https://github.com/shenwei356/seqkit/
+ doi: "10.1371/journal.pone.016396"
+ identifier: biotools:seqkit
+input:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - fastx:
+ type: file
+ description: fasta/q file
+ pattern: "*.{fasta,fastq,fa,fq,fas,fna,faa}*"
+ ontologies:
+ - edam: http://edamontology.org/format_1930 # FASTQ
+ - out_ext:
+ type: string
+ description: >
+ Output file extension. Leave empty to auto-detect from input (for example
+ `.fasta`, `.fasta.gz`)
+output:
+ fastx:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - "*.fast*":
+ type: file
+ description: fasta/q file with replaced values
+ pattern: "*.{fasta,fastq,fa,fq,fas,fna,faa}*"
+ ontologies:
+ - edam: http://edamontology.org/format_1930 # FASTQ
+ versions_seqkit:
+ - - ${task.process}:
+ type: string
+ description: The name of the process
+ - seqkit:
+ type: string
+ description: The name of the tool
+ - seqkit version | sed 's/^.*v//':
+ type: eval
+ description: The expression to obtain the version of seqkit
+
+topics:
+ versions:
+ - - ${task.process}:
+ type: string
+ description: The name of the process
+ - seqkit:
+ type: string
+ description: The name of the tool
+ - seqkit version | sed 's/^.*v//':
+ type: eval
+ description: The expression to obtain the version of seqkit
+
+authors:
+ - "@mjcipriano"
+maintainers:
+ - "@mjcipriano"
diff --git a/modules/nf-core/seqkit/replace/tests/main.nf.test b/modules/nf-core/seqkit/replace/tests/main.nf.test
new file mode 100644
index 0000000..679312e
--- /dev/null
+++ b/modules/nf-core/seqkit/replace/tests/main.nf.test
@@ -0,0 +1,111 @@
+nextflow_process {
+
+ name "Test Process SEQKIT_REPLACE"
+ script "../main.nf"
+ process "SEQKIT_REPLACE"
+
+ tag "modules"
+ tag "modules_nfcore"
+ tag "seqkit"
+ tag "seqkit/replace"
+
+ config "./nextflow.config"
+
+ test("sarscov2 - fasta - replace") {
+ when {
+ params {
+ modules_args = "-s -p 'A' -r 'N'"
+ }
+ process {
+ """
+ input[0] = [
+ [ id:'test' ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/proteome.fasta.gz', checkIfExists: true)
+ ]
+ input[1] = ''
+ """
+ }
+ }
+
+ then {
+ assert process.success
+ assertAll(
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fasta - replace - uncompressed") {
+ when {
+ params {
+ modules_args = "-s -p 'A' -r 'N'"
+ }
+ process {
+ """
+ input[0] = [
+ [ id:'test' ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)
+ ]
+ input[1] = ''
+ """
+ }
+ }
+
+ then {
+ assert process.success
+ assertAll(
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fasta - uncomp - custom") {
+ when {
+ params {
+ modules_args = "-s -p 'T' -r 'N'"
+ }
+ process {
+ """
+ input[0] = [
+ [ id:'test' ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)
+ ]
+ input[1] = 'fasta'
+ """
+ }
+ }
+
+ then {
+ assert process.success
+ assertAll(
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fasta - stub") {
+ options '-stub'
+
+ when {
+ params {
+ modules_args = ""
+ }
+ process {
+ """
+ input[0] = [
+ [ id:'test' ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)
+ ]
+ input[1] = ''
+ """
+ }
+ }
+
+ then {
+ assert process.success
+ assertAll(
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+ }
+}
diff --git a/modules/nf-core/seqkit/replace/tests/main.nf.test.snap b/modules/nf-core/seqkit/replace/tests/main.nf.test.snap
new file mode 100644
index 0000000..02bace1
--- /dev/null
+++ b/modules/nf-core/seqkit/replace/tests/main.nf.test.snap
@@ -0,0 +1,106 @@
+{
+ "sarscov2 - fasta - stub": {
+ "content": [
+ {
+ "fastx": [
+ [
+ {
+ "id": "test"
+ },
+ "test.fasta:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "versions_seqkit": [
+ [
+ "SEQKIT_REPLACE",
+ "seqkit",
+ "2.13.0"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-13T12:47:21.226842064",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "sarscov2 - fasta - replace - uncompressed": {
+ "content": [
+ {
+ "fastx": [
+ [
+ {
+ "id": "test"
+ },
+ "test.fasta:md5,b1518908253a4997fcad98270751112e"
+ ]
+ ],
+ "versions_seqkit": [
+ [
+ "SEQKIT_REPLACE",
+ "seqkit",
+ "2.13.0"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-12T17:26:01.449665395",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "sarscov2 - fasta - uncomp - custom": {
+ "content": [
+ {
+ "fastx": [
+ [
+ {
+ "id": "test"
+ },
+ "test.fasta:md5,05d3294a62c72f5489f067c1da3c2f6c"
+ ]
+ ],
+ "versions_seqkit": [
+ [
+ "SEQKIT_REPLACE",
+ "seqkit",
+ "2.13.0"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-13T12:54:19.323281146",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "sarscov2 - fasta - replace": {
+ "content": [
+ {
+ "fastx": [
+ [
+ {
+ "id": "test"
+ },
+ "test.fasta.gz:md5,c40eaff961f6f2a48bb7e8fd156ed5d7"
+ ]
+ ],
+ "versions_seqkit": [
+ [
+ "SEQKIT_REPLACE",
+ "seqkit",
+ "2.13.0"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-12T17:25:54.471016576",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/nf-core/seqkit/replace/tests/nextflow.config b/modules/nf-core/seqkit/replace/tests/nextflow.config
new file mode 100644
index 0000000..09856f3
--- /dev/null
+++ b/modules/nf-core/seqkit/replace/tests/nextflow.config
@@ -0,0 +1,5 @@
+process {
+ withName: SEQKIT_REPLACE {
+ ext.args = params.modules_args ?: ''
+ }
+}
diff --git a/modules/nf-core/seqkit/rmdup/environment.yml b/modules/nf-core/seqkit/rmdup/environment.yml
new file mode 100644
index 0000000..09abb79
--- /dev/null
+++ b/modules/nf-core/seqkit/rmdup/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::seqkit=2.13.0
diff --git a/modules/nf-core/seqkit/rmdup/main.nf b/modules/nf-core/seqkit/rmdup/main.nf
new file mode 100644
index 0000000..7f86538
--- /dev/null
+++ b/modules/nf-core/seqkit/rmdup/main.nf
@@ -0,0 +1,64 @@
+process SEQKIT_RMDUP {
+ tag "${meta.id}"
+ label 'process_low'
+ // File IO can be a bottleneck. See: https://bioinf.shenwei.me/seqkit/usage/#parallelization-of-cpu-intensive-jobs
+
+ 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/4f/4fe272ab9a519cf418160471a485b5ef50ea3f571a8e4555a826f70a4d8243ae/data'
+ : 'community.wave.seqera.io/library/seqkit:2.13.0--05c0a96bf9fb2751'}"
+
+ input:
+ tuple val(meta), path(fastx)
+
+ output:
+ tuple val(meta), path("${prefix}.${extension}"), emit: fastx
+ tuple val(meta), path("*.log"), emit: log
+ tuple val("${task.process}"), val('seqkit'), eval("seqkit version | sed 's/^.*v//'"), emit: versions_seqkit, topic: versions
+
+ when:
+ task.ext.when == null || task.ext.when
+
+ script:
+ def args = task.ext.args ?: ''
+ prefix = task.ext.prefix ?: "${meta.id}"
+ extension = "fastq"
+ if ("${fastx}" ==~ /.+\.fasta|.+\.fasta.gz|.+\.fa|.+\.fa.gz|.+\.fas|.+\.fas.gz|.+\.fna|.+\.fna.gz|.+\.fsa|.+\.fsa.gz/) {
+ extension = "fasta"
+ }
+ extension = fastx.toString().endsWith('.gz') ? "${extension}.gz" : extension
+ // SeqKit/rmdup takes care of compressing the output: https://bioinf.shenwei.me/seqkit/usage/#rmdup
+ if ("${prefix}.${extension}" == "${fastx}") {
+ error("Input and output names are the same, use \"task.ext.prefix\" to disambiguate!")
+ }
+ """
+ seqkit \\
+ rmdup \\
+ --threads ${task.cpus} \\
+ ${args} \\
+ ${fastx} \\
+ -o ${prefix}.${extension} \\
+ 2>| >(tee ${prefix}.log >&2)
+ """
+
+ stub:
+ def args = task.ext.args ?: ''
+ prefix = task.ext.prefix ?: "${meta.id}"
+ extension = "fastq"
+ if ("${fastx}" ==~ /.+\.fasta|.+\.fasta.gz|.+\.fa|.+\.fa.gz|.+\.fas|.+\.fas.gz|.+\.fna|.+\.fna.gz|.+\.fsa|.+\.fsa.gz/) {
+ extension = "fasta"
+ }
+ extension = fastx.toString().endsWith('.gz') ? "${extension}.gz" : extension
+ if ("${prefix}.${extension}" == "${fastx}") {
+ error("Input and output names are the same, use \"task.ext.prefix\" to disambiguate!")
+ }
+ def create_cmd = extension.endsWith('gz') ? "echo '' | gzip >" : "touch"
+ """
+ echo ${args}
+
+ ${create_cmd} ${prefix}.${extension}
+ echo \\
+ '[INFO] 0 duplicated records removed' \\
+ > ${prefix}.log
+ """
+}
diff --git a/modules/nf-core/seqkit/rmdup/meta.yml b/modules/nf-core/seqkit/rmdup/meta.yml
new file mode 100644
index 0000000..e8dd3f0
--- /dev/null
+++ b/modules/nf-core/seqkit/rmdup/meta.yml
@@ -0,0 +1,80 @@
+# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json
+name: "seqkit_rmdup"
+description: Transforms sequences (extract ID, filter by length, remove gaps, reverse
+ complement...)
+keywords:
+ - genomics
+ - fasta
+ - fastq
+ - remove
+ - duplicates
+tools:
+ - "seqkit":
+ description: "A cross-platform and ultrafast toolkit for FASTA/Q file manipulation"
+ homepage: "https://bioinf.shenwei.me/seqkit/"
+ documentation: "https://bioinf.shenwei.me/seqkit/usage/"
+ tool_dev_url: "https://github.com/shenwei356/seqkit"
+ doi: "10.1371/journal.pone.0163962"
+ licence: ["MIT"]
+ identifier: biotools:seqkit
+input:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. `[ id:'sample1' ]`
+ - fastx:
+ type: file
+ description: Input fasta/fastq file
+ pattern: "*.{fsa,fas,fa,fasta,fastq,fq,fsa.gz,fas.gz,fa.gz,fasta.gz,fastq.gz,fq.gz}"
+ ontologies:
+ - edam: http://edamontology.org/format_1930 # FASTQ
+output:
+ fastx:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. `[ id:'sample1' ]`
+ - ${prefix}.${extension}:
+ type: file
+ description: Output fasta/fastq file
+ pattern: "*.{fasta,fasta.gz,fastq,fastq.gz}"
+ ontologies:
+ - edam: http://edamontology.org/format_1930 # FASTQ
+ log:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. `[ id:'sample1' ]`
+ - "*.log":
+ type: file
+ description: Log containing information regarding removed duplicates
+ pattern: "*.log"
+ ontologies: []
+ versions_seqkit:
+ - - ${task.process}:
+ type: string
+ description: The name of the process
+ - seqkit:
+ type: string
+ description: The name of the tool
+ - seqkit version | sed 's/^.*v//':
+ type: eval
+ description: The expression to obtain the version of the tool
+topics:
+ versions:
+ - - ${task.process}:
+ type: string
+ description: The name of the process
+ - seqkit:
+ type: string
+ description: The name of the tool
+ - seqkit version | sed 's/^.*v//':
+ type: eval
+ description: The expression to obtain the version of the tool
+authors:
+ - "@GallVp"
+maintainers:
+ - "@GallVp"
diff --git a/modules/nf-core/seqkit/rmdup/tests/main.nf.test b/modules/nf-core/seqkit/rmdup/tests/main.nf.test
new file mode 100644
index 0000000..411a2dd
--- /dev/null
+++ b/modules/nf-core/seqkit/rmdup/tests/main.nf.test
@@ -0,0 +1,163 @@
+nextflow_process {
+
+ name "Test Process SEQKIT_RMDUP"
+ script "../main.nf"
+ process "SEQKIT_RMDUP"
+
+ tag "modules"
+ tag "modules_nfcore"
+ tag "seqkit"
+ tag "seqkit/rmdup"
+
+ test("sarscov2-genome_fasta") {
+ when {
+ process {
+ """
+ input[0] = [
+ [ id:'test' ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)
+ ]
+ """
+ }
+ }
+
+ then {
+ assert process.success
+ assertAll(
+ { assert snapshot(sanitizeOutput(process.out)).match() },
+ { assert path(process.out.log[0][1]).text.contains('0 duplicated records removed') }
+ )
+ }
+ }
+
+ test("repeated-fasta") {
+ when {
+ process {
+ """
+ def repeated_fasta = file('repeated.fasta')
+ repeated_fasta.text = '>A\\nAGCTAGCTAGCT\\n>B\\nAGCTAGCTAGCT\\n>A\\nAGCTAGCTAGCT'
+
+ input[0] = [
+ [ id:'test' ], // meta map
+ repeated_fasta
+ ]
+ """
+ }
+ }
+
+ then {
+ assert process.success
+ assertAll(
+ { assert snapshot(sanitizeOutput(process.out)).match() },
+ { assert path(process.out.log[0][1]).text.contains('1 duplicated records removed') }
+ )
+ }
+ }
+
+ test("sarscov2-genome_fasta_gz") {
+ when {
+ process {
+ """
+ input[0] = [
+ [ id:'test' ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.gz', checkIfExists: true)
+ ]
+ """
+ }
+ }
+
+ then {
+ assert process.success
+ assertAll(
+ { assert snapshot(sanitizeOutput(process.out)).match() },
+ { assert path(process.out.log[0][1]).text.contains('0 duplicated records removed') }
+ )
+ }
+ }
+
+ test("sarscov2-test_1_fastq_gz") {
+ when {
+ process {
+ """
+ input[0] = [
+ [ id:'test' ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ]
+ """
+ }
+ }
+
+ then {
+ assert process.success
+ assertAll(
+ { assert snapshot(sanitizeOutput(process.out)).match() },
+ { assert path(process.out.log[0][1]).text.contains('0 duplicated records removed') }
+ )
+ }
+ }
+
+ test("file_name_conflict-fail_with_error") {
+ when {
+ process {
+ """
+ input[0] = [
+ [ id:'test_1' ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ]
+ """
+ }
+ }
+
+ then {
+ assert process.failed
+ assertAll(
+ { assert process.stdout.toString().contains("Input and output names are the same") }
+ )
+ }
+ }
+
+ test("sarscov2-genome_fasta-stub") {
+ options '-stub'
+
+ when {
+ process {
+ """
+ input[0] = [
+ [ id:'test' ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)
+ ]
+ """
+ }
+ }
+
+ then {
+ assert process.success
+ assertAll(
+ { assert snapshot(sanitizeOutput(process.out)).match() },
+ { assert path(process.out.log[0][1]).text.contains('0 duplicated records removed') }
+ )
+ }
+ }
+
+ test("file_name_conflict-fail_with_error-stub") {
+ options '-stub'
+
+ when {
+ process {
+ """
+ input[0] = [
+ [ id:'genome' ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)
+ ]
+ """
+ }
+ }
+
+ then {
+ assert process.failed
+ assertAll(
+ { assert process.stdout.toString().contains("Input and output names are the same") }
+ )
+ }
+ }
+}
diff --git a/modules/nf-core/seqkit/rmdup/tests/main.nf.test.snap b/modules/nf-core/seqkit/rmdup/tests/main.nf.test.snap
new file mode 100644
index 0000000..0bd79b3
--- /dev/null
+++ b/modules/nf-core/seqkit/rmdup/tests/main.nf.test.snap
@@ -0,0 +1,172 @@
+{
+ "sarscov2-genome_fasta-stub": {
+ "content": [
+ {
+ "fastx": [
+ [
+ {
+ "id": "test"
+ },
+ "test.fasta:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "log": [
+ [
+ {
+ "id": "test"
+ },
+ "test.log:md5,cf833211befdf890bb6b2a3cd0b91853"
+ ]
+ ],
+ "versions_seqkit": [
+ [
+ "SEQKIT_RMDUP",
+ "seqkit",
+ "2.13.0"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-12T17:26:48.435786445",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "sarscov2-test_1_fastq_gz": {
+ "content": [
+ {
+ "fastx": [
+ [
+ {
+ "id": "test"
+ },
+ "test.fastq.gz:md5,4161df271f9bfcd25d5845a1e220dbec"
+ ]
+ ],
+ "log": [
+ [
+ {
+ "id": "test"
+ },
+ "test.log:md5,a41135cfe024baaf42f135583fe73f0d"
+ ]
+ ],
+ "versions_seqkit": [
+ [
+ "SEQKIT_RMDUP",
+ "seqkit",
+ "2.13.0"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-12T17:26:39.040489053",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "sarscov2-genome_fasta": {
+ "content": [
+ {
+ "fastx": [
+ [
+ {
+ "id": "test"
+ },
+ "test.fasta:md5,483f4a5dfe60171c86ee9b7e6dff908b"
+ ]
+ ],
+ "log": [
+ [
+ {
+ "id": "test"
+ },
+ "test.log:md5,a41135cfe024baaf42f135583fe73f0d"
+ ]
+ ],
+ "versions_seqkit": [
+ [
+ "SEQKIT_RMDUP",
+ "seqkit",
+ "2.13.0"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-12T17:26:21.035761383",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "repeated-fasta": {
+ "content": [
+ {
+ "fastx": [
+ [
+ {
+ "id": "test"
+ },
+ "test.fasta:md5,7510a742291241e7d7556bf720caf65c"
+ ]
+ ],
+ "log": [
+ [
+ {
+ "id": "test"
+ },
+ "test.log:md5,314c0aaef0f832a217a3f6ce3f8bc117"
+ ]
+ ],
+ "versions_seqkit": [
+ [
+ "SEQKIT_RMDUP",
+ "seqkit",
+ "2.13.0"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-12T17:26:26.868734329",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "sarscov2-genome_fasta_gz": {
+ "content": [
+ {
+ "fastx": [
+ [
+ {
+ "id": "test"
+ },
+ "test.fasta.gz:md5,483f4a5dfe60171c86ee9b7e6dff908b"
+ ]
+ ],
+ "log": [
+ [
+ {
+ "id": "test"
+ },
+ "test.log:md5,a41135cfe024baaf42f135583fe73f0d"
+ ]
+ ],
+ "versions_seqkit": [
+ [
+ "SEQKIT_RMDUP",
+ "seqkit",
+ "2.13.0"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-12T17:26:33.522032009",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/nf-core/seqkit/sana/environment.yml b/modules/nf-core/seqkit/sana/environment.yml
new file mode 100644
index 0000000..09abb79
--- /dev/null
+++ b/modules/nf-core/seqkit/sana/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::seqkit=2.13.0
diff --git a/modules/nf-core/seqkit/sana/main.nf b/modules/nf-core/seqkit/sana/main.nf
new file mode 100644
index 0000000..dd800ba
--- /dev/null
+++ b/modules/nf-core/seqkit/sana/main.nf
@@ -0,0 +1,43 @@
+process SEQKIT_SANA {
+ tag "${meta.id}"
+ label 'process_single'
+
+ 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/4f/4fe272ab9a519cf418160471a485b5ef50ea3f571a8e4555a826f70a4d8243ae/data'
+ : 'community.wave.seqera.io/library/seqkit:2.13.0--05c0a96bf9fb2751'}"
+
+ input:
+ tuple val(meta), path(reads)
+
+ output:
+ tuple val(meta), path("${prefix}${extension}"), emit: reads
+ tuple val(meta), path("${prefix}.log"), emit: log
+ tuple val("${task.process}"), val('seqkit'), eval("seqkit version | sed 's/^.*v//'"), emit: versions_seqkit, topic: versions
+
+ when:
+ task.ext.when == null || task.ext.when
+
+ script:
+ def args = task.ext.args ?: ''
+ prefix = task.ext.prefix ?: "${meta.id}"
+ extension = reads.getName() - reads.getSimpleName()
+ """
+ seqkit sana \\
+ ${args} \\
+ ${reads} \\
+ -o ${prefix}${extension} > ${prefix}.log 2>&1
+ """
+
+ stub:
+ def args = task.ext.args ?: ''
+ prefix = task.ext.prefix ?: "${meta.id}"
+ extension = reads.getName() - reads.getSimpleName()
+ def create_cmd = extension.endsWith('gz') ? "echo '' | gzip >" : "touch"
+ """
+ echo ${args}
+
+ ${create_cmd} ${prefix}${extension}
+ touch ${prefix}.log
+ """
+}
diff --git a/modules/nf-core/seqkit/sana/meta.yml b/modules/nf-core/seqkit/sana/meta.yml
new file mode 100644
index 0000000..6941d25
--- /dev/null
+++ b/modules/nf-core/seqkit/sana/meta.yml
@@ -0,0 +1,71 @@
+name: "seqkit_sana"
+description: Sanitize broken single line FASTQ files
+keywords:
+ - fastq
+ - quality check
+ - skip malformed
+ - parser
+tools:
+ - "seqkit":
+ description: A cross-platform and ultrafast toolkit for FASTA/Q file manipulation.
+ homepage: "https://bioinf.shenwei.me/seqkit"
+ documentation: "https://bioinf.shenwei.me/seqkit"
+ tool_dev_url: "https://github.com/shenwei356/seqkit"
+ doi: "10.1371/journal.pone.0163962"
+ licence: ["MIT"]
+ identifier: biotools:seqkit
+input:
+ - - meta:
+ type: map
+ description: Groovy Map containing sample information. e.g. [ id:'sample1']
+ - reads:
+ type: file
+ description: One line for each sequence and quality value
+ pattern: "*.{fq,fastq}{,.gz}"
+ ontologies:
+ - edam: http://edamontology.org/format_1930 # FASTQ
+output:
+ reads:
+ - - meta:
+ type: map
+ description: Groovy Map containing sample information. e.g. [ id:'sample1']
+ - ${prefix}${extension}:
+ type: file
+ description: Parsed fastq file without malformed entries/lines
+ pattern: "*.${extension}"
+ ontologies:
+ - edam: http://edamontology.org/format_1930 # FASTQ
+ log:
+ - - meta:
+ type: map
+ description: Groovy Map containing sample information. e.g. [ id:'sample1']
+ - ${prefix}.log:
+ type: file
+ description: Log file produced by the seqkit/sana software
+ pattern: "*.log"
+ ontologies: []
+ versions_seqkit:
+ - - ${task.process}:
+ type: string
+ description: The name of the process
+ - seqkit:
+ type: string
+ description: The name of the tool
+ - seqkit version | sed 's/^.*v//':
+ type: eval
+ description: The expression to obtain the version of the tool
+topics:
+ versions:
+ - - ${task.process}:
+ type: string
+ description: The name of the process
+ - seqkit:
+ type: string
+ description: The name of the tool
+ - seqkit version | sed 's/^.*v//':
+ type: eval
+ description: The expression to obtain the version of the tool
+authors:
+ - "@vagkaratzas"
+maintainers:
+ - "@vagkaratzas"
diff --git a/modules/nf-core/seqkit/sana/tests/main.nf.test b/modules/nf-core/seqkit/sana/tests/main.nf.test
new file mode 100644
index 0000000..026746c
--- /dev/null
+++ b/modules/nf-core/seqkit/sana/tests/main.nf.test
@@ -0,0 +1,53 @@
+nextflow_process {
+
+ name "Test Process SEQKIT_SANA"
+ script "../main.nf"
+ process "SEQKIT_SANA"
+
+ tag "modules"
+ tag "modules_nfcore"
+ tag "seqkit"
+ tag "seqkit/sana"
+
+ test("sarscov2 - fastq") {
+ when {
+ process {
+ """
+ input[0] = [
+ [ id:'test', single_end:true ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1_broken.fastq.gz', checkIfExists: true)
+ ]
+ """
+ }
+ }
+
+ then {
+ assert process.success
+ assertAll(
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - stub") {
+ options '-stub'
+
+ when {
+ process {
+ """
+ input[0] = [
+ [ id:'test', single_end:true ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1_broken.fastq.gz', checkIfExists: true)
+ ]
+ """
+ }
+ }
+
+ then {
+ assert process.success
+ assertAll(
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+ }
+}
diff --git a/modules/nf-core/seqkit/sana/tests/main.nf.test.snap b/modules/nf-core/seqkit/sana/tests/main.nf.test.snap
new file mode 100644
index 0000000..8971a2e
--- /dev/null
+++ b/modules/nf-core/seqkit/sana/tests/main.nf.test.snap
@@ -0,0 +1,74 @@
+{
+ "sarscov2 - fastq - stub": {
+ "content": [
+ {
+ "log": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "reads": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "versions_seqkit": [
+ [
+ "SEQKIT_SANA",
+ "seqkit",
+ "2.13.0"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-15T10:29:52.287449164",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "sarscov2 - fastq": {
+ "content": [
+ {
+ "log": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.log:md5,72633c56cad32996aa9c7daa9e687e59"
+ ]
+ ],
+ "reads": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastq.gz:md5,03b43487ca6aeecc5eb8b55bbc1fe2f3"
+ ]
+ ],
+ "versions_seqkit": [
+ [
+ "SEQKIT_SANA",
+ "seqkit",
+ "2.13.0"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-13T12:30:27.845745504",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/nf-core/seqkit/seq/environment.yml b/modules/nf-core/seqkit/seq/environment.yml
new file mode 100644
index 0000000..09abb79
--- /dev/null
+++ b/modules/nf-core/seqkit/seq/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::seqkit=2.13.0
diff --git a/modules/nf-core/seqkit/seq/main.nf b/modules/nf-core/seqkit/seq/main.nf
new file mode 100644
index 0000000..3bade4e
--- /dev/null
+++ b/modules/nf-core/seqkit/seq/main.nf
@@ -0,0 +1,63 @@
+process SEQKIT_SEQ {
+ tag "${meta.id}"
+ label 'process_low'
+ // File IO can be a bottleneck. See: https://bioinf.shenwei.me/seqkit/usage/#parallelization-of-cpu-intensive-jobs
+
+ 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/4f/4fe272ab9a519cf418160471a485b5ef50ea3f571a8e4555a826f70a4d8243ae/data'
+ : 'community.wave.seqera.io/library/seqkit:2.13.0--05c0a96bf9fb2751'}"
+
+ input:
+ tuple val(meta), path(fastx)
+
+ output:
+ tuple val(meta), path("${prefix}.*"), emit: fastx
+ tuple val("${task.process}"), val('seqkit'), eval("seqkit version | sed 's/^.*v//'"), emit: versions_seqkit, topic: versions
+
+ when:
+ task.ext.when == null || task.ext.when
+
+ script:
+ def args = task.ext.args ?: ''
+ def args2 = task.ext.args2 ?: ''
+ prefix = task.ext.prefix ?: "${meta.id}"
+ def extension = "fastq"
+ if ("${fastx}" ==~ /.+\.fasta|.+\.fasta.gz|.+\.fa|.+\.fa.gz|.+\.fas|.+\.fas.gz|.+\.fna|.+\.fna.gz|.+\.fsa|.+\.fsa.gz/) {
+ extension = "fasta"
+ }
+ extension = fastx.toString().endsWith('.gz') ? "${extension}.gz" : extension
+ def call_gzip = extension.endsWith('.gz') ? "| gzip -c ${args2}" : ''
+ if ("${prefix}.${extension}" == "${fastx}") {
+ error("Input and output names are the same, use \"task.ext.prefix\" to disambiguate!")
+ }
+ """
+ seqkit \\
+ seq \\
+ --threads ${task.cpus} \\
+ ${args} \\
+ ${fastx} \\
+ ${call_gzip} \\
+ > ${prefix}.${extension}
+ """
+
+ stub:
+ def args = task.ext.args ?: ''
+ def args2 = task.ext.args2 ?: ''
+ prefix = task.ext.prefix ?: "${meta.id}"
+ def extension = "fastq"
+ if ("${fastx}" ==~ /.+\.fasta|.+\.fasta.gz|.+\.fa|.+\.fa.gz|.+\.fas|.+\.fas.gz|.+\.fna|.+\.fna.gz|.+\.fsa|.+\.fsa.gz/) {
+ extension = "fasta"
+ }
+ extension = fastx.toString().endsWith('.gz') ? "${extension}.gz" : extension
+ if ("${prefix}.${extension}" == "${fastx}") {
+ error("Input and output names are the same, use \"task.ext.prefix\" to disambiguate!")
+ }
+ def create_cmd = extension.endsWith('gz') ? "echo '' | gzip >" : "touch"
+ """
+ echo ${args}
+ echo ${args2}
+
+ ${create_cmd} ${prefix}.${extension}
+ """
+}
diff --git a/modules/nf-core/seqkit/seq/meta.yml b/modules/nf-core/seqkit/seq/meta.yml
new file mode 100644
index 0000000..df93e0c
--- /dev/null
+++ b/modules/nf-core/seqkit/seq/meta.yml
@@ -0,0 +1,71 @@
+# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json
+name: "seqkit_seq"
+description: Transforms sequences (extract ID, filter by length, remove gaps, reverse
+ complement...)
+keywords:
+ - genomics
+ - fasta
+ - fastq
+ - transform
+ - filter
+ - gaps
+ - complement
+tools:
+ - "seqkit":
+ description: "A cross-platform and ultrafast toolkit for FASTA/Q file manipulation"
+ homepage: "https://bioinf.shenwei.me/seqkit/"
+ documentation: "https://bioinf.shenwei.me/seqkit/usage/"
+ tool_dev_url: "https://github.com/shenwei356/seqkit"
+ doi: "10.1371/journal.pone.0163962"
+ licence: ["MIT"]
+ identifier: biotools:seqkit
+input:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. `[ id:'sample1' ]`
+ - fastx:
+ type: file
+ description: Input fasta/fastq file
+ pattern: "*.{fsa,fas,fa,fasta,fastq,fq,fsa.gz,fas.gz,fa.gz,fasta.gz,fastq.gz,fq.gz}"
+ ontologies:
+ - edam: http://edamontology.org/format_1930 # FASTQ
+output:
+ fastx:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. `[ id:'sample1' ]`
+ - ${prefix}.*:
+ type: file
+ description: Output fasta/fastq file
+ pattern: "*.{fasta,fasta.gz,fastq,fastq.gz}"
+ ontologies:
+ - edam: http://edamontology.org/format_1930 # FASTQ
+ versions_seqkit:
+ - - ${task.process}:
+ type: string
+ description: The name of the process
+ - seqkit:
+ type: string
+ description: The name of the tool
+ - seqkit version | sed 's/^.*v//':
+ type: eval
+ description: The expression to obtain the version of the tool
+topics:
+ versions:
+ - - ${task.process}:
+ type: string
+ description: The name of the process
+ - seqkit:
+ type: string
+ description: The name of the tool
+ - seqkit version | sed 's/^.*v//':
+ type: eval
+ description: The expression to obtain the version of the tool
+authors:
+ - "@GallVp"
+maintainers:
+ - "@GallVp"
diff --git a/modules/nf-core/seqkit/seq/tests/main.nf.test b/modules/nf-core/seqkit/seq/tests/main.nf.test
new file mode 100644
index 0000000..457be88
--- /dev/null
+++ b/modules/nf-core/seqkit/seq/tests/main.nf.test
@@ -0,0 +1,154 @@
+nextflow_process {
+
+ name "Test Process SEQKIT_SEQ"
+ script "../main.nf"
+ process "SEQKIT_SEQ"
+ config './nextflow.config'
+
+ tag "modules"
+ tag "modules_nfcore"
+ tag "seqkit"
+ tag "seqkit/seq"
+
+ test("sarscov2-genome_fasta") {
+ when {
+ params {
+ modules_args2 = '-n'
+ }
+ process {
+ """
+ input[0] = [
+ [ id:'test' ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)
+ ]
+ """
+ }
+ }
+
+ then {
+ assert process.success
+ assertAll(
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+ }
+
+ test("sarscov2-genome_fasta_gz") {
+ when {
+ params {
+ modules_args2 = '-n'
+ }
+ process {
+ """
+ input[0] = [
+ [ id:'test' ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.gz', checkIfExists: true)
+ ]
+ """
+ }
+ }
+
+ then {
+ assert process.success
+ assertAll(
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+ }
+
+ test("sarscov2-test_1_fastq_gz") {
+ when {
+ params {
+ modules_args2 = '-n'
+ }
+ process {
+ """
+ input[0] = [
+ [ id:'test' ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ]
+ """
+ }
+ }
+
+ then {
+ assert process.success
+ assertAll(
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+ }
+
+ test("file_name_conflict-fail_with_error") {
+ when {
+ params {
+ modules_args2 = '-n'
+ }
+ process {
+ """
+ input[0] = [
+ [ id:'test_1' ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ]
+ """
+ }
+ }
+
+ then {
+ assert process.failed
+ assertAll(
+ { assert process.stdout.toString().contains("Input and output names are the same") }
+ )
+ }
+ }
+
+ test("sarscov2-genome_fasta-stub") {
+ options '-stub'
+
+ when {
+ params {
+ modules_args2 = '-n'
+ }
+ process {
+ """
+ input[0] = [
+ [ id:'test' ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)
+ ]
+ """
+ }
+ }
+
+ then {
+ assert process.success
+ assertAll(
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+ }
+
+ test("file_name_conflict-fail_with_error-stub") {
+ options '-stub'
+
+ when {
+ params {
+ modules_args2 = '-n'
+ }
+ process {
+ """
+ input[0] = [
+ [ id:'genome' ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)
+ ]
+ """
+ }
+ }
+
+ then {
+ assert process.failed
+ assertAll(
+ { assert process.stdout.toString().contains("Input and output names are the same") }
+ )
+ }
+ }
+}
diff --git a/modules/nf-core/seqkit/seq/tests/main.nf.test.snap b/modules/nf-core/seqkit/seq/tests/main.nf.test.snap
new file mode 100644
index 0000000..d8e79f6
--- /dev/null
+++ b/modules/nf-core/seqkit/seq/tests/main.nf.test.snap
@@ -0,0 +1,106 @@
+{
+ "sarscov2-genome_fasta-stub": {
+ "content": [
+ {
+ "fastx": [
+ [
+ {
+ "id": "test"
+ },
+ "test.fasta:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "versions_seqkit": [
+ [
+ "SEQKIT_SEQ",
+ "seqkit",
+ "2.13.0"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-14T18:54:38.462339108",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "sarscov2-test_1_fastq_gz": {
+ "content": [
+ {
+ "fastx": [
+ [
+ {
+ "id": "test"
+ },
+ "test.fastq.gz:md5,4161df271f9bfcd25d5845a1e220dbec"
+ ]
+ ],
+ "versions_seqkit": [
+ [
+ "SEQKIT_SEQ",
+ "seqkit",
+ "2.13.0"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-12T17:27:53.391265296",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "sarscov2-genome_fasta": {
+ "content": [
+ {
+ "fastx": [
+ [
+ {
+ "id": "test"
+ },
+ "test.fasta:md5,483f4a5dfe60171c86ee9b7e6dff908b"
+ ]
+ ],
+ "versions_seqkit": [
+ [
+ "SEQKIT_SEQ",
+ "seqkit",
+ "2.13.0"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-12T17:27:43.147566645",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "sarscov2-genome_fasta_gz": {
+ "content": [
+ {
+ "fastx": [
+ [
+ {
+ "id": "test"
+ },
+ "test.fasta.gz:md5,483f4a5dfe60171c86ee9b7e6dff908b"
+ ]
+ ],
+ "versions_seqkit": [
+ [
+ "SEQKIT_SEQ",
+ "seqkit",
+ "2.13.0"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-12T17:27:48.241605452",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/nf-core/seqkit/seq/tests/nextflow.config b/modules/nf-core/seqkit/seq/tests/nextflow.config
new file mode 100644
index 0000000..cd1f022
--- /dev/null
+++ b/modules/nf-core/seqkit/seq/tests/nextflow.config
@@ -0,0 +1,5 @@
+process {
+ withName: SEQKIT_SEQ {
+ ext.args2 = params.modules_args2 ?: ''
+ }
+}
diff --git a/modules/nf-core/seqkit/stats/environment.yml b/modules/nf-core/seqkit/stats/environment.yml
new file mode 100644
index 0000000..09abb79
--- /dev/null
+++ b/modules/nf-core/seqkit/stats/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::seqkit=2.13.0
diff --git a/modules/nf-core/seqkit/stats/main.nf b/modules/nf-core/seqkit/stats/main.nf
new file mode 100644
index 0000000..dd9e2bc
--- /dev/null
+++ b/modules/nf-core/seqkit/stats/main.nf
@@ -0,0 +1,39 @@
+process SEQKIT_STATS {
+ tag "${meta.id}"
+ label 'process_low'
+
+ 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/4f/4fe272ab9a519cf418160471a485b5ef50ea3f571a8e4555a826f70a4d8243ae/data'
+ : 'community.wave.seqera.io/library/seqkit:2.13.0--05c0a96bf9fb2751'}"
+
+ input:
+ tuple val(meta), path(reads)
+
+ output:
+ tuple val(meta), path("*.tsv"), emit: stats
+ tuple val("${task.process}"), val('seqkit'), eval("seqkit version | sed 's/^.*v//'"), emit: versions_seqkit, topic: versions
+
+ when:
+ task.ext.when == null || task.ext.when
+
+ script:
+ def args = task.ext.args ?: '--all'
+ def prefix = task.ext.prefix ?: "${meta.id}"
+ """
+ seqkit stats \\
+ --tabular \\
+ --threads ${task.cpus} \\
+ ${args} \\
+ ${reads} > '${prefix}.tsv'
+ """
+
+ stub:
+ def args = task.ext.args ?: '--all'
+ def prefix = task.ext.prefix ?: "${meta.id}"
+ """
+ echo ${args}
+
+ touch ${prefix}.tsv
+ """
+}
diff --git a/modules/nf-core/seqkit/stats/meta.yml b/modules/nf-core/seqkit/stats/meta.yml
new file mode 100644
index 0000000..3143449
--- /dev/null
+++ b/modules/nf-core/seqkit/stats/meta.yml
@@ -0,0 +1,72 @@
+name: "seqkit_stats"
+description: simple statistics of FASTA/Q files
+keywords:
+ - seqkit
+ - fasta
+ - stats
+tools:
+ - "seqkit":
+ description: Cross-platform and ultrafast toolkit for FASTA/Q file manipulation,
+ written by Wei Shen.
+ homepage: https://bioinf.shenwei.me/seqkit/usage/
+ documentation: https://bioinf.shenwei.me/seqkit/usage/
+ tool_dev_url: https://github.com/shenwei356/seqkit/
+ doi: "10.1371/journal.pone.0163962"
+ licence: ["MIT"]
+ identifier: biotools:seqkit
+input:
+ - - meta:
+ type: map
+ description: >
+ Groovy Map containing sample information e.g. [ id:'test', single_end:false
+ ]
+ - reads:
+ type: file
+ description: >
+ Either FASTA or FASTQ files.
+ pattern: "*.{fa,fna,faa,fasta,fq,fastq}[.gz]"
+ ontologies:
+ - edam: http://edamontology.org/format_1930 # FASTQ
+output:
+ stats:
+ - - meta:
+ type: map
+ description: >
+ Groovy Map containing sample information e.g. [ id:'test', single_end:false
+ ]
+ - "*.tsv":
+ type: file
+ description: >
+ Tab-separated output file with basic sequence statistics.
+ pattern: "*.tsv"
+ ontologies:
+ - edam: http://edamontology.org/format_3475 # TSV
+ versions_seqkit:
+ - - ${task.process}:
+ type: string
+ description: The name of the process
+ - seqkit:
+ type: string
+ description: The name of the tool
+ - seqkit version | sed 's/^.*v//':
+ type: eval
+ description: The expression to obtain the version of seqkit
+
+topics:
+ versions:
+ - - ${task.process}:
+ type: string
+ description: The name of the process
+ - seqkit:
+ type: string
+ description: The name of the tool
+ - seqkit version | sed 's/^.*v//':
+ type: eval
+ description: The expression to obtain the version of seqkit
+
+authors:
+ - "@Midnighter"
+ - "@heuermh"
+maintainers:
+ - "@Midnighter"
+ - "@heuermh"
diff --git a/modules/nf-core/seqkit/stats/tests/main.nf.test b/modules/nf-core/seqkit/stats/tests/main.nf.test
new file mode 100644
index 0000000..aa942ec
--- /dev/null
+++ b/modules/nf-core/seqkit/stats/tests/main.nf.test
@@ -0,0 +1,136 @@
+nextflow_process {
+
+ name "Test Process SEQKIT_STATS"
+ script "../main.nf"
+ process "SEQKIT_STATS"
+
+ tag "modules"
+ tag "modules_nfcore"
+ tag "seqkit"
+ tag "seqkit/stats"
+
+ test("single_end") {
+ when {
+ process {
+ """
+ input[0] = [
+ [ id:'test', single_end:true ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ]
+ """
+ }
+ }
+
+ then {
+ assert process.success
+ assertAll(
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+ }
+
+ test("paired_end") {
+ when {
+ process {
+ """
+ input[0] = [
+ [ id:'test', single_end:false ], // meta map
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ ]
+ ]
+ """
+ }
+ }
+
+ then {
+ assert process.success
+ assertAll(
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+ }
+
+ test("nanopore") {
+ when {
+ process {
+ """
+ input[0] = [
+ [ id:'test', single_end:true ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/nanopore/fastq/test.fastq.gz', checkIfExists: true)
+ ]
+ """
+ }
+ }
+
+ then {
+ assert process.success
+ assertAll(
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+ }
+
+ test("genome_fasta") {
+ when {
+ process {
+ """
+ input[0] = [
+ [ id:'test', single_end:false ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)
+ ]
+ """
+ }
+ }
+
+ then {
+ assert process.success
+ assertAll(
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+ }
+
+ test("transcriptome_fasta") {
+ when {
+ process {
+ """
+ input[0] = [
+ [ id:'test', single_end:false ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/transcriptome.fasta', checkIfExists: true)
+ ]
+ """
+ }
+ }
+
+ then {
+ assert process.success
+ assertAll(
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+ }
+
+ test("single_end - stub") {
+ options '-stub'
+
+ when {
+ process {
+ """
+ input[0] = [
+ [ id:'test', single_end:true ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ]
+ """
+ }
+ }
+
+ then {
+ assert process.success
+ assertAll(
+ { assert snapshot(sanitizeOutput(process.out)).match() }
+ )
+ }
+ }
+}
diff --git a/modules/nf-core/seqkit/stats/tests/main.nf.test.snap b/modules/nf-core/seqkit/stats/tests/main.nf.test.snap
new file mode 100644
index 0000000..6c75aa0
--- /dev/null
+++ b/modules/nf-core/seqkit/stats/tests/main.nf.test.snap
@@ -0,0 +1,164 @@
+{
+ "nanopore": {
+ "content": [
+ {
+ "stats": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.tsv:md5,388047b0263b8d0a3041d6d80b316782"
+ ]
+ ],
+ "versions_seqkit": [
+ [
+ "SEQKIT_STATS",
+ "seqkit",
+ "2.13.0"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-12T17:30:18.267532797",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "genome_fasta": {
+ "content": [
+ {
+ "stats": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.tsv:md5,6f6f8b42abd183c6ab90346a311ca522"
+ ]
+ ],
+ "versions_seqkit": [
+ [
+ "SEQKIT_STATS",
+ "seqkit",
+ "2.13.0"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-12T17:30:22.693843391",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "transcriptome_fasta": {
+ "content": [
+ {
+ "stats": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.tsv:md5,c79d860b2b0379810e4df3e8359fc8ed"
+ ]
+ ],
+ "versions_seqkit": [
+ [
+ "SEQKIT_STATS",
+ "seqkit",
+ "2.13.0"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-12T17:30:27.169227967",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "single_end": {
+ "content": [
+ {
+ "stats": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.tsv:md5,76d5ab444de880ef443f358841e44cf9"
+ ]
+ ],
+ "versions_seqkit": [
+ [
+ "SEQKIT_STATS",
+ "seqkit",
+ "2.13.0"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-12T17:30:09.156247735",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "paired_end": {
+ "content": [
+ {
+ "stats": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.tsv:md5,2d96d9f7d85456090e138b81bf97bcd5"
+ ]
+ ],
+ "versions_seqkit": [
+ [
+ "SEQKIT_STATS",
+ "seqkit",
+ "2.13.0"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-12T17:35:49.824033505",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "single_end - stub": {
+ "content": [
+ {
+ "stats": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.tsv:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "versions_seqkit": [
+ [
+ "SEQKIT_STATS",
+ "seqkit",
+ "2.13.0"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-12T17:30:31.558337736",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/nf-core/seqtk/comp/environment.yml b/modules/nf-core/seqtk/comp/environment.yml
new file mode 100644
index 0000000..bfa1b01
--- /dev/null
+++ b/modules/nf-core/seqtk/comp/environment.yml
@@ -0,0 +1,6 @@
+---
+channels:
+ - conda-forge
+ - bioconda
+dependencies:
+ - "bioconda::seqtk=1.4"
diff --git a/modules/nf-core/seqtk/comp/main.nf b/modules/nf-core/seqtk/comp/main.nf
new file mode 100644
index 0000000..e5d7c06
--- /dev/null
+++ b/modules/nf-core/seqtk/comp/main.nf
@@ -0,0 +1,37 @@
+
+process SEQTK_COMP {
+ tag "$meta.id"
+ label 'process_low'
+
+ conda "${moduleDir}/environment.yml"
+ container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ?
+ 'https://depot.galaxyproject.org/singularity/seqtk:1.4--h577a1d6_3':
+ 'quay.io/biocontainers/seqtk:1.4--h577a1d6_3' }"
+
+ input:
+ tuple val(meta), path(fastx)
+
+ output:
+ tuple val(meta), path("*.seqtk_stats.tsv"), emit: seqtk_stats
+ tuple val("${task.process}"), val('seqtk'), eval("seqtk 2>&1 | sed -n 's/^Version: //p'"), emit: versions_seqtk, topic: versions
+
+ when:
+ task.ext.when == null || task.ext.when
+
+ script:
+ def args = task.ext.args ?: ''
+ def prefix = task.ext.prefix ?: "${meta.id}"
+
+ """
+ seqtk comp \\
+ ${args} \\
+ ${fastx} > ${prefix}.seqtk_stats.tsv
+ """
+
+ stub:
+ def prefix = task.ext.prefix ?: "${meta.id}"
+
+ """
+ echo "" > ${prefix}.seqtk_stats.tsv
+ """
+}
diff --git a/modules/nf-core/seqtk/comp/meta.yml b/modules/nf-core/seqtk/comp/meta.yml
new file mode 100644
index 0000000..bcdd15d
--- /dev/null
+++ b/modules/nf-core/seqtk/comp/meta.yml
@@ -0,0 +1,73 @@
+name: "seqtk_comp"
+description: Computes sequence statistics from FASTQ or FASTA files
+keywords:
+ - seqtk
+ - comp
+ - fastx
+
+tools:
+ - "seqtk_comp":
+ description: |
+ Seqtk is a fast and lightweight tool for processing sequences in the FASTA or FASTQ format.
+ The seqtk comp command computes base composition, sequence length, and GC content for quality control.
+
+ homepage: https://github.com/lh3/seqtk
+ documentation: https://docs.csc.fi/apps/seqtk/
+ tool_dev_url: https://github.com/lh3/seqtk
+ licence: ["MIT"]
+ identifier: biotools:seqtk
+
+input:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'sample1' ]
+ - fastx:
+ type: file
+ description: A FASTQ or FASTA file
+ pattern: "*.{fastq,fq,fasta,fa,fas,fna}{,.gz}"
+
+ ontologies:
+ - edam: http://edamontology.org/format_1930 # FASTQ
+output:
+ seqtk_stats:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'sample1', single_end:false ]
+ - "*.seqtk_stats.tsv":
+ type: file
+ description: The output TSV file summarizing sequence statistics with
+ columns for sequence name, length, counts of A, C, G, T, and N bases,
+ and GC content percentage."
+ pattern: "*.seqtk_stats.tsv"
+
+ ontologies:
+ - edam: http://edamontology.org/format_3475 # TSV
+ versions_seqtk:
+ - - ${task.process}:
+ type: string
+ description: The name of the process
+ - seqtk:
+ type: string
+ description: The name of the tool
+ - "seqtk 2>&1 | sed -n 's/^Version: //p'":
+ type: eval
+ description: The expression to obtain the version of the tool
+topics:
+ versions:
+ - - ${task.process}:
+ type: string
+ description: The name of the process
+ - seqtk:
+ type: string
+ description: The name of the tool
+ - "seqtk 2>&1 | sed -n 's/^Version: //p'":
+ type: eval
+ description: The expression to obtain the version of the tool
+authors:
+ - "@sainsachiko"
+maintainers:
+ - "@sainsachiko"
diff --git a/modules/nf-core/seqtk/comp/tests/main.nf.test b/modules/nf-core/seqtk/comp/tests/main.nf.test
new file mode 100644
index 0000000..8149813
--- /dev/null
+++ b/modules/nf-core/seqtk/comp/tests/main.nf.test
@@ -0,0 +1,59 @@
+nextflow_process {
+
+ name "Test Process SEQTK_COMP"
+ script "../main.nf"
+ process "SEQTK_COMP"
+
+ tag "modules"
+ tag "modules_nfcore"
+ tag "seqtk"
+ tag "seqtk/comp"
+
+ test("bacteroides_fragilis - fasta") {
+
+ when {
+ process {
+ """
+ input[0] = [
+ [ id:'test', single_end:true ],
+ file( params.modules_testdata_base_path + 'genomics/prokaryotes/bacteroides_fragilis/genome/genome.fna.gz', checkIfExists: true),
+ ]
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert process.success },
+ { assert snapshot(process.out).match() }
+ )
+ with(process.out.seqtk_stats[0]) {
+ assert path(get(1)).text.contains("NZ_CP069563.1")
+ }
+ }
+
+ }
+
+ test("bacteroides_fragilis - fasta - stub") {
+
+ options "-stub"
+
+ when {
+ process {
+ """
+ input[0] = [
+ [ id:'test', single_end:true ],
+ file( params.modules_testdata_base_path + 'genomics/prokaryotes/bacteroides_fragilis/genome/genome.fna.gz', checkIfExists: true),
+ ]
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert process.success },
+ { assert snapshot(process.out).match() }
+ )
+ }
+ }
+}
diff --git a/modules/nf-core/seqtk/comp/tests/main.nf.test.snap b/modules/nf-core/seqtk/comp/tests/main.nf.test.snap
new file mode 100644
index 0000000..a78f282
--- /dev/null
+++ b/modules/nf-core/seqtk/comp/tests/main.nf.test.snap
@@ -0,0 +1,88 @@
+{
+ "bacteroides_fragilis - fasta": {
+ "content": [
+ {
+ "0": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.seqtk_stats.tsv:md5,46c8788954c64c718a799c0f6a41ecdc"
+ ]
+ ],
+ "1": [
+ [
+ "SEQTK_COMP",
+ "seqtk",
+ "1.4-r122"
+ ]
+ ],
+ "seqtk_stats": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.seqtk_stats.tsv:md5,46c8788954c64c718a799c0f6a41ecdc"
+ ]
+ ],
+ "versions_seqtk": [
+ [
+ "SEQTK_COMP",
+ "seqtk",
+ "1.4-r122"
+ ]
+ ]
+ }
+ ],
+ "meta": {
+ "nf-test": "0.9.2",
+ "nextflow": "25.10.2"
+ },
+ "timestamp": "2026-01-14T12:07:29.217316713"
+ },
+ "bacteroides_fragilis - fasta - stub": {
+ "content": [
+ {
+ "0": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.seqtk_stats.tsv:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "1": [
+ [
+ "SEQTK_COMP",
+ "seqtk",
+ "1.4-r122"
+ ]
+ ],
+ "seqtk_stats": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.seqtk_stats.tsv:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "versions_seqtk": [
+ [
+ "SEQTK_COMP",
+ "seqtk",
+ "1.4-r122"
+ ]
+ ]
+ }
+ ],
+ "meta": {
+ "nf-test": "0.9.2",
+ "nextflow": "25.10.2"
+ },
+ "timestamp": "2026-01-14T12:07:37.125237854"
+ }
+}
\ No newline at end of file
diff --git a/modules/nf-core/seqtk/comp/tests/tags.yml b/modules/nf-core/seqtk/comp/tests/tags.yml
new file mode 100644
index 0000000..eb168ac
--- /dev/null
+++ b/modules/nf-core/seqtk/comp/tests/tags.yml
@@ -0,0 +1,2 @@
+seqtk/mergepe:
+ - "modules/nf-core/seqtk/comp/**"
diff --git a/modules/nf-core/trimgalore/environment.yml b/modules/nf-core/trimgalore/environment.yml
new file mode 100644
index 0000000..6f07df0
--- /dev/null
+++ b/modules/nf-core/trimgalore/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::trim-galore=2.3.0
diff --git a/modules/nf-core/trimgalore/main.nf b/modules/nf-core/trimgalore/main.nf
new file mode 100644
index 0000000..af819db
--- /dev/null
+++ b/modules/nf-core/trimgalore/main.nf
@@ -0,0 +1,87 @@
+process TRIMGALORE {
+ tag "${meta.id}"
+ label 'process_medium'
+ label 'process_low_memory'
+
+ 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/e0/e00369598bd6b7b34a7c83d5496c381104bf8b885c31a4b65b92e6ea2059fbb3/data' :
+ 'community.wave.seqera.io/library/trim-galore:2.3.0--6a38a479b4972363'}"
+
+ input:
+ tuple val(meta), path(reads)
+
+ output:
+ tuple val(meta), path("*{3prime,5prime,trimmed,val}{,_1,_2}.fq.gz"), emit: reads
+ tuple val(meta), path("*report.txt") , emit: log , optional: true
+ tuple val(meta), path("*unpaired{,_1,_2}.fq.gz") , emit: unpaired, optional: true
+ tuple val(meta), path("*.html") , emit: html , optional: true
+ tuple val(meta), path("*.zip") , emit: zip , optional: true
+ tuple val("${task.process}"), val("trimgalore"), eval('trim_galore --version | grep -Eo "[0-9]+(\\.[0-9]+)+"'), topic: versions, emit: versions_trimgalore
+
+ when:
+ task.ext.when == null || task.ext.when
+
+ script:
+ def args = task.ext.args ?: ''
+ // Calculate number of --cores for TrimGalore based on value of task.cpus
+ // See: https://github.com/FelixKrueger/TrimGalore/blob/master/CHANGELOG.md#version-060-release-on-1-mar-2019
+ // See: https://github.com/nf-core/atacseq/pull/65
+ def cores = 1
+ if (task.cpus) {
+ cores = (task.cpus as int) - 4
+ if (meta.single_end) {
+ cores = (task.cpus as int) - 3
+ }
+ if (cores < 1) {
+ cores = 1
+ }
+ if (cores > 8) {
+ cores = 8
+ }
+ }
+
+ // Added soft-links to original fastqs for consistent naming in MultiQC
+ def prefix = task.ext.prefix ?: "${meta.id}"
+ if (meta.single_end) {
+ def args_list = args.split("\\s(?=--)").toList()
+ args_list.removeAll { arg -> arg.toLowerCase().contains('_r2 ') }
+ """
+ [ ! -f ${prefix}.fastq.gz ] && ln -s ${reads} ${prefix}.fastq.gz
+ trim_galore \\
+ ${args_list.join(' ')} \\
+ --cores ${cores} \\
+ --gzip \\
+ ${prefix}.fastq.gz
+ """
+ }
+ else {
+ """
+ [ ! -f ${prefix}_1.fastq.gz ] && ln -s ${reads[0]} ${prefix}_1.fastq.gz
+ [ ! -f ${prefix}_2.fastq.gz ] && ln -s ${reads[1]} ${prefix}_2.fastq.gz
+ trim_galore \\
+ ${args} \\
+ --cores ${cores} \\
+ --paired \\
+ --gzip \\
+ ${prefix}_1.fastq.gz \\
+ ${prefix}_2.fastq.gz
+ """
+ }
+
+ stub:
+ def prefix = task.ext.prefix ?: "${meta.id}"
+ if (meta.single_end) {
+ output_command = "echo '' | gzip > ${prefix}_trimmed.fq.gz ;"
+ output_command += "touch ${prefix}.fastq.gz_trimming_report.txt"
+ }
+ else {
+ output_command = "echo '' | gzip > ${prefix}_1_trimmed.fq.gz ;"
+ output_command += "touch ${prefix}_1.fastq.gz_trimming_report.txt ;"
+ output_command += "echo '' | gzip > ${prefix}_2_trimmed.fq.gz ;"
+ output_command += "touch ${prefix}_2.fastq.gz_trimming_report.txt"
+ }
+ """
+ ${output_command}
+ """
+}
diff --git a/modules/nf-core/trimgalore/meta.yml b/modules/nf-core/trimgalore/meta.yml
new file mode 100644
index 0000000..dcd9b17
--- /dev/null
+++ b/modules/nf-core/trimgalore/meta.yml
@@ -0,0 +1,129 @@
+name: trimgalore
+description: |
+ A wrapper around Cutadapt and FastQC to consistently apply adapter and quality trimming to FastQ files,
+ with extra functionality for RRBS data
+keywords:
+ - trimming
+ - adapters
+ - sequencing
+ - fastq
+tools:
+ - trimgalore:
+ description: |
+ A wrapper tool around Cutadapt and FastQC to consistently apply quality
+ and adapter trimming to FastQ files, with some extra functionality for
+ MspI-digested RRBS-type (Reduced Representation Bisufite-Seq) libraries.
+ homepage: https://www.bioinformatics.babraham.ac.uk/projects/trim_galore/
+ documentation: https://github.com/FelixKrueger/TrimGalore/blob/master/Docs/Trim_Galore_User_Guide.md
+ licence: ["GPL-3.0-or-later"]
+ identifier: biotools:trim_galore
+
+input:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - reads:
+ type: file
+ description: |
+ List of input FastQ files of size 1 and 2 for single-end and paired-end data,
+ respectively.
+ ontologies:
+ - edam: "http://edamontology.org/format_1930" # FASTQ
+output:
+ reads:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - "*{3prime,5prime,trimmed,val}{,_1,_2}.fq.gz":
+ type: file
+ description: The trimmed/modified fastq reads
+ pattern: "*{3prime,5prime,trimmed,val}{,_1,_2}.fq.gz"
+ ontologies:
+ - edam: "http://edamontology.org/format_1930" # FASTQ
+ - edam: http://edamontology.org/format_3989 # GZIP format
+ log:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - "*report.txt":
+ type: file
+ description: trimgalore log file
+ pattern: "*report.txt"
+ ontologies:
+ - edam: "http://edamontology.org/format_2330" # Textual format
+ unpaired:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - "*unpaired{,_1,_2}.fq.gz":
+ type: file
+ description: unpaired reads when --retain_unpaired flag is used
+ pattern: "*unpaired*.fq.gz"
+ ontologies:
+ - edam: "http://edamontology.org/format_1930" # FASTQ
+ - edam: http://edamontology.org/format_3989 # GZIP format
+ html:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - "*.html":
+ type: file
+ description: FastQC HTML report after trimming when the --fastqc flag is used
+ pattern: "*_fastqc.html"
+ ontologies:
+ - edam: "http://edamontology.org/format_2331" # HTML
+ zip:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - "*.zip":
+ type: file
+ description: FastQC report output zip after trimming when the --fastqc flag
+ is used
+ pattern: "*_fastqc.zip"
+ ontologies:
+ - edam: http://edamontology.org/format_3987 # ZIP format
+ versions_trimgalore:
+ - - ${task.process}:
+ type: string
+ description: The name of the process
+ - trimgalore:
+ type: string
+ description: The name of the tool
+ - trim_galore --version | grep -Eo "[0-9]+(\.[0-9]+)+":
+ type: eval
+ description: The expression to obtain the version of the tool
+
+topics:
+ versions:
+ - - ${task.process}:
+ type: string
+ description: The name of the process
+ - trimgalore:
+ type: string
+ description: The name of the tool
+ - trim_galore --version | grep -Eo "[0-9]+(\.[0-9]+)+":
+ type: eval
+ description: The expression to obtain the version of the tool
+
+authors:
+ - "@drpatelh"
+ - "@ewels"
+ - "@FelixKrueger"
+maintainers:
+ - "@drpatelh"
+ - "@ewels"
+ - "@FelixKrueger"
+ - "@vagkaratzas"
diff --git a/modules/nf-core/trimgalore/tests/main.nf.test b/modules/nf-core/trimgalore/tests/main.nf.test
new file mode 100644
index 0000000..11d68f4
--- /dev/null
+++ b/modules/nf-core/trimgalore/tests/main.nf.test
@@ -0,0 +1,154 @@
+nextflow_process {
+
+ name "Test Process TRIMGALORE"
+ script "../main.nf"
+ process "TRIMGALORE"
+
+ tag "modules"
+ tag "modules_nfcore"
+ tag "trimgalore"
+
+ test("sarscov2 - fastq - single-end") {
+
+ when {
+ process {
+ """
+ input[0] = [
+ [ id:'test', single_end:true ],
+ [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) ]
+ ]
+ """
+ }
+ }
+
+ then {
+ assertAll (
+ { assert process.success },
+ { assert snapshot(
+ process.out.reads,
+ path(process.out.log[0][1]).readLines().dropWhile { !it.startsWith("=== Summary") }.join('\n').md5(),
+ process.out.findAll { key, val -> key.startsWith("versions") }
+ ).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - paired-end") {
+
+ when {
+ process {
+ """
+ input[0] = [ [ id:'test', single_end:false ],
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ ]
+ ]
+ """
+ }
+ }
+
+ then {
+ assertAll (
+ { assert process.success },
+ { assert snapshot(
+ process.out.reads,
+ path(process.out.log[0][1][0]).readLines().dropWhile { !it.startsWith("=== Summary") }.join('\n').md5(),
+ path(process.out.log[0][1][1]).readLines().dropWhile { !it.startsWith("=== Summary") }.join('\n').md5(),
+ process.out.findAll { key, val -> key.startsWith("versions") }
+ ).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - paired-end - keep-unpaired") {
+
+ config "./nextflow.config"
+
+ when {
+ params {
+ module_args = '--retain_unpaired --length 150'
+ }
+
+ process {
+ """
+ input[0] = [ [ id:'test', single_end:false ],
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ ]
+ ]
+ """
+ }
+ }
+
+ then {
+ assertAll (
+ { assert process.success },
+ { assert snapshot(
+ process.out.reads,
+ process.out.unpaired,
+ path(process.out.log[0][1][0]).readLines().dropWhile { !it.startsWith("=== Summary") }.join('\n').md5(),
+ path(process.out.log[0][1][1]).readLines().dropWhile { !it.startsWith("=== Summary") }.join('\n').md5(),
+ process.out.findAll { key, val -> key.startsWith("versions") }
+ ).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - single-end - stub") {
+
+ options "-stub"
+
+ when {
+ process {
+ """
+ input[0] = [
+ [ id:'test', single_end:true ],
+ [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) ]
+ ]
+ """
+ }
+ }
+
+ then {
+ assertAll (
+ { assert process.success },
+ { assert snapshot(
+ process.out.reads,
+ process.out.log,
+ process.out.findAll { key, val -> key.startsWith("versions") }
+ ).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - paired-end - stub") {
+
+ options "-stub"
+
+ when {
+ process {
+ """
+ input[0] = [ [ id:'test', single_end:false ],
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ ]
+ ]
+ """
+ }
+ }
+
+ then {
+ assertAll (
+ { assert process.success },
+ { assert snapshot(
+ process.out.reads,
+ process.out.log,
+ process.out.findAll { key, val -> key.startsWith("versions") }
+ ).match() }
+ )
+ }
+ }
+}
diff --git a/modules/nf-core/trimgalore/tests/main.nf.test.snap b/modules/nf-core/trimgalore/tests/main.nf.test.snap
new file mode 100644
index 0000000..a909b56
--- /dev/null
+++ b/modules/nf-core/trimgalore/tests/main.nf.test.snap
@@ -0,0 +1,184 @@
+{
+ "sarscov2 - fastq - paired-end - stub": {
+ "content": [
+ [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test_1_trimmed.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940",
+ "test_2_trimmed.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ]
+ ],
+ [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test_1.fastq.gz_trimming_report.txt:md5,d41d8cd98f00b204e9800998ecf8427e",
+ "test_2.fastq.gz_trimming_report.txt:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ]
+ ],
+ {
+ "versions_trimgalore": [
+ [
+ "TRIMGALORE",
+ "trimgalore",
+ "2.3.0"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-05-05T08:48:10.832490464",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.0"
+ }
+ },
+ "sarscov2 - fastq - paired-end - keep-unpaired": {
+ "content": [
+ [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test_1_val_1.fq.gz:md5,75413e85910bbc2e1556e12f6479f935",
+ "test_2_val_2.fq.gz:md5,d3c588c12646ebd36a0812fe02d0bda6"
+ ]
+ ]
+ ],
+ [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test_1_unpaired_1.fq.gz:md5,17e0e878f6d0e93b9008a05f128660b6",
+ "test_2_unpaired_2.fq.gz:md5,b09a064368a867e099e66df5ef69b044"
+ ]
+ ]
+ ],
+ "4cbef464fbc09252a1282078ec23942e",
+ "ee1b8a371f1490e5faf5e346523b8886",
+ {
+ "versions_trimgalore": [
+ [
+ "TRIMGALORE",
+ "trimgalore",
+ "2.3.0"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-05-05T09:34:57.668635539",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.0"
+ }
+ },
+ "sarscov2 - fastq - single-end - stub": {
+ "content": [
+ [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test_trimmed.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastq.gz_trimming_report.txt:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ {
+ "versions_trimgalore": [
+ [
+ "TRIMGALORE",
+ "trimgalore",
+ "2.3.0"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-05-05T08:48:05.803691794",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.0"
+ }
+ },
+ "sarscov2 - fastq - paired-end": {
+ "content": [
+ [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test_1_val_1.fq.gz:md5,566d44cca0d22c522d6cf0e50c7165dc",
+ "test_2_val_2.fq.gz:md5,3c023e8e890b897821df3dc98f48c2b3"
+ ]
+ ]
+ ],
+ "4da0165f1c5a0d3f1d5eea9ad17fb1fb",
+ "808929d2f70ffea0ddd7750bbb400b02",
+ {
+ "versions_trimgalore": [
+ [
+ "TRIMGALORE",
+ "trimgalore",
+ "2.3.0"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-05-05T09:34:52.551563029",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.0"
+ }
+ },
+ "sarscov2 - fastq - single-end": {
+ "content": [
+ [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test_trimmed.fq.gz:md5,566d44cca0d22c522d6cf0e50c7165dc"
+ ]
+ ],
+ "76dd6b9d009e332d2aa6443de525c182",
+ {
+ "versions_trimgalore": [
+ [
+ "TRIMGALORE",
+ "trimgalore",
+ "2.3.0"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-05-05T09:34:47.273738299",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.0"
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/nf-core/trimgalore/tests/nextflow.config b/modules/nf-core/trimgalore/tests/nextflow.config
new file mode 100644
index 0000000..d8e3ac1
--- /dev/null
+++ b/modules/nf-core/trimgalore/tests/nextflow.config
@@ -0,0 +1,5 @@
+process {
+ withName: TRIMGALORE {
+ ext.args = params.module_args
+ }
+}
diff --git a/modules/nf-core/trimmomatic/environment.yml b/modules/nf-core/trimmomatic/environment.yml
new file mode 100644
index 0000000..994bfd5
--- /dev/null
+++ b/modules/nf-core/trimmomatic/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::trimmomatic=0.39
diff --git a/modules/nf-core/trimmomatic/main.nf b/modules/nf-core/trimmomatic/main.nf
new file mode 100644
index 0000000..3b2d416
--- /dev/null
+++ b/modules/nf-core/trimmomatic/main.nf
@@ -0,0 +1,63 @@
+process TRIMMOMATIC {
+ tag "$meta.id"
+ label 'process_medium'
+
+ conda "${moduleDir}/environment.yml"
+ container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ?
+ 'https://depot.galaxyproject.org/singularity/trimmomatic:0.39--hdfd78af_2':
+ 'quay.io/biocontainers/trimmomatic:0.39--hdfd78af_2' }"
+
+ input:
+ tuple val(meta), path(reads)
+
+ output:
+ tuple val(meta), path("*.paired.trim*.fastq.gz") , emit: trimmed_reads
+ tuple val(meta), path("*.unpaired.trim_*.fastq.gz"), emit: unpaired_reads, optional:true
+ tuple val(meta), path("*_trim.log") , emit: trim_log
+ tuple val(meta), path("*_out.log") , emit: out_log
+ tuple val(meta), path("*.summary") , emit: summary
+ tuple val("${task.process}"), val('trimmomatic'), eval("trimmomatic -version"), topic: versions, emit: versions_trimmomatic
+
+ when:
+ task.ext.when == null || task.ext.when
+
+ script:
+ def args = task.ext.args ?: ''
+ def prefix = task.ext.prefix ?: "${meta.id}"
+ def trimmed = meta.single_end ? "SE" : "PE"
+ def output = meta.single_end ?
+ "${prefix}.SE.paired.trim.fastq.gz" // HACK to avoid unpaired and paired in the trimmed_reads output
+ : "${prefix}.paired.trim_1.fastq.gz ${prefix}.unpaired.trim_1.fastq.gz ${prefix}.paired.trim_2.fastq.gz ${prefix}.unpaired.trim_2.fastq.gz"
+ def qual_trim = task.ext.args2 ?: ''
+ """
+ trimmomatic \\
+ $trimmed \\
+ -threads $task.cpus \\
+ -trimlog ${prefix}_trim.log \\
+ -summary ${prefix}.summary \\
+ $reads \\
+ $output \\
+ $qual_trim \\
+ $args 2>| >(tee ${prefix}_out.log >&2)
+ """
+
+ stub:
+ def prefix = task.ext.prefix ?: "${meta.id}"
+
+ if (meta.single_end) {
+ output_command = "echo '' | gzip > ${prefix}.SE.paired.trim.fastq.gz"
+ } else {
+ output_command = "echo '' | gzip > ${prefix}.paired.trim_1.fastq.gz\n"
+ output_command += "echo '' | gzip > ${prefix}.paired.trim_2.fastq.gz\n"
+ output_command += "echo '' | gzip > ${prefix}.unpaired.trim_1.fastq.gz\n"
+ output_command += "echo '' | gzip > ${prefix}.unpaired.trim_2.fastq.gz"
+ }
+
+ """
+ $output_command
+ touch ${prefix}.summary
+ touch ${prefix}_trim.log
+ touch ${prefix}_out.log
+ """
+
+}
diff --git a/modules/nf-core/trimmomatic/meta.yml b/modules/nf-core/trimmomatic/meta.yml
new file mode 100644
index 0000000..7e7839c
--- /dev/null
+++ b/modules/nf-core/trimmomatic/meta.yml
@@ -0,0 +1,112 @@
+name: "trimmomatic"
+description: Performs quality and adapter trimming on paired end and single end
+ reads
+keywords:
+ - trimming
+ - adapter trimming
+ - quality trimming
+tools:
+ - "trimmomatic":
+ description: "A flexible read trimming tool for Illumina NGS data"
+ homepage: "http://www.usadellab.org/cms/?page=trimmomatic"
+ documentation: "https://github.com/usadellab/Trimmomatic"
+ doi: "10.1093/bioinformatics/btu170"
+ licence:
+ - "GPL v3"
+ identifier: biotools:trimmomatic
+input:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - reads:
+ type: file
+ description: |
+ Input FastQ files of size 1 or 2 for single-end and paired-end data, respectively.
+ pattern: "*.fastq.gz"
+ ontologies:
+ - edam: http://edamontology.org/format_3989 # GZIP format
+output:
+ trimmed_reads:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - "*.paired.trim*.fastq.gz":
+ type: file
+ description: The trimmed/modified paired end fastq reads
+ pattern: "*.paired.trim*.fastq.gz"
+ ontologies:
+ - edam: http://edamontology.org/format_3989 # GZIP format
+ unpaired_reads:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - "*.unpaired.trim_*.fastq.gz":
+ type: file
+ description: The trimmed/modified unpaired end fastq reads
+ pattern: "*.unpaired.trim_*.fastq.gz"
+ ontologies:
+ - edam: http://edamontology.org/format_3989 # GZIP format
+ trim_log:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - "*_trim.log":
+ type: file
+ description: trimmomatic log file, from the trim_log parameter
+ pattern: "*.log"
+ ontologies: []
+ out_log:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - "*_out.log":
+ type: file
+ description: log of output from the standard out
+ pattern: "*.log"
+ ontologies: []
+ summary:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - "*.summary":
+ type: file
+ description: trimmomatic summary file of surviving and dropped reads
+ pattern: "*.summary"
+ ontologies: []
+ versions_trimmomatic:
+ - - ${task.process}:
+ type: string
+ description: The name of the process
+ - trimmomatic:
+ type: string
+ description: The name of the tool
+ - trimmomatic -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
+ - trimmomatic:
+ type: string
+ description: The name of the tool
+ - trimmomatic -version:
+ type: eval
+ description: The expression to obtain the version of the tool
+authors:
+ - "@alyssa-ab"
+maintainers:
+ - "@alyssa-ab"
diff --git a/modules/nf-core/trimmomatic/tests/main.nf.test b/modules/nf-core/trimmomatic/tests/main.nf.test
new file mode 100644
index 0000000..ce4a298
--- /dev/null
+++ b/modules/nf-core/trimmomatic/tests/main.nf.test
@@ -0,0 +1,111 @@
+nextflow_process {
+
+ name "Test Process TRIMMOMATIC"
+ script "../main.nf"
+ process "TRIMMOMATIC"
+ tag "modules"
+ tag "modules_nfcore"
+ tag "trimmomatic"
+
+ test("Single-Read") {
+ config "./nextflow_SE.config"
+ when {
+ process {
+ """
+ input[0] = [
+ [ id: 'test', single_end:true ],
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ]
+ ]
+ """
+ }
+ }
+
+ then {
+ assertAll (
+ { assert process.success },
+ { assert snapshot(process.out.trimmed_reads,
+ process.out.summary,
+ file(process.out.trim_log[0][1]).name,
+ process.out.findAll { key, val -> key.startsWith("versions")}
+ ).match() }
+ )
+ }
+ }
+
+ test("Paired-Reads") {
+ config "./nextflow_PE.config"
+ when {
+ process {
+ """
+ input[0] = [
+ [ id: 'test', single_end:false ],
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ ]
+ ]
+ """
+ }
+ }
+
+ then {
+ assertAll (
+ { assert process.success },
+ { assert snapshot(process.out.trimmed_reads,
+ process.out.summary,
+ file(process.out.trim_log[0][1]).name,
+ process.out.findAll { key, val -> key.startsWith("versions")}
+ ).match() }
+ )
+ }
+ }
+
+ test("No Adaptors") {
+
+ when {
+ process {
+ """
+ input[0] = [
+ [ id: 'test', single_end:false ],
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ ]
+ ]
+ """
+ }
+ }
+
+ then {
+ assertAll (
+ { assert process.failed }
+ )
+ }
+ }
+
+ test("Single-Read - stub") {
+ options "-stub"
+ when {
+ process {
+ """
+ input[0] = [
+ [ id: 'test', single_end:true ],
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ]
+ ]
+ """
+ }
+ }
+
+ then {
+ assertAll (
+ { assert process.success },
+ { assert snapshot(process.out).match() }
+ )
+ }
+ }
+
+}
diff --git a/modules/nf-core/trimmomatic/tests/main.nf.test.snap b/modules/nf-core/trimmomatic/tests/main.nf.test.snap
new file mode 100644
index 0000000..d47ee14
--- /dev/null
+++ b/modules/nf-core/trimmomatic/tests/main.nf.test.snap
@@ -0,0 +1,182 @@
+{
+ "Single-Read - stub": {
+ "content": [
+ {
+ "0": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.SE.paired.trim.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "1": [
+
+ ],
+ "2": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test_trim.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "3": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test_out.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "4": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.summary:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "5": [
+ [
+ "TRIMMOMATIC",
+ "trimmomatic",
+ "0.39"
+ ]
+ ],
+ "out_log": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test_out.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "summary": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.summary:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "trim_log": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test_trim.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "trimmed_reads": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.SE.paired.trim.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "unpaired_reads": [
+
+ ],
+ "versions_trimmomatic": [
+ [
+ "TRIMMOMATIC",
+ "trimmomatic",
+ "0.39"
+ ]
+ ]
+ }
+ ],
+ "meta": {
+ "nf-test": "0.9.3",
+ "nextflow": "25.10.3"
+ },
+ "timestamp": "2026-02-11T11:27:00.107996"
+ },
+ "Single-Read": {
+ "content": [
+ [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.SE.paired.trim.fastq.gz:md5,e68abbd3b88f7ec12940a4f5c2b8bfb9"
+ ]
+ ],
+ [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.summary:md5,24c973237557a1439c775ca19a5deaa5"
+ ]
+ ],
+ "test_trim.log",
+ {
+ "versions_trimmomatic": [
+ [
+ "TRIMMOMATIC",
+ "trimmomatic",
+ "0.39"
+ ]
+ ]
+ }
+ ],
+ "meta": {
+ "nf-test": "0.9.3",
+ "nextflow": "25.10.3"
+ },
+ "timestamp": "2026-02-11T12:21:46.59093"
+ },
+ "Paired-Reads": {
+ "content": [
+ [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test.paired.trim_1.fastq.gz:md5,a866e64f451745f176316d0df7d52b30",
+ "test.paired.trim_2.fastq.gz:md5,725d4ab909b39291ab56b090cab32075"
+ ]
+ ]
+ ],
+ [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.summary:md5,9698e5e5c060bbe64588998fe35f8d71"
+ ]
+ ],
+ "test_trim.log",
+ {
+ "versions_trimmomatic": [
+ [
+ "TRIMMOMATIC",
+ "trimmomatic",
+ "0.39"
+ ]
+ ]
+ }
+ ],
+ "meta": {
+ "nf-test": "0.9.3",
+ "nextflow": "25.10.3"
+ },
+ "timestamp": "2026-02-11T12:21:53.926135"
+ }
+}
\ No newline at end of file
diff --git a/modules/nf-core/trimmomatic/tests/nextflow_PE.config b/modules/nf-core/trimmomatic/tests/nextflow_PE.config
new file mode 100644
index 0000000..3f8fdfe
--- /dev/null
+++ b/modules/nf-core/trimmomatic/tests/nextflow_PE.config
@@ -0,0 +1,6 @@
+process {
+
+ withName: TRIMMOMATIC {
+ ext.args = 'ILLUMINACLIP:TruSeq3-PE.fa:2:30:10 LEADING:3 TRAILING:3 SLIDINGWINDOW:4:15 MINLEN:36'
+ }
+}
diff --git a/modules/nf-core/trimmomatic/tests/nextflow_SE.config b/modules/nf-core/trimmomatic/tests/nextflow_SE.config
new file mode 100644
index 0000000..af777fd
--- /dev/null
+++ b/modules/nf-core/trimmomatic/tests/nextflow_SE.config
@@ -0,0 +1,6 @@
+process {
+
+ withName: TRIMMOMATIC {
+ ext.args = 'ILLUMINACLIP:TruSeq3-SE:2:30:10 LEADING:3 TRAILING:3 SLIDINGWINDOW:4:15 MINLEN:36'
+ }
+}
diff --git a/modules/nf-core/umitools/extract/environment.yml b/modules/nf-core/umitools/extract/environment.yml
new file mode 100644
index 0000000..4d05188
--- /dev/null
+++ b/modules/nf-core/umitools/extract/environment.yml
@@ -0,0 +1,17 @@
+---
+# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json
+channels:
+ - conda-forge
+ - bioconda
+dependencies:
+ # renovate: datasource=conda depName=bioconda/umi_tools
+ - bioconda::umi_tools=1.1.6
+ - conda-forge::future=0.18.3
+ - conda-forge::matplotlib=3.8.2
+ - conda-forge::numpy=1.26.4
+ - conda-forge::pandas=2.2.0
+ - conda-forge::python=3.9.18
+ - conda-forge::regex=2023.12.25
+ - conda-forge::scipy=1.12.0
+ - conda-forge::pybktree=1.1
+ - bioconda::pysam=0.22.0
diff --git a/modules/nf-core/umitools/extract/main.nf b/modules/nf-core/umitools/extract/main.nf
new file mode 100644
index 0000000..0aafe59
--- /dev/null
+++ b/modules/nf-core/umitools/extract/main.nf
@@ -0,0 +1,59 @@
+process UMITOOLS_EXTRACT {
+ tag "$meta.id"
+ label "process_single"
+ label "process_long"
+
+ 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/32/32476f0107d72dbd2210a4e56b2873abde07300025cc11052680475509d2db81/data' :
+ 'community.wave.seqera.io/library/umi_tools_future_matplotlib_numpy_pruned:1ee668bafc8c9f81' }"
+
+ input:
+ tuple val(meta), path(reads)
+
+ output:
+ tuple val(meta), path("*.fastq.gz"), emit: reads
+ tuple val(meta), path("*.log") , emit: log
+ tuple val("${task.process}"), val('umitools'), eval("umi_tools --version | sed -n '/version:/s/.*: //p'"), emit: versions_umitools, topic: versions
+
+ when:
+ task.ext.when == null || task.ext.when
+
+ script:
+ def args = task.ext.args ?: ''
+ def prefix = task.ext.prefix ?: "${meta.id}"
+ if (meta.single_end) {
+ """
+ umi_tools \\
+ extract \\
+ -I $reads \\
+ -S ${prefix}.umi_extract.fastq.gz \\
+ $args \\
+ > ${prefix}.umi_extract.log
+ """
+ } else {
+ """
+ umi_tools \\
+ extract \\
+ -I ${reads[0]} \\
+ --read2-in=${reads[1]} \\
+ -S ${prefix}.umi_extract_1.fastq.gz \\
+ --read2-out=${prefix}.umi_extract_2.fastq.gz \\
+ $args \\
+ > ${prefix}.umi_extract.log
+ """
+ }
+
+ stub:
+ def prefix = task.ext.prefix ?: "${meta.id}"
+ if (meta.single_end) {
+ output_command = "echo '' | gzip > ${prefix}.umi_extract.fastq.gz"
+ } else {
+ output_command = "echo '' | gzip > ${prefix}.umi_extract_1.fastq.gz ;"
+ output_command += "echo '' | gzip > ${prefix}.umi_extract_2.fastq.gz"
+ }
+ """
+ touch ${prefix}.umi_extract.log
+ ${output_command}
+ """
+}
diff --git a/modules/nf-core/umitools/extract/meta.yml b/modules/nf-core/umitools/extract/meta.yml
new file mode 100644
index 0000000..0bec392
--- /dev/null
+++ b/modules/nf-core/umitools/extract/meta.yml
@@ -0,0 +1,80 @@
+name: umitools_extract
+description: Extracts UMI barcode from a read and add it to the read name, leaving
+ any sample barcode in place
+keywords:
+ - UMI
+ - barcode
+ - extract
+ - umitools
+tools:
+ - umi_tools:
+ description: >
+ UMI-tools contains tools for dealing with Unique Molecular Identifiers (UMIs)/Random
+ Molecular Tags (RMTs) and single cell RNA-Seq cell barcodes
+ documentation: https://umi-tools.readthedocs.io/en/latest/
+ license: "MIT"
+ identifier: ""
+input:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - reads:
+ type: list
+ description: |
+ List of input FASTQ files whose UMIs will be extracted.
+output:
+ reads:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - "*.fastq.gz":
+ type: file
+ description: >
+ Extracted FASTQ files. | For single-end reads, pattern is \${prefix}.umi_extract.fastq.gz.
+ | For paired-end reads, pattern is \${prefix}.umi_extract_{1,2}.fastq.gz.
+ pattern: "*.{fastq.gz}"
+ ontologies: []
+ log:
+ - - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. [ id:'test', single_end:false ]
+ - "*.log":
+ type: file
+ description: Logfile for umi_tools
+ pattern: "*.{log}"
+ ontologies: []
+ versions_umitools:
+ - - ${task.process}:
+ type: string
+ description: The name of the process
+ - umitools:
+ type: string
+ description: The name of the tool
+ - "umi_tools --version | sed -n '/version:/s/.*: //p'":
+ type: eval
+ description: The expression to obtain the version of umitools
+
+topics:
+ versions:
+ - - ${task.process}:
+ type: string
+ description: The name of the process
+ - umitools:
+ type: string
+ description: The name of the tool
+ - "umi_tools --version | sed -n '/version:/s/.*: //p'":
+ type: eval
+ description: The expression to obtain the version of umitools
+
+authors:
+ - "@drpatelh"
+ - "@grst"
+maintainers:
+ - "@drpatelh"
+ - "@grst"
diff --git a/modules/nf-core/umitools/extract/tests/main.nf.test b/modules/nf-core/umitools/extract/tests/main.nf.test
new file mode 100644
index 0000000..b6fa048
--- /dev/null
+++ b/modules/nf-core/umitools/extract/tests/main.nf.test
@@ -0,0 +1,106 @@
+nextflow_process {
+
+ name "Test Process UMITOOLS_EXTRACT"
+ script "../main.nf"
+ process "UMITOOLS_EXTRACT"
+ config "./nextflow.config"
+ tag "modules_nfcore"
+ tag "modules"
+ tag "umitools"
+ tag "umitools/extract"
+
+ test("single end") {
+
+ when {
+ process {
+ """
+ input[0] = [ [ id:'test', single_end:true ], // meta map
+ [ file(params.modules_testdata_base_path + "genomics/sarscov2/illumina/fastq/test_1.fastq.gz", checkIfExists: true) ]
+ ]
+ """
+ }
+ }
+
+ then {
+ assertAll (
+ { assert process.success },
+ { assert snapshot(
+ process.out.reads.collect { item -> item.collect { element -> element instanceof Map ? element : file(element).name }},
+ process.out.log.collect { item -> item.collect { element -> element instanceof Map ? element : file(element).name }},
+ process.out.findAll { key, val -> key.startsWith('versions') }
+ ).match() }
+ )
+ }
+ }
+
+ test("single end - stub") {
+
+ options "-stub"
+
+ when {
+ process {
+ """
+ input[0] = [ [ id:'test', single_end:true ], // meta map
+ [ file(params.modules_testdata_base_path + "genomics/sarscov2/illumina/fastq/test_1.fastq.gz", checkIfExists: true) ]
+ ]
+ """
+ }
+ }
+
+ then {
+ assertAll (
+ { assert process.success },
+ { assert snapshot(process.out).match() }
+ )
+ }
+ }
+
+ test("pair end") {
+
+ when {
+ process {
+ """
+ input[0] = [ [ id:'test', single_end:false ], // meta map
+ [ file(params.modules_testdata_base_path + "genomics/sarscov2/illumina/fastq/test_1.fastq.gz", checkIfExists: true),
+ file(params.modules_testdata_base_path + "genomics/sarscov2/illumina/fastq/test_2.fastq.gz", checkIfExists: true) ]
+ ]
+ """
+ }
+ }
+
+ then {
+ assertAll (
+ { assert process.success },
+ { assert snapshot(
+ file(process.out.reads[0][1][0]).name,
+ file(process.out.reads[0][1][1]).name,
+ process.out.log.collect { item -> item.collect { element -> element instanceof Map ? element : file(element).name }},
+ process.out.findAll { key, val -> key.startsWith('versions') }
+ ).match() }
+ )
+ }
+ }
+
+ test("pair end - stub") {
+
+ options "-stub"
+
+ when {
+ process {
+ """
+ input[0] = [ [ id:'test', single_end:false ], // meta map
+ [ file(params.modules_testdata_base_path + "genomics/sarscov2/illumina/fastq/test_1.fastq.gz", checkIfExists: true),
+ file(params.modules_testdata_base_path + "genomics/sarscov2/illumina/fastq/test_2.fastq.gz", checkIfExists: true) ]
+ ]
+ """
+ }
+ }
+
+ then {
+ assertAll (
+ { assert process.success },
+ { assert snapshot(process.out).match() }
+ )
+ }
+ }
+}
diff --git a/modules/nf-core/umitools/extract/tests/main.nf.test.snap b/modules/nf-core/umitools/extract/tests/main.nf.test.snap
new file mode 100644
index 0000000..9162342
--- /dev/null
+++ b/modules/nf-core/umitools/extract/tests/main.nf.test.snap
@@ -0,0 +1,195 @@
+{
+ "pair end - stub": {
+ "content": [
+ {
+ "0": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test.umi_extract_1.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940",
+ "test.umi_extract_2.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ]
+ ],
+ "1": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.umi_extract.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "2": [
+ [
+ "UMITOOLS_EXTRACT",
+ "umitools",
+ "1.1.6"
+ ]
+ ],
+ "log": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.umi_extract.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "reads": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test.umi_extract_1.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940",
+ "test.umi_extract_2.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ]
+ ],
+ "versions_umitools": [
+ [
+ "UMITOOLS_EXTRACT",
+ "umitools",
+ "1.1.6"
+ ]
+ ]
+ }
+ ],
+ "meta": {
+ "nf-test": "0.9.3",
+ "nextflow": "25.10.3"
+ },
+ "timestamp": "2026-02-03T13:17:55.612739621"
+ },
+ "single end - stub": {
+ "content": [
+ {
+ "0": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.umi_extract.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "1": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.umi_extract.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "2": [
+ [
+ "UMITOOLS_EXTRACT",
+ "umitools",
+ "1.1.6"
+ ]
+ ],
+ "log": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.umi_extract.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "reads": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.umi_extract.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "versions_umitools": [
+ [
+ "UMITOOLS_EXTRACT",
+ "umitools",
+ "1.1.6"
+ ]
+ ]
+ }
+ ],
+ "meta": {
+ "nf-test": "0.9.3",
+ "nextflow": "25.10.3"
+ },
+ "timestamp": "2026-02-03T13:17:37.310716092"
+ },
+ "pair end": {
+ "content": [
+ "test.umi_extract_1.fastq.gz",
+ "test.umi_extract_2.fastq.gz",
+ [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.umi_extract.log"
+ ]
+ ],
+ {
+ "versions_umitools": [
+ [
+ "UMITOOLS_EXTRACT",
+ "umitools",
+ "1.1.6"
+ ]
+ ]
+ }
+ ],
+ "meta": {
+ "nf-test": "0.9.3",
+ "nextflow": "25.10.3"
+ },
+ "timestamp": "2026-02-03T13:17:47.683802227"
+ },
+ "single end": {
+ "content": [
+ [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.umi_extract.fastq.gz"
+ ]
+ ],
+ [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.umi_extract.log"
+ ]
+ ],
+ {
+ "versions_umitools": [
+ [
+ "UMITOOLS_EXTRACT",
+ "umitools",
+ "1.1.6"
+ ]
+ ]
+ }
+ ],
+ "meta": {
+ "nf-test": "0.9.3",
+ "nextflow": "25.10.3"
+ },
+ "timestamp": "2026-02-03T13:17:29.607052009"
+ }
+}
\ No newline at end of file
diff --git a/modules/nf-core/umitools/extract/tests/nextflow.config b/modules/nf-core/umitools/extract/tests/nextflow.config
new file mode 100644
index 0000000..628f5fc
--- /dev/null
+++ b/modules/nf-core/umitools/extract/tests/nextflow.config
@@ -0,0 +1,9 @@
+process {
+
+ publishDir = { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" }
+
+ withName: UMITOOLS_EXTRACT {
+ ext.args = '--bc-pattern="NNNN"'
+ }
+
+}
diff --git a/nextflow.config b/nextflow.config
index 6344e60..d03d664 100644
--- a/nextflow.config
+++ b/nextflow.config
@@ -52,8 +52,29 @@ params {
save_untarred_databases = false
// FASTQ preprocessing
- skip_preprocessing_qc = false
- save_runmerged_reads = false
+ skip_preprocessing_qc = false
+ save_runmerged_reads = false
+ save_preprocessed_reads = false
+
+ // Short read adapter trimming / quality filtering
+ perform_shortread_qc = false
+ shortread_qc_tool = 'fastp'
+ shortread_qc_skipadaptertrim = false
+ shortread_qc_adapterlist = null
+ shortread_qc_mergepairs = false
+ shortread_qc_dedup = false
+ shortread_qc_savetrimmedfail = false
+
+ // Short read complexity filtering
+ perform_shortread_complexityfilter = false
+ shortread_complexityfilter_tool = 'bbduk'
+
+ // Short read host removal
+ perform_shortread_hostremoval = false
+ shortread_hostremoval_tool = 'hostile'
+ shortread_hostremoval_reference = null
+ shortread_hostremoval_index = null
+ shortread_hostremoval_index_name = null
// RGI
run_rgi = false
diff --git a/nextflow_schema.json b/nextflow_schema.json
index f979414..e1e0924 100644
--- a/nextflow_schema.json
+++ b/nextflow_schema.json
@@ -61,15 +61,144 @@
}
},
"preprocessing_qc_options": {
- "title": "Preprocessing QC options",
+ "title": "Preprocessing general options",
"type": "object",
- "description": "",
+ "description": "Options that apply to read preprocessing as a whole",
"default": "",
"properties": {
"skip_preprocessing_qc": {
"type": "boolean",
"description": "Skip quality control step when preprocessing",
"help_text": "When True, this won't perform any of the quality control steps in preprocessing, such as discarding low-quality reads."
+ },
+ "save_preprocessed_reads": {
+ "type": "boolean",
+ "fa_icon": "fas fa-save",
+ "description": "Save reads from adapter clipping/pair-merging, length filtering, complexity filtering and/or host-removal",
+ "help_text": "Saves the FASTQ files of every preprocessing step that ran, so you can inspect what each step removed. Files are published to `
/preprocessing/`.\n\nOnly steps that actually ran produce output, so enabling this without enabling any of `--perform_shortread_qc`, `--perform_shortread_complexityfilter` or `--perform_shortread_hostremoval` saves nothing."
+ }
+ },
+ "fa_icon": "fas fa-users-cog"
+ },
+ "preprocessing_shortread_qc_options": {
+ "title": "Preprocessing short-read QC options",
+ "type": "object",
+ "description": "Options for adapter clipping, quality trimming, pair-merging and deduplication of short reads",
+ "default": "",
+ "fa_icon": "fas fa-cut",
+ "properties": {
+ "perform_shortread_qc": {
+ "type": "boolean",
+ "fa_icon": "fas fa-toolbox",
+ "description": "Turn on short-read adapter clipping and quality trimming",
+ "help_text": "Runs the `fastq_shortreads_preprocess_qc` nf-core subworkflow on every short-read library before run merging. The tool is selected with `--shortread_qc_tool`."
+ },
+ "shortread_qc_tool": {
+ "type": "string",
+ "default": "fastp",
+ "enum": ["fastp", "adapterremoval", "trimmomatic", "cutadapt", "trimgalore", "bbduk", "leehom"],
+ "fa_icon": "fas fa-tools",
+ "description": "Specify which tool to use for short-read QC"
+ },
+ "shortread_qc_skipadaptertrim": {
+ "type": "boolean",
+ "fa_icon": "fas fa-forward",
+ "description": "Skip adapter trimming",
+ "help_text": "Turns off adapter removal while keeping the other operations of the selected tool, e.g. quality trimming or pair merging. Useful when the reads have already been adapter-clipped."
+ },
+ "shortread_qc_adapterlist": {
+ "type": "string",
+ "format": "file-path",
+ "exists": true,
+ "fa_icon": "fas fa-list",
+ "description": "Specify a list of adapter sequences to be removed",
+ "help_text": "A file of adapter sequences to clip instead of the tool's built-in defaults. The expected format depends on the tool: fastp and Trimmomatic take FASTA, AdapterRemoval takes a two-column (paired-end) or one-column (single-end) text file."
+ },
+ "shortread_qc_mergepairs": {
+ "type": "boolean",
+ "fa_icon": "fas fa-object-group",
+ "description": "Turn on merging of read pairs",
+ "help_text": "Collapses overlapping read pairs into single reads. Note that funcprofiler concatenates R1 and R2 for most profilers anyway, so this mainly matters for the RGI path, which consumes the paired channel."
+ },
+ "shortread_qc_dedup": {
+ "type": "boolean",
+ "fa_icon": "fas fa-clone",
+ "description": "Turn on deduplication of reads with Clumpify",
+ "help_text": "Removes exact and near-duplicate reads with BBMap's Clumpify (`dedupe=t`) after adapter clipping."
+ },
+ "shortread_qc_savetrimmedfail": {
+ "type": "boolean",
+ "fa_icon": "fas fa-save",
+ "description": "Save reads that failed the trimming/filtering thresholds"
+ }
+ }
+ },
+ "preprocessing_shortread_complexityfilter_options": {
+ "title": "Preprocessing short-read complexity filtering options",
+ "type": "object",
+ "description": "Options for removing low-complexity reads",
+ "default": "",
+ "fa_icon": "fas fa-filter",
+ "properties": {
+ "perform_shortread_complexityfilter": {
+ "type": "boolean",
+ "fa_icon": "fas fa-toolbox",
+ "description": "Turn on removal of low-complexity reads",
+ "help_text": "Low-complexity reads (homopolymers, short tandem repeats) inflate spurious hits in translated search. Removing them cuts both runtime and false positives."
+ },
+ "shortread_complexityfilter_tool": {
+ "type": "string",
+ "default": "bbduk",
+ "enum": ["bbduk", "prinseqplusplus", "fastp"],
+ "fa_icon": "fas fa-tools",
+ "description": "Specify which tool to use for complexity filtering",
+ "help_text": "Thresholds are not exposed as params. Override them through `ext.args` in `conf/modules.config`, e.g. `entropy=` for BBDuk or `--low_complexity_filter --complexity_threshold` for fastp."
+ }
+ }
+ },
+ "preprocessing_hostremoval_options": {
+ "title": "Preprocessing host-removal options",
+ "type": "object",
+ "description": "Options for removing host-derived reads",
+ "default": "",
+ "fa_icon": "fas fa-user-times",
+ "properties": {
+ "perform_shortread_hostremoval": {
+ "type": "boolean",
+ "fa_icon": "fas fa-toolbox",
+ "description": "Turn on removal of host-derived reads",
+ "help_text": "Requires either `--shortread_hostremoval_reference`, `--shortread_hostremoval_index` or `--shortread_hostremoval_index_name`, depending on the selected tool."
+ },
+ "shortread_hostremoval_tool": {
+ "type": "string",
+ "default": "hostile",
+ "enum": ["hostile", "deacon"],
+ "fa_icon": "fas fa-tools",
+ "description": "Specify which tool to use for host removal",
+ "help_text": "Hostile fetches and caches its own reference indexes by name; Deacon builds a minimiser index from a FASTA reference."
+ },
+ "shortread_hostremoval_reference": {
+ "type": "string",
+ "format": "file-path",
+ "exists": true,
+ "mimetype": "text/plain",
+ "fa_icon": "fas fa-file-alt",
+ "description": "Specify a reference genome FASTA to remove reads against",
+ "help_text": "Used by Deacon to build a filtering index. Ignored when a pre-built index is supplied with `--shortread_hostremoval_index`."
+ },
+ "shortread_hostremoval_index": {
+ "type": "string",
+ "format": "path",
+ "exists": true,
+ "fa_icon": "fas fa-database",
+ "description": "Specify a pre-built host-removal index",
+ "help_text": "Skips index construction. The expected format depends on the tool: a Deacon `.idx` file, or a Hostile index directory."
+ },
+ "shortread_hostremoval_index_name": {
+ "type": "string",
+ "fa_icon": "fas fa-tag",
+ "description": "Specify the name of a Hostile-hosted index to download",
+ "help_text": "Hostile resolves the name against its own index registry and caches the download, e.g. `human-t2t-hla`. Only used when `--shortread_hostremoval_tool hostile`."
}
}
},
@@ -287,6 +416,15 @@
{
"$ref": "#/$defs/preprocessing_qc_options"
},
+ {
+ "$ref": "#/$defs/preprocessing_shortread_qc_options"
+ },
+ {
+ "$ref": "#/$defs/preprocessing_shortread_complexityfilter_options"
+ },
+ {
+ "$ref": "#/$defs/preprocessing_hostremoval_options"
+ },
{
"$ref": "#/$defs/preprocessing_run_merging_options"
},
diff --git a/subworkflows/local/dataprep/main.nf b/subworkflows/local/dataprep/main.nf
index 971ac2a..977006e 100644
--- a/subworkflows/local/dataprep/main.nf
+++ b/subworkflows/local/dataprep/main.nf
@@ -1,5 +1,6 @@
include { CAT_FASTQ as MERGE_RUNS } from '../../../modules/nf-core/cat/fastq/main'
include { CAT_FASTQ } from '../../../modules/nf-core/cat/fastq/main'
+include { FASTQ_SHORTREADS_PREPROCESS_QC } from '../../nf-core/fastq_shortreads_preprocess_qc/main'
workflow DATAPREP {
@@ -34,8 +35,59 @@ workflow DATAPREP {
return [meta, reads]
}
+ // Step 1b: Optional preprocessing and read QC, per run.
+ // This runs before the run merging below so that each run is trimmed against its own
+ // adapters and quality profile, which is the order nf-core/taxprofiler uses. The
+ // subworkflow can concatenate runs itself, but MERGE_RUNS already does that here, so
+ // its final concatenation stays switched off.
+ ch_multiqc_files = Channel.empty()
+ def run_preprocessing = !params.skip_preprocessing_qc || params.perform_shortread_qc || params.perform_shortread_complexityfilter || params.perform_shortread_hostremoval
+
+ if (run_preprocessing) {
+ FASTQ_SHORTREADS_PREPROCESS_QC(
+ ch_validated,
+ params.skip_preprocessing_qc,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ 0,
+ !params.perform_shortread_qc || params.shortread_qc_skipadaptertrim,
+ params.shortread_qc_tool,
+ params.shortread_qc_adapterlist ? Channel.value(file(params.shortread_qc_adapterlist, checkIfExists: true)) : Channel.value([]),
+ params.shortread_qc_mergepairs,
+ false,
+ params.shortread_qc_savetrimmedfail,
+ !params.perform_shortread_complexityfilter,
+ params.shortread_complexityfilter_tool,
+ !params.shortread_qc_dedup,
+ !params.perform_shortread_hostremoval,
+ params.shortread_hostremoval_reference ? Channel.value([[id: 'host'], file(params.shortread_hostremoval_reference, checkIfExists: true)]) : Channel.value([[], []]),
+ params.shortread_hostremoval_index ? Channel.value([params.shortread_hostremoval_index_name, file(params.shortread_hostremoval_index, checkIfExists: true)]) : Channel.value([[], []]),
+ params.shortread_hostremoval_index_name,
+ params.shortread_hostremoval_tool,
+ true,
+ )
+ ch_preprocessed = FASTQ_SHORTREADS_PREPROCESS_QC.out.reads
+ // multiqc_files carries the trimming and complexity-filter logs but not the FastQC
+ // archives, which the subworkflow emits on their own channels. MultiQC wants bare
+ // paths, so drop the meta on the way out.
+ ch_multiqc_files = FASTQ_SHORTREADS_PREPROCESS_QC.out.multiqc_files
+ .mix(FASTQ_SHORTREADS_PREPROCESS_QC.out.pre_stats_fastqc_zip)
+ .mix(FASTQ_SHORTREADS_PREPROCESS_QC.out.post_stats_fastqc_zip)
+ .map { _meta, report -> report }
+ }
+ else {
+ ch_preprocessed = ch_validated
+ }
+
// Step 2: Group by meta.id and merge runs if needed
- ch_grouped = ch_validated
+ ch_grouped = ch_preprocessed
.map { meta, reads ->
// Create grouping key and new meta without run_accession for grouping
def group_key = meta.id
@@ -92,4 +144,5 @@ workflow DATAPREP {
emit:
reads = ch_reads // Paired-end reads (R1, R2) or single-end
reads_concat = ch_concatenated // All reads concatenated into single file
+ multiqc_files = ch_multiqc_files // Reports from the preprocessing subworkflow
}
diff --git a/subworkflows/local/dataprep/meta.yml b/subworkflows/local/dataprep/meta.yml
index 915faa4..8107d12 100644
--- a/subworkflows/local/dataprep/meta.yml
+++ b/subworkflows/local/dataprep/meta.yml
@@ -2,18 +2,21 @@
name: "dataprep"
description: |
Validate the input samplesheet and prepare read channels for profiling. Sets
- `single_end`, `run_accession` and `instrument_platform` on the meta map, merges
- multiple runs belonging to the same sample, and produces an additional channel
- in which read pairs are concatenated into a single file for tools that cannot
- take paired input.
+ `single_end`, `run_accession` and `instrument_platform` on the meta map,
+ optionally runs read QC and preprocessing per sequencing run, merges multiple
+ runs belonging to the same sample, and produces an additional channel in which
+ read pairs are concatenated into a single file for tools that cannot take
+ paired input.
keywords:
- samplesheet
- fastq
+ - preprocessing
- merging
- concatenation
- metagenome
components:
- cat/fastq
+ - fastq_shortreads_preprocess_qc
input:
- samplesheet:
type: file
@@ -37,6 +40,13 @@ output:
with `single_end` forced to `true`, for tools that accept only one FASTQ.
Structure: [ val(meta), [ path(reads) ] ]
pattern: "*.{fq,fastq}{,.gz}"
+ - multiqc_files:
+ type: file
+ description: |
+ Logs and reports from the preprocessing steps that ran, ready for MultiQC.
+ Empty when preprocessing is skipped entirely.
+ Structure: [ path(report) ]
+ pattern: "*.{html,json,log,settings,txt,zip}"
authors:
- "@nickp60"
maintainers:
diff --git a/subworkflows/local/dataprep/tests/main.nf.test.snap b/subworkflows/local/dataprep/tests/main.nf.test.snap
index 8b9fa61..efa3445 100644
--- a/subworkflows/local/dataprep/tests/main.nf.test.snap
+++ b/subworkflows/local/dataprep/tests/main.nf.test.snap
@@ -47,6 +47,12 @@
"minigut.merged.fastq.gz:md5,1fbae8f5fe6fdfa6552cfca32d9c3129"
]
]
+ ],
+ "2": [
+
+ ],
+ "multiqc_files": [
+
],
"reads": [
[
@@ -96,10 +102,10 @@
]
}
],
- "timestamp": "2026-04-18T00:05:15.049339",
+ "timestamp": "2026-08-05T17:01:48.189660194",
"meta": {
"nf-test": "0.9.5",
- "nextflow": "25.10.4"
+ "nextflow": "26.04.6"
}
}
}
\ No newline at end of file
diff --git a/subworkflows/local/utils_nfcore_funcprofiler_pipeline/main.nf b/subworkflows/local/utils_nfcore_funcprofiler_pipeline/main.nf
index 2350e72..f535d97 100644
--- a/subworkflows/local/utils_nfcore_funcprofiler_pipeline/main.nf
+++ b/subworkflows/local/utils_nfcore_funcprofiler_pipeline/main.nf
@@ -184,6 +184,43 @@ def validateInputSamplesheet(input) {
def toolCitationText() {
def text_qc = ["Read quality was assessed with FastQC (Andrews 2010)."].join(' ').trim()
+ def shortread_qc_citations = [
+ 'fastp': "fastp (Chen et al. 2018)",
+ 'adapterremoval': "AdapterRemoval (Schubert et al. 2016)",
+ 'trimmomatic': "Trimmomatic (Bolger et al. 2014)",
+ 'cutadapt': "Cutadapt (Martin 2011)",
+ 'trimgalore': "Trim Galore! (Krueger 2015)",
+ 'bbduk': "BBDuk (Bushnell 2022)",
+ 'leehom': "leeHom (Renaud et al. 2014)",
+ ]
+
+ def complexityfilter_citations = [
+ 'bbduk': "BBDuk (Bushnell 2022)",
+ 'prinseqplusplus': "PRINSEQ++ (Cantu et al. 2019)",
+ 'fastp': "fastp (Chen et al. 2018)",
+ ]
+
+ def hostremoval_citations = [
+ 'hostile': "Hostile (Constantinides et al. 2023)",
+ 'deacon': "Deacon (Constantinides 2024)",
+ ]
+
+ def text_shortread_qc = [
+ "Adapter clipping and quality filtering were performed with",
+ "${shortread_qc_citations[params.shortread_qc_tool]}.",
+ params.shortread_qc_dedup ? "Duplicate reads were removed with Clumpify (Bushnell 2022)." : "",
+ ].join(' ').trim()
+
+ def text_complexityfilter = [
+ "Low-complexity reads were removed with",
+ "${complexityfilter_citations[params.shortread_complexityfilter_tool]}.",
+ ].join(' ').trim()
+
+ def text_hostremoval = [
+ "Host-derived reads were removed with",
+ "${hostremoval_citations[params.shortread_hostremoval_tool]}.",
+ ].join(' ').trim()
+
def text_humann = [
"Functional profiling was performed with",
params.run_humann_v3 && params.run_humann_v4
@@ -207,6 +244,9 @@ def toolCitationText() {
def citation_text = [
"Tools used in the workflow included:",
!params.skip_preprocessing_qc ? text_qc : "",
+ params.perform_shortread_qc ? text_shortread_qc : "",
+ params.perform_shortread_complexityfilter ? text_complexityfilter : "",
+ params.perform_shortread_hostremoval ? text_hostremoval : "",
params.run_humann_v3 || params.run_humann_v4 ? text_humann : "",
params.run_diamond ? text_diamond : "",
params.run_fmhfunprofiler ? text_fmhfunprofiler : "",
@@ -222,6 +262,27 @@ def toolCitationText() {
def toolBibliographyText() {
def text_qc = [!params.skip_preprocessing_qc ? "Andrews, S. (2010). FastQC: A Quality Control Tool for High Throughput Sequence Data [Online]. Available at: http://www.bioinformatics.babraham.ac.uk/projects/fastqc/" : ""].join(' ').trim()
+ // One reference per preprocessing tool, keyed the same way as the params that select them.
+ def bib_preprocessing = [
+ 'adapterremoval': "Schubert, M., Lindgreen, S., & Orlando, L. (2016). AdapterRemoval v2: rapid adapter trimming, identification, and read merging. BMC Research Notes, 9, 88. 10.1186/s13104-016-1900-2",
+ 'bbduk': "Bushnell, B. (2022). BBMap. https://sourceforge.net/projects/bbmap/",
+ 'cutadapt': "Martin, M. (2011). Cutadapt removes adapter sequences from high-throughput sequencing reads. EMBnet.journal, 17(1), 10–12. 10.14806/ej.17.1.200",
+ 'deacon': "Constantinides, B. (2024). Deacon: fast alignment-free host depletion. https://github.com/bede/deacon",
+ 'fastp': "Chen, S., Zhou, Y., Chen, Y., & Gu, J. (2018). fastp: an ultra-fast all-in-one FASTQ preprocessor. Bioinformatics, 34(17), i884–i890. 10.1093/bioinformatics/bty560",
+ 'hostile': "Constantinides, B., Hunt, M., & Crook, D. W. (2023). Hostile: accurate decontamination of microbial host sequences. Bioinformatics, 39(12), btad728. 10.1093/bioinformatics/btad728",
+ 'leehom': "Renaud, G., Stenzel, U., & Kelso, J. (2014). leeHom: adaptor trimming and merging for Illumina sequencing reads. Nucleic Acids Research, 42(18), e141. 10.1093/nar/gku699",
+ 'prinseqplusplus': "Cantu, V. A., Sadural, J., & Edwards, R. (2019). PRINSEQ++, a multi-threaded tool for fast and efficient quality control and preprocessing of sequencing datasets. PeerJ Preprints, 7, e27553v1. 10.7287/peerj.preprints.27553v1",
+ 'trimgalore': "Krueger, F. (2015). Trim Galore!: a wrapper around Cutadapt and FastQC. https://www.bioinformatics.babraham.ac.uk/projects/trim_galore/",
+ 'trimmomatic': "Bolger, A. M., Lohse, M., & Usadel, B. (2014). Trimmomatic: a flexible trimmer for Illumina sequence data. Bioinformatics, 30(15), 2114–2120. 10.1093/bioinformatics/btu170",
+ ]
+
+ def text_preprocessing = [
+ params.perform_shortread_qc ? bib_preprocessing[params.shortread_qc_tool] : "",
+ params.perform_shortread_qc && params.shortread_qc_dedup ? bib_preprocessing['bbduk'] : "",
+ params.perform_shortread_complexityfilter ? bib_preprocessing[params.shortread_complexityfilter_tool] : "",
+ params.perform_shortread_hostremoval ? bib_preprocessing[params.shortread_hostremoval_tool] : "",
+ ].unique().join(' ').trim()
+
def text_humann = [
params.run_humann_v3 || params.run_humann_v4 ? "Beghini, F., McIver, L. J., Blanco-M\u00edguez, A., Dubois, L., Asnicar, F., Maharjan, S., Mailyan, A., Thomas, A. M., Manghi, P., Valles-Colomer, M., Weingart, G., Zhang, Y., Zolfo, M., Huttenhower, C., Franzosa, E. A., & Segata, N. (2021). Integrating taxonomic, functional, and strain-level profiling of diverse microbial communities with bioBakery 3. eLife, 10, e65088. 10.7554/eLife.65088" : "",
params.run_humann_v3 || params.run_humann_v4 ? "Blanco-M\u00edguez, A., Beghini, F., Cumbo, F., McIver, L. J., Thompson, K. N., Zolfo, M., Manghi, P., Dubois, L., Huang, K. D., Thomas, A. M., Nickols, W. A., Piccinno, G., Piperni, E., Pun\u010doch\u00e1\u0159, M., Valles-Colomer, M., Tett, A., Giordano, F., Davies, R., Wolf, J., \u2026 Segata, N. (2023). Extending and improving metagenomic taxonomic profiling with uncharacterized species using MetaPhlAn 4. Nature Biotechnology, 41, 1633\u20131645. 10.1038/s41587-023-01688-w" : "",
@@ -243,6 +304,7 @@ def toolBibliographyText() {
def reference_text = [
text_qc,
+ text_preprocessing,
text_humann,
text_eggnggmapper,
text_rgi,
diff --git a/subworkflows/nf-core/fastq_complexity_filter/main.nf b/subworkflows/nf-core/fastq_complexity_filter/main.nf
new file mode 100644
index 0000000..4c9678a
--- /dev/null
+++ b/subworkflows/nf-core/fastq_complexity_filter/main.nf
@@ -0,0 +1,48 @@
+include { PRINSEQPLUSPLUS } from '../../../modules/nf-core/prinseqplusplus/main'
+include { BBMAP_BBDUK } from '../../../modules/nf-core/bbmap/bbduk/main'
+include { FASTP } from '../../../modules/nf-core/fastp/main'
+
+workflow FASTQ_COMPLEXITY_FILTER {
+
+ take:
+ ch_reads // channel: [ val(meta), [ fastq ] ]
+ val_complexity_filter_tool // string: [mandatory] tool_name // choose from: ["prinseqplusplus", "bbduk", "fastp"]
+
+ main:
+
+ ch_log = channel.empty()
+ ch_report = channel.empty()
+ ch_multiqc_files = channel.empty()
+
+ if (val_complexity_filter_tool == "prinseqplusplus") {
+ PRINSEQPLUSPLUS( ch_reads )
+
+ ch_filtered_reads = PRINSEQPLUSPLUS.out.good_reads
+ ch_log = PRINSEQPLUSPLUS.out.log
+ } else if (val_complexity_filter_tool == "bbduk") {
+ BBMAP_BBDUK( ch_reads, [] )
+
+ ch_filtered_reads = BBMAP_BBDUK.out.reads
+ ch_multiqc_files = ch_multiqc_files.mix(BBMAP_BBDUK.out.log)
+ } else if (val_complexity_filter_tool == "fastp") {
+ FASTP(
+ ch_reads.map { meta, files -> [ meta, files, [] ] },
+ false,
+ false,
+ false
+ )
+
+ ch_filtered_reads = FASTP.out.reads
+ ch_log = FASTP.out.log
+ ch_report = FASTP.out.html
+ ch_multiqc_files = ch_multiqc_files.mix(FASTP.out.json)
+ } else {
+ error('Please choose one of the available complexity filtering tools: ["prinseqplusplus", "bbduk", "fastp"]')
+ }
+
+ emit:
+ filtered_reads = ch_filtered_reads // channel: [ val(meta), [ fastq.gz ] ]
+ logfile = ch_log // channel: [ val(meta), [ {txt} ] ]
+ report = ch_report // channel: [ val(meta), [ {html} ] ]
+ multiqc_files = ch_multiqc_files
+}
diff --git a/subworkflows/nf-core/fastq_complexity_filter/meta.yml b/subworkflows/nf-core/fastq_complexity_filter/meta.yml
new file mode 100644
index 0000000..75f56d0
--- /dev/null
+++ b/subworkflows/nf-core/fastq_complexity_filter/meta.yml
@@ -0,0 +1,68 @@
+# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json
+name: "fastq_complexity_filter"
+description: |
+ Perform low-complexity filtering of FASTQ reads using a selectable tool.
+ One of PRINSEQ++, BBDuk, or fastp is executed based on the provided tool name.
+ Only complexity filtering is applied; no adapter trimming, read merging, or
+ quality trimming is performed.
+keywords:
+ - fastq
+ - complexity filtering
+ - low complexity
+ - entropy
+ - qc
+components:
+ - prinseqplusplus
+ - bbmap/bbduk
+ - fastp
+
+input:
+ - ch_reads:
+ type: file
+ description: |
+ Input channel containing sample metadata and FASTQ reads.
+ Structure: [ val(meta), path(fastq) ]
+ pattern: "*.{fastq,fastq.gz}"
+ - val_complexity_filter_tool:
+ type: string
+ description: |
+ Complexity filtering tool to use.
+ Must be one of: 'prinseqplusplus', 'bbduk', or 'fastp'.
+
+output:
+ - filtered_reads:
+ type: file
+ description: |
+ Channel containing complexity-filtered FASTQ reads.
+ Structure: [ val(meta), path(fastq) ]
+ pattern: "*.{fastq,fastq.gz}"
+ - logfile:
+ type: file
+ description: |
+ Tool-specific log files, when available.
+ Structure: [ val(meta), path(log) ]
+ pattern: "*.log"
+ - report:
+ type: file
+ description: |
+ HTML report generated by fastp. Empty for other tools.
+ Structure: [ val(meta), path(html) ]
+ pattern: "*.html"
+ - multiqc_files:
+ type: file
+ description: |
+ Files emitted for MultiQC aggregation (e.g. fastp JSON reports,
+ BBDuk logs).
+ Structure: [ path(file) ]
+ pattern: "*.{json,log}"
+ - versions:
+ type: file
+ description: |
+ File containing software versions
+ Structure: [ path(versions.yml) ]
+ pattern: "versions.yml"
+
+authors:
+ - "@vagkaratzas"
+maintainers:
+ - "@vagkaratzas"
diff --git a/subworkflows/nf-core/fastq_complexity_filter/nextflow.config b/subworkflows/nf-core/fastq_complexity_filter/nextflow.config
new file mode 100644
index 0000000..78f02d1
--- /dev/null
+++ b/subworkflows/nf-core/fastq_complexity_filter/nextflow.config
@@ -0,0 +1,22 @@
+// IMPORTANT: Add this configuration to your modules.config
+
+process {
+ withName: ".*:FASTQ_COMPLEXITY_FILTER:BBMAP_BBDUK" {
+ ext.args = [
+ 'entropy=0.7', // needed to turn on complexity filtering
+ 'minlength=0' // needed, to not discard reads shorter than this
+ ].join(' ')
+ ext.prefix = { "${meta.id}.trim" }
+ }
+
+ // need FASTP to only do complexity filtering
+ withName: ".*:FASTQ_COMPLEXITY_FILTER:FASTP" {
+ ext.args = [
+ '--low_complexity_filter',
+ '--disable_adapter_trimming',
+ '--disable_trim_poly_g',
+ '--disable_quality_filtering',
+ '--disable_length_filtering'
+ ].join(' ')
+ }
+}
diff --git a/subworkflows/nf-core/fastq_complexity_filter/tests/main.nf.test b/subworkflows/nf-core/fastq_complexity_filter/tests/main.nf.test
new file mode 100644
index 0000000..6a54e89
--- /dev/null
+++ b/subworkflows/nf-core/fastq_complexity_filter/tests/main.nf.test
@@ -0,0 +1,118 @@
+nextflow_workflow {
+
+ name "Test Subworkflow FASTQ_COMPLEXITY_FILTER"
+ script "../main.nf"
+ workflow "FASTQ_COMPLEXITY_FILTER"
+ config "./nextflow.config"
+
+ tag "subworkflows"
+ tag "subworkflows_nfcore"
+ tag "subworkflows/fastq_complexity_filter"
+
+ tag "prinseqplusplus"
+ tag "bbmap"
+ tag "bbmap/bbduk"
+ tag "fastp"
+
+ test("sarscov2 - fastq - single_end - prinseqplusplus") {
+
+ when {
+ workflow {
+ """
+ input[0] = channel.of([
+ [ id:'test', single_end:true ],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ])
+ input[1] = "prinseqplusplus" // val_complexity_filter_tool
+ """
+ }
+ }
+ then {
+ assert workflow.success
+ assertAll(
+ { assert snapshot(
+ file(workflow.out.filtered_reads[0][1]).name, // unstable
+ workflow.out.logfile[0][1]
+ ).match()}
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - paired_end - bbduk") {
+
+ when {
+ workflow {
+ """
+ input[0] = channel.of([
+ [ id:'test', single_end:false ],
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ ]
+ ])
+ input[1] = "bbduk" // val_complexity_filter_tool
+ """
+ }
+ }
+ then {
+ assert workflow.success
+ assertAll(
+ { assert snapshot(
+ workflow.out.filtered_reads[0][1],
+ workflow.out.multiqc_files.collect { file(it[1]).name }
+ ).match()}
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - single_end - fastp") {
+
+ when {
+ workflow {
+ """
+ input[0] = channel.of([
+ [ id:'test', single_end:true ],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ])
+ input[1] = "fastp" // val_complexity_filter_tool
+ """
+ }
+ }
+ then {
+ assert workflow.success
+ assertAll(
+ { assert snapshot(
+ workflow.out.filtered_reads[0][1],
+ path(workflow.out.logfile[0][1]).readLines().size(),
+ path(workflow.out.report[0][1]).readLines().size(),
+ workflow.out.multiqc_files[0][1]
+ ).match()}
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - single_end - fastp - stub") {
+
+ options "-stub"
+
+ when {
+ workflow {
+ """
+ input[0] = channel.of([
+ [ id:'test', single_end:true ],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ])
+ input[1] = "fastp" // val_complexity_filter_tool
+ """
+ }
+ }
+ then {
+ assert workflow.success
+ assertAll(
+ { assert snapshot(
+ workflow.out
+ ).match()}
+ )
+ }
+ }
+}
diff --git a/subworkflows/nf-core/fastq_complexity_filter/tests/main.nf.test.snap b/subworkflows/nf-core/fastq_complexity_filter/tests/main.nf.test.snap
new file mode 100644
index 0000000..2a0315c
--- /dev/null
+++ b/subworkflows/nf-core/fastq_complexity_filter/tests/main.nf.test.snap
@@ -0,0 +1,125 @@
+{
+ "sarscov2 - fastq - single_end - fastp - stub": {
+ "content": [
+ {
+ "0": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "1": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "2": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "3": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "filtered_reads": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "logfile": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "multiqc_files": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "report": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-05-15T07:14:24.213399133",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "25.10.2"
+ }
+ },
+ "sarscov2 - fastq - single_end - prinseqplusplus": {
+ "content": [
+ "test_good_out.fastq.gz",
+ "test.log:md5,f9bceec6e29b63bac4b8da14efeadfcd"
+ ],
+ "timestamp": "2026-05-15T07:13:47.00739468",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "25.10.2"
+ }
+ },
+ "sarscov2 - fastq - single_end - fastp": {
+ "content": [
+ "test.fastp.fastq.gz:md5,0536c516b46941a349c8ba69b324fd73",
+ 27,
+ 2394,
+ "test.fastp.json:md5,3c1b5d91fb701b0a3753b48873b543f5"
+ ],
+ "timestamp": "2026-07-31T10:08:06.668906",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.6"
+ }
+ },
+ "sarscov2 - fastq - paired_end - bbduk": {
+ "content": [
+ [
+ "test.trim_1.fastq.gz:md5,4161df271f9bfcd25d5845a1e220dbec",
+ "test.trim_2.fastq.gz:md5,2ebae722295ea66d84075a3b042e2b42"
+ ],
+ [
+ "test.trim.bbduk.log"
+ ]
+ ],
+ "timestamp": "2026-05-15T07:14:08.730079029",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "25.10.2"
+ }
+ }
+}
\ No newline at end of file
diff --git a/subworkflows/nf-core/fastq_complexity_filter/tests/nextflow.config b/subworkflows/nf-core/fastq_complexity_filter/tests/nextflow.config
new file mode 100644
index 0000000..fa6b940
--- /dev/null
+++ b/subworkflows/nf-core/fastq_complexity_filter/tests/nextflow.config
@@ -0,0 +1,24 @@
+process {
+ withName: 'PRINSEQPLUSPLUS' {
+ ext.args = "-lc_entropy=0.8"
+ }
+
+ withName: 'BBMAP_BBDUK' {
+ ext.args = [
+ 'entropy=0.7', // needed to turn on complexity filtering
+ 'minlength=0' // needed, to not discard reads shorter than this
+ ].join(' ')
+ ext.prefix = { "${meta.id}.trim" }
+ }
+
+ withName: 'FASTP' {
+ ext.args = [
+ '--low_complexity_filter', // needed to turn on complexity filtering
+ '--complexity_threshold 60', // default is 30, testing with 60 to filter something out
+ '--disable_adapter_trimming',
+ '--disable_trim_poly_g',
+ '--disable_quality_filtering',
+ '--disable_length_filtering'
+ ].join(' ')
+ }
+}
diff --git a/subworkflows/nf-core/fastq_decontaminate_deacon_hostile/main.nf b/subworkflows/nf-core/fastq_decontaminate_deacon_hostile/main.nf
new file mode 100644
index 0000000..6b1099f
--- /dev/null
+++ b/subworkflows/nf-core/fastq_decontaminate_deacon_hostile/main.nf
@@ -0,0 +1,50 @@
+include { FASTQ_FETCH_CLEAN_HOSTILE } from '../fastq_fetch_clean_hostile/main'
+include { FASTQ_INDEX_FILTER_DEACON } from '../fastq_index_filter_deacon/main'
+
+workflow FASTQ_DECONTAMINATE_DEACON_HOSTILE {
+
+ take:
+ ch_reads // channel: [ val(meta), [ reads ] ]
+ ch_fasta // channel: [ val(meta), [ fasta ] ] (optional)
+ ch_reference // channel: [ val(reference_name), path(reference_dir) ] (optional)
+ index_name // val (optional)
+ decontaminator // string (enum): 'hostile' or 'deacon'
+
+ main:
+
+ reference = channel.empty()
+ json = channel.empty()
+ index = channel.empty()
+ summary = channel.empty()
+
+ if (decontaminator != "hostile" && decontaminator != "deacon"){
+ error("Unknown decontaminator '${decontaminator}'")
+ }
+
+ // Fastq decontamination
+ if (decontaminator == "hostile") {
+ FASTQ_FETCH_CLEAN_HOSTILE (
+ ch_reads,
+ ch_reference,
+ index_name
+ )
+ fastq_filtered = FASTQ_FETCH_CLEAN_HOSTILE.out.fastq
+ reference = FASTQ_FETCH_CLEAN_HOSTILE.out.reference
+ json = FASTQ_FETCH_CLEAN_HOSTILE.out.json
+ } else if (decontaminator == "deacon") {
+ FASTQ_INDEX_FILTER_DEACON (
+ ch_fasta.join(ch_reads)
+ )
+ fastq_filtered = FASTQ_INDEX_FILTER_DEACON.out.fastq_filtered
+ index = FASTQ_INDEX_FILTER_DEACON.out.index
+ summary = FASTQ_INDEX_FILTER_DEACON.out.summary
+ }
+
+
+ emit:
+ fastq_filtered = fastq_filtered // channel: [ val(meta), [ fastq ] ]
+ reference = reference // channel: [ val(reference_name), path(reference_dir) ] (hostile only)
+ json = json // channel: [ val(meta), [ *.json ] ] (hostile only)
+ index = index // channel: [ val(meta), [ index ] ] (deacon only)
+ summary = summary // channel: [ val(meta), [ log ] ] (deacon only)
+}
diff --git a/subworkflows/nf-core/fastq_decontaminate_deacon_hostile/meta.yml b/subworkflows/nf-core/fastq_decontaminate_deacon_hostile/meta.yml
new file mode 100644
index 0000000..beb8d34
--- /dev/null
+++ b/subworkflows/nf-core/fastq_decontaminate_deacon_hostile/meta.yml
@@ -0,0 +1,79 @@
+# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json
+name: "fastq_decontaminate_deacon_hostile"
+description: Decontaminate FastQ files by filtering reads that match a reference genome using Deacon or Hostile
+keywords:
+ - hostile
+ - deacon
+ - filter
+ - index
+ - fasta
+ - fastq
+ - genome
+ - reference
+ - minimizer
+ - decontamination
+components:
+ - hostile/fetch
+ - hostile/clean
+ - deacon/index
+ - deacon/filter
+ - fastq_fetch_clean_hostile
+ - fastq_index_filter_deacon
+input:
+ - ch_reads:
+ type: file
+ description: |
+ List of FastQ files of size 1 and 2 for single-end and paired-end data, respectively.
+ Structure: [ val(meta), [ path(reads) ] ]
+ - ch_fasta:
+ type: file
+ description: |
+ Input genome fasta file used by Deacon. Meta must match the meta of ch_reads.
+ Structure: [ val(meta), [ path(fasta) ] ]
+ - ch_reference:
+ type: directory
+ description: |
+ Directory containing index file(s) corresponding to the preferred aligner (bowtie2 short reads or minimap for long reads) used by Hostile.
+ Note that single end data is assumed to be long reads. If you have single-end short read you must supply both the BowTie2
+ indices AND explicitly specify `--aligner bowtie2`
+ - index_name:
+ type: string
+ description: Name of the reference genome index to download for Hostile fetch if the reference is not provided.
+ - decontaminator:
+ type: string
+ description: Name of the deacontamination tool to use.
+ enum: ["deacon", "hostile"]
+output:
+ - fastq_filtered:
+ type: file
+ description: |
+ List of output filtered FastQ files of size 1 and 2 for single-end and paired-end data, respectively.
+ Structure: [ val(meta), path(${prefix}*.fq.gz) ]
+ pattern: "*.fq.gz"
+ - reference:
+ type: channel
+ description: |
+ Channel containing reference name and directory with index files for Hostile.
+ Structure: [ val(reference_name), path(reference_dir) ]
+ - json:
+ type: channel
+ description: |
+ Channel containing sample metadata and Hostile cleaning log in JSON format.
+ Structure: [ val(meta), path(*.json) ]
+ pattern: "*.json"
+ - index:
+ type: file
+ description: |
+ Deacon minimizer index file.
+ Structure: [ val(meta), path(index) ]
+ pattern: ".idx"
+ - summary:
+ type: file
+ description: |
+ JSON file containing summary of results.
+ Structure: [ val(meta), path(${prefix}.json) ]
+ pattern: "*.json"
+authors:
+ - "@Baksic-Ivan"
+maintainers:
+ - "@Baksic-Ivan"
diff --git a/subworkflows/nf-core/fastq_decontaminate_deacon_hostile/tests/main.nf.test b/subworkflows/nf-core/fastq_decontaminate_deacon_hostile/tests/main.nf.test
new file mode 100644
index 0000000..83e12b0
--- /dev/null
+++ b/subworkflows/nf-core/fastq_decontaminate_deacon_hostile/tests/main.nf.test
@@ -0,0 +1,192 @@
+nextflow_workflow {
+
+ name "Test Subworkflow FASTQ_DECONTAMINATE_DEACON_HOSTILE"
+ script "../main.nf"
+ workflow "FASTQ_DECONTAMINATE_DEACON_HOSTILE"
+ config './nextflow.config'
+
+ tag "subworkflows"
+ tag "subworkflows_nfcore"
+ tag "subworkflows/fastq_index_filter_deacon"
+ tag "subworkflows/fastq_fetch_clean_hostile"
+ tag "subworkflows/fastq_decontaminate_deacon_hostile"
+ tag "hostile"
+ tag "hostile/fetch"
+ tag "hostile/clean"
+ tag "bowtie2/build"
+ tag "deacon"
+ tag "deacon/filter"
+ tag "deacon/index"
+
+ test("sarscov2 - fastq - single-end - hostile") {
+ setup {
+ run("BOWTIE2_BUILD") {
+ script "../../../../modules/nf-core/bowtie2/build/main.nf"
+ process {
+ """
+ input[0] = [
+ [ id:'test' ],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)
+ ]
+ """
+ }
+ }
+ }
+ when {
+ workflow {
+ """
+ input[0] = channel.of([
+ [ id:'test_single', single_end:true ],
+ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ])
+ input[1] = []
+ input[2] = BOWTIE2_BUILD.out.index.map { meta, index -> tuple('genome', index) }
+ input[3] = []
+ input[4] = 'hostile'
+ """
+ }
+ }
+ then {
+ assertAll(
+ { assert workflow.success },
+ { assert snapshot(sanitizeOutput(workflow.out, unstableKeys:["json"])).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - paired-end - hostile") {
+ setup {
+ run("BOWTIE2_BUILD") {
+ script "../../../../modules/nf-core/bowtie2/build/main.nf"
+ process {
+ """
+ input[0] = [
+ [ id:'test' ],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)
+ ]
+ """
+ }
+ }
+ }
+ when {
+ workflow {
+ """
+ input[0] = channel.of([
+ [ id:'test_paired', single_end:false ],
+ [
+ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ ]
+ ])
+ input[1] = []
+ input[2] = BOWTIE2_BUILD.out.index.map { meta, index -> tuple('genome', index) }
+ input[3] = []
+ input[4] = 'hostile'
+ """
+ }
+ }
+ then {
+ assertAll(
+ { assert workflow.success },
+ { assert snapshot(sanitizeOutput(workflow.out, unstableKeys:["json"])).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - single-end - deacon") {
+ when {
+ workflow {
+ """
+ input[0] = channel.of(
+ [
+ [ id:'test', single_end:true ],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ]
+ )
+ input[1] = channel.of(
+ [
+ [ id:'test', single_end:true ],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)
+ ]
+ )
+ input[2] = []
+ input[3] = []
+ input[4] = 'deacon'
+ """
+ }
+ }
+ then {
+ assertAll(
+ { assert workflow.success },
+ { assert snapshot(sanitizeOutput(workflow.out, unstableKeys:["summary"])).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - paired-end - deacon") {
+ when {
+ workflow {
+ """
+ input[0] = channel.of(
+ [
+ [ id:'test', single_end:false ],
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ ]
+ ]
+ )
+ input[1] = channel.of(
+ [
+ [ id:'test', single_end:false ],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)
+ ]
+ )
+ input[2] = []
+ input[3] = []
+ input[4] = 'deacon'
+ """
+ }
+ }
+ then {
+ assertAll(
+ { assert workflow.success },
+ { assert snapshot(sanitizeOutput(workflow.out, unstableKeys:["summary"])).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - single-end - deacon - stub") {
+
+ options "-stub"
+
+ when {
+ workflow {
+ """
+ input[0] = channel.of(
+ [
+ [ id:'test', single_end:true ],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ]
+ )
+ input[1] = channel.of(
+ [
+ [ id:'test', single_end:true ],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)
+ ]
+ )
+ input[2] = []
+ input[3] = []
+ input[4] = 'deacon'
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert workflow.success },
+ { assert snapshot(sanitizeOutput(workflow.out)).match() }
+ )
+ }
+ }
+}
diff --git a/subworkflows/nf-core/fastq_decontaminate_deacon_hostile/tests/main.nf.test.snap b/subworkflows/nf-core/fastq_decontaminate_deacon_hostile/tests/main.nf.test.snap
new file mode 100644
index 0000000..a11e3d7
--- /dev/null
+++ b/subworkflows/nf-core/fastq_decontaminate_deacon_hostile/tests/main.nf.test.snap
@@ -0,0 +1,239 @@
+{
+ "sarscov2 - fastq - single-end - deacon": {
+ "content": [
+ {
+ "fastq_filtered": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fq.gz:md5,2250e99ec61fca0e28e2dcb5124d1a16"
+ ]
+ ],
+ "index": [
+ [
+ {
+ "id": "test",
+ "single_end": true,
+ "index_id": "genome"
+ },
+ "genome.idx:md5,84e4985c91800686db9c9dca28fabd1a"
+ ]
+ ],
+ "json": [
+
+ ],
+ "reference": [
+
+ ],
+ "summary": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.json"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-25T09:33:51.466919727",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "sarscov2 - fastq - single-end - hostile": {
+ "content": [
+ {
+ "fastq_filtered": [
+ [
+ {
+ "id": "test_single",
+ "single_end": true
+ },
+ "test_1.clean.fastq.gz:md5,9224ada21765cb3ad5fc05f40df78f17"
+ ]
+ ],
+ "index": [
+
+ ],
+ "json": [
+ [
+ {
+ "id": "test_single",
+ "single_end": true
+ },
+ "test_single.json"
+ ]
+ ],
+ "reference": [
+ [
+ "genome",
+ [
+ "genome.1.bt2:md5,cbe3d0bbea55bc57c99b4bfa25b5fbdf",
+ "genome.2.bt2:md5,47b153cd1319abc88dda532462651fcf",
+ "genome.3.bt2:md5,4ed93abba181d8dfab2e303e33114777",
+ "genome.4.bt2:md5,c25be5f8b0378abf7a58c8a880b87626",
+ "genome.rev.1.bt2:md5,52be6950579598a990570fbcf5372184",
+ "genome.rev.2.bt2:md5,e3b4ef343dea4dd571642010a7d09597"
+ ]
+ ]
+ ],
+ "summary": [
+
+ ]
+ }
+ ],
+ "timestamp": "2026-06-25T09:36:52.498104951",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "sarscov2 - fastq - paired-end - deacon": {
+ "content": [
+ {
+ "fastq_filtered": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test_1.fq.gz:md5,bf1aa22249e7e2b462c6be1a53ac55e9",
+ "test_2.fq.gz:md5,d3106ad7395c78a212e6214177d5c054"
+ ]
+ ]
+ ],
+ "index": [
+ [
+ {
+ "id": "test",
+ "single_end": false,
+ "index_id": "genome"
+ },
+ "genome.idx:md5,84e4985c91800686db9c9dca28fabd1a"
+ ]
+ ],
+ "json": [
+
+ ],
+ "reference": [
+
+ ],
+ "summary": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.json"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-25T09:34:04.449112509",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "sarscov2 - fastq - paired-end - hostile": {
+ "content": [
+ {
+ "fastq_filtered": [
+ [
+ {
+ "id": "test_paired",
+ "single_end": false
+ },
+ [
+ "test_1.clean_1.fastq.gz:md5,6de988b85909e6529bad0022703bcab2",
+ "test_2.clean_2.fastq.gz:md5,539acd65e93bf16a5ace7d6034e704c7"
+ ]
+ ]
+ ],
+ "index": [
+
+ ],
+ "json": [
+ [
+ {
+ "id": "test_paired",
+ "single_end": false
+ },
+ "test_paired.json"
+ ]
+ ],
+ "reference": [
+ [
+ "genome",
+ [
+ "genome.1.bt2:md5,cbe3d0bbea55bc57c99b4bfa25b5fbdf",
+ "genome.2.bt2:md5,47b153cd1319abc88dda532462651fcf",
+ "genome.3.bt2:md5,4ed93abba181d8dfab2e303e33114777",
+ "genome.4.bt2:md5,c25be5f8b0378abf7a58c8a880b87626",
+ "genome.rev.1.bt2:md5,52be6950579598a990570fbcf5372184",
+ "genome.rev.2.bt2:md5,e3b4ef343dea4dd571642010a7d09597"
+ ]
+ ]
+ ],
+ "summary": [
+
+ ]
+ }
+ ],
+ "timestamp": "2026-06-25T09:37:16.895941056",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "sarscov2 - fastq - single-end - deacon - stub": {
+ "content": [
+ {
+ "fastq_filtered": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "index": [
+ [
+ {
+ "id": "test",
+ "single_end": true,
+ "index_id": "genome"
+ },
+ "genome.idx:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "json": [
+
+ ],
+ "reference": [
+
+ ],
+ "summary": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.json:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-25T09:34:17.49591082",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ }
+}
\ No newline at end of file
diff --git a/subworkflows/nf-core/fastq_decontaminate_deacon_hostile/tests/nextflow.config b/subworkflows/nf-core/fastq_decontaminate_deacon_hostile/tests/nextflow.config
new file mode 100644
index 0000000..08d4009
--- /dev/null
+++ b/subworkflows/nf-core/fastq_decontaminate_deacon_hostile/tests/nextflow.config
@@ -0,0 +1,5 @@
+process {
+ withName: 'HOSTILE_CLEAN' {
+ ext.args = '--aligner bowtie2'
+ }
+}
diff --git a/subworkflows/nf-core/fastq_fetch_clean_hostile/main.nf b/subworkflows/nf-core/fastq_fetch_clean_hostile/main.nf
new file mode 100644
index 0000000..c071359
--- /dev/null
+++ b/subworkflows/nf-core/fastq_fetch_clean_hostile/main.nf
@@ -0,0 +1,35 @@
+include { HOSTILE_FETCH } from '../../../modules/nf-core/hostile/fetch/main'
+include { HOSTILE_CLEAN } from '../../../modules/nf-core/hostile/clean/main'
+
+workflow FASTQ_FETCH_CLEAN_HOSTILE {
+
+ take:
+ ch_reads // channel: [ val(meta), [ fastq ] ]
+ ch_reference // channel: [ val(reference_name), path(reference_dir) ] (optional)
+ index_name // val (optional)
+
+ main:
+
+ if (!index_name && !ch_reference) {
+ error "Provide either the reference index name for HOSTILE_FETCH or an existing reference path for HOSTILE_CLEAN."
+ }
+
+ if (index_name && ch_reference) {
+ error "Cannot provide both the reference index name for fetching and a reference path, please provide only one."
+ }
+
+ if (index_name) {
+ HOSTILE_FETCH( index_name )
+ out_reference = HOSTILE_FETCH.out.reference
+ }
+ else {
+ out_reference = ch_reference
+ }
+
+ HOSTILE_CLEAN( ch_reads, out_reference )
+
+ emit:
+ reference = out_reference // channel: [ val(reference_name), path(reference_dir) ]
+ fastq = HOSTILE_CLEAN.out.fastq // channel: [ val(meta), [ *.fastq.gz ] ]
+ json = HOSTILE_CLEAN.out.json // channel: [ val(meta), [ *.json ] ]
+}
diff --git a/subworkflows/nf-core/fastq_fetch_clean_hostile/meta.yml b/subworkflows/nf-core/fastq_fetch_clean_hostile/meta.yml
new file mode 100644
index 0000000..38ae866
--- /dev/null
+++ b/subworkflows/nf-core/fastq_fetch_clean_hostile/meta.yml
@@ -0,0 +1,58 @@
+# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json
+name: "fastq_fetch_clean_hostile"
+description: Downloads required reference genomes for hostile and removes host reads from short-read FASTQ sequencing files
+keywords:
+ - hostile
+ - decontamination
+ - human removal
+ - download
+ - host removal
+ - clean
+components:
+ - hostile/fetch
+ - hostile/clean
+input:
+ - meta:
+ type: map
+ description: |
+ Groovy Map containing sample information
+ e.g. `[ id:'sample1', single_end:false ]`
+ - reads:
+ type: file
+ description: Input FASTQ files for hostile cleaning
+ pattern: "*.{fastq,fastq.gz,fq,fq.gz}"
+ - reference_name:
+ type: string
+ description: Name of the reference to align against and thus remove mapped reads to.
+ - reference_dir:
+ type: directory
+ description: |
+ Directory containing index file(s) corresponding to the preferred aligner (bowtie2 short reads or minimap for long reads).
+ Note that single end data is assumed to be long reads. If you have single-end short read you must supply both the BowTie2
+ indices AND explicitly specify `--aligner bowtie2`
+ - index_name:
+ type: string
+ description: Name of the reference genome index to download for hostile fetch if the reference is not provided.
+output:
+ - reference:
+ type: channel
+ description: |
+ Channel containing reference name and directory with index files for Hostile.
+ Structure: [ val(reference_name), path(reference_dir) ]
+ - fastq:
+ type: file
+ description: |
+ Channel containing sample metadata and cleaned FASTQ files.
+ Structure: [ val(meta), path(*.fastq.gz) ]
+ pattern: "*.{fastq,fq,fastq.gz,fq.gz}"
+ - json:
+ type: channel
+ description: |
+ Channel containing sample metadata and Hostile cleaning log in JSON format.
+ Structure: [ val(meta), path(*.json) ]
+ pattern: "*.json"
+authors:
+ - "@maia-munteanu"
+maintainers:
+ - "@maia-munteanu"
+ - "@vagkaratzas"
diff --git a/subworkflows/nf-core/fastq_fetch_clean_hostile/tests/main.nf.test b/subworkflows/nf-core/fastq_fetch_clean_hostile/tests/main.nf.test
new file mode 100644
index 0000000..f00a106
--- /dev/null
+++ b/subworkflows/nf-core/fastq_fetch_clean_hostile/tests/main.nf.test
@@ -0,0 +1,101 @@
+nextflow_workflow {
+
+ name "Test Subworkflow FASTQ_FETCH_CLEAN_HOSTILE"
+ script "../main.nf"
+ workflow "FASTQ_FETCH_CLEAN_HOSTILE"
+ config './nextflow.config'
+
+ tag "subworkflows"
+ tag "subworkflows_nfcore"
+ tag "subworkflows/fastq_fetch_clean_hostile"
+ tag "hostile"
+ tag "hostile/fetch"
+ tag "hostile/clean"
+ tag "bowtie2/build"
+
+ setup {
+ run("BOWTIE2_BUILD") {
+ script "../../../../modules/nf-core/bowtie2/build/main.nf"
+ process {
+ """
+ input[0] = [
+ [ id:'test' ],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)
+ ]
+ """
+ }
+ }
+ }
+
+ test("fastq - single-end - with reference") {
+ when {
+ workflow {
+ """
+ input[0] = Channel.of([
+ [ id:'test_single', single_end:true ],
+ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ])
+ input[1] = BOWTIE2_BUILD.out.index.map { meta, index -> tuple('genome', index) }
+ input[2] = null
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert workflow.success },
+ { assert snapshot(sanitizeOutput(workflow.out, unstableKeys: ["json"])).match() }
+ )
+ }
+ }
+
+ test("fastq - paired-end - with reference") {
+ when {
+ workflow {
+ """
+ input[0] = Channel.of([
+ [ id:'test_paired', single_end:false ],
+ [
+ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ ]
+ ])
+ input[1] = BOWTIE2_BUILD.out.index.map { meta, index -> tuple('genome', index) }
+ input[2] = null
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert workflow.success },
+ { assert snapshot(sanitizeOutput(workflow.out, unstableKeys: ["json"])).match() }
+ )
+ }
+ }
+
+ test("fastq - single-end - with reference - stub") {
+
+ options "-stub"
+
+ when {
+ workflow {
+ """
+ input[0] = Channel.of([
+ [ id:'test_single', single_end:true ],
+ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ])
+ input[1] = BOWTIE2_BUILD.out.index.map { ['genome', it[1]] }
+ input[2] = null
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert workflow.success },
+ { assert snapshot(sanitizeOutput(workflow.out)).match() }
+ )
+ }
+ }
+}
diff --git a/subworkflows/nf-core/fastq_fetch_clean_hostile/tests/main.nf.test.snap b/subworkflows/nf-core/fastq_fetch_clean_hostile/tests/main.nf.test.snap
new file mode 100644
index 0000000..7ff2e26
--- /dev/null
+++ b/subworkflows/nf-core/fastq_fetch_clean_hostile/tests/main.nf.test.snap
@@ -0,0 +1,131 @@
+{
+ "fastq - single-end - with reference - stub": {
+ "content": [
+ {
+ "fastq": [
+ [
+ {
+ "id": "test_single",
+ "single_end": true
+ },
+ "test_single.clean_1.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "json": [
+ [
+ {
+ "id": "test_single",
+ "single_end": true
+ },
+ "test_single.json:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "reference": [
+ [
+ "genome",
+ [
+ "genome.1.bt2:md5,d41d8cd98f00b204e9800998ecf8427e",
+ "genome.2.bt2:md5,d41d8cd98f00b204e9800998ecf8427e",
+ "genome.3.bt2:md5,d41d8cd98f00b204e9800998ecf8427e",
+ "genome.4.bt2:md5,d41d8cd98f00b204e9800998ecf8427e",
+ "genome.rev.1.bt2:md5,d41d8cd98f00b204e9800998ecf8427e",
+ "genome.rev.2.bt2:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-05-26T14:30:20.029664659",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.0"
+ }
+ },
+ "fastq - single-end - with reference": {
+ "content": [
+ {
+ "fastq": [
+ [
+ {
+ "id": "test_single",
+ "single_end": true
+ },
+ "test_1.clean.fastq.gz:md5,9224ada21765cb3ad5fc05f40df78f17"
+ ]
+ ],
+ "json": [
+ [
+ {
+ "id": "test_single",
+ "single_end": true
+ },
+ "test_single.json"
+ ]
+ ],
+ "reference": [
+ [
+ "genome",
+ [
+ "genome.1.bt2:md5,cbe3d0bbea55bc57c99b4bfa25b5fbdf",
+ "genome.2.bt2:md5,47b153cd1319abc88dda532462651fcf",
+ "genome.3.bt2:md5,4ed93abba181d8dfab2e303e33114777",
+ "genome.4.bt2:md5,c25be5f8b0378abf7a58c8a880b87626",
+ "genome.rev.1.bt2:md5,52be6950579598a990570fbcf5372184",
+ "genome.rev.2.bt2:md5,e3b4ef343dea4dd571642010a7d09597"
+ ]
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-05-26T14:43:39.771257929",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.0"
+ }
+ },
+ "fastq - paired-end - with reference": {
+ "content": [
+ {
+ "fastq": [
+ [
+ {
+ "id": "test_paired",
+ "single_end": false
+ },
+ [
+ "test_1.clean_1.fastq.gz:md5,6de988b85909e6529bad0022703bcab2",
+ "test_2.clean_2.fastq.gz:md5,539acd65e93bf16a5ace7d6034e704c7"
+ ]
+ ]
+ ],
+ "json": [
+ [
+ {
+ "id": "test_paired",
+ "single_end": false
+ },
+ "test_paired.json"
+ ]
+ ],
+ "reference": [
+ [
+ "genome",
+ [
+ "genome.1.bt2:md5,cbe3d0bbea55bc57c99b4bfa25b5fbdf",
+ "genome.2.bt2:md5,47b153cd1319abc88dda532462651fcf",
+ "genome.3.bt2:md5,4ed93abba181d8dfab2e303e33114777",
+ "genome.4.bt2:md5,c25be5f8b0378abf7a58c8a880b87626",
+ "genome.rev.1.bt2:md5,52be6950579598a990570fbcf5372184",
+ "genome.rev.2.bt2:md5,e3b4ef343dea4dd571642010a7d09597"
+ ]
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-05-26T14:44:03.047395234",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.0"
+ }
+ }
+}
\ No newline at end of file
diff --git a/subworkflows/nf-core/fastq_fetch_clean_hostile/tests/nextflow.config b/subworkflows/nf-core/fastq_fetch_clean_hostile/tests/nextflow.config
new file mode 100644
index 0000000..08d4009
--- /dev/null
+++ b/subworkflows/nf-core/fastq_fetch_clean_hostile/tests/nextflow.config
@@ -0,0 +1,5 @@
+process {
+ withName: 'HOSTILE_CLEAN' {
+ ext.args = '--aligner bowtie2'
+ }
+}
diff --git a/subworkflows/nf-core/fastq_index_filter_deacon/main.nf b/subworkflows/nf-core/fastq_index_filter_deacon/main.nf
new file mode 100644
index 0000000..33cecad
--- /dev/null
+++ b/subworkflows/nf-core/fastq_index_filter_deacon/main.nf
@@ -0,0 +1,65 @@
+include { DEACON_INDEX } from '../../../modules/nf-core/deacon/index/main'
+include { DEACON_FILTER } from '../../../modules/nf-core/deacon/filter/main'
+
+workflow FASTQ_INDEX_FILTER_DEACON {
+
+ take:
+ ch_fasta_reads // [ val(meta), [ fasta ], [ reads ] ]
+
+ main:
+
+ // Check if fastqs are single-end or paired-end
+ ch_fasta_reads = ch_fasta_reads
+ .map { meta, fasta, reads ->
+ if (meta.single_end) {
+ if (reads instanceof List && reads.size() != 1) {
+ error("Error: Check your meta.single_end value. Single-end reads should contain one file only.")
+ }
+ return [ meta, fasta, reads ]
+ } else {
+ if (!(reads instanceof List) || reads.size() != 2) {
+ error("Error: Check your meta.single_end value. Paired-end reads should contain two files; a forward and a reverse.")
+ }
+ return [ meta, fasta, reads ]
+ }
+ }
+
+ // Extract unique reference fasta files and create fasta-specific metadata
+ // This ensures each unique reference is indexed only once
+ ch_unique_fastas = ch_fasta_reads
+ .map { _meta, fasta, _reads -> fasta }
+ .unique()
+ .map { fasta ->
+ def meta_fasta = [ id: fasta.baseName ]
+ [ meta_fasta, fasta ]
+ }
+
+ // Index unique FASTA files only
+ DEACON_INDEX ( ch_unique_fastas )
+
+ // Match indexes back to original samples using fasta file base name
+ ch_indexes = DEACON_INDEX.out.index
+ .map { meta_fasta, index -> [ meta_fasta.id, index ] }
+
+ ch_reads_with_index = ch_fasta_reads
+ .map { meta, fasta, reads ->
+ [ fasta.baseName, meta, reads ]
+ }
+ .combine(ch_indexes, by: 0)
+ .map { _fasta_id, meta, reads, index ->
+ [ meta, index, reads ]
+ }
+
+ // Filter reads using the matched index
+ DEACON_FILTER(ch_reads_with_index)
+
+ // Create output channel with indexes, the original sample-level metadata and index id
+ ch_index_with_meta = ch_reads_with_index
+ .map { meta, index, _reads -> [ meta + [ index_id: index.baseName ], index ] }
+
+
+ emit:
+ index = ch_index_with_meta // channel: [ val(meta), [ index ] ]
+ fastq_filtered = DEACON_FILTER.out.fastq_filtered // channel: [ val(meta), [ fastq ] ]
+ summary = DEACON_FILTER.out.log // channel: [ val(meta), [ log ] ]
+}
diff --git a/subworkflows/nf-core/fastq_index_filter_deacon/meta.yml b/subworkflows/nf-core/fastq_index_filter_deacon/meta.yml
new file mode 100644
index 0000000..6cb64e0
--- /dev/null
+++ b/subworkflows/nf-core/fastq_index_filter_deacon/meta.yml
@@ -0,0 +1,48 @@
+# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json
+name: "fastq_index_filter_deacon"
+description: |
+ Decontaminate FastQ files by filtering reads that match a reference genome using Deacon
+keywords:
+ - filter
+ - index
+ - fasta
+ - fastq
+ - genome
+ - reference
+ - minimizer
+ - decontamination
+components:
+ - deacon/index
+ - deacon/filter
+input:
+ - ch_fasta_reads:
+ type: file
+ description: |
+ Input genome fasta file and a list of FastQ files of size 1 and 2 for single-end and paired-end data, respectively.
+ Structure: [ val(meta), path(fasta), [ path(reads) ] ]
+output:
+ - index:
+ type: file
+ description: |
+ Deacon minimizer index file
+ Structure: [ val(meta), path(index) ]
+ pattern: ".idx"
+ - fastq_filtered:
+ type: file
+ description: |
+ List of output filtered FastQ files of size 1 and 2 for single-end and paired-end data, respectively.
+ Structure: [ val(meta), path(${prefix}*.fq) ]
+ pattern: "*.fq.gz"
+ - summary:
+ type: file
+ description: |
+ JSON file containing summary of results.
+ Structure: [ val(meta), path(${prefix}.json) ]
+ pattern: "*.json"
+authors:
+ - "@Baksic-Ivan"
+ - "@Omer0191"
+maintainers:
+ - "@Baksic-Ivan"
+ - "@Omer0191"
+ - "@pmoris"
diff --git a/subworkflows/nf-core/fastq_index_filter_deacon/tests/main.nf.test b/subworkflows/nf-core/fastq_index_filter_deacon/tests/main.nf.test
new file mode 100644
index 0000000..7c1f04d
--- /dev/null
+++ b/subworkflows/nf-core/fastq_index_filter_deacon/tests/main.nf.test
@@ -0,0 +1,127 @@
+nextflow_workflow {
+
+ name "Test Subworkflow FASTQ_INDEX_FILTER_DEACON"
+ script "../main.nf"
+ workflow "FASTQ_INDEX_FILTER_DEACON"
+
+ tag "subworkflows"
+ tag "subworkflows_nfcore"
+ tag "subworkflows/fastq_index_filter_deacon"
+ tag "deacon"
+ tag "deacon/index"
+ tag "deacon/filter"
+
+ test("sarscov2 - fastq - single-end") {
+
+ when {
+ workflow {
+ """
+ input[0] = channel.of(
+ [
+ [ 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/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ]
+ )
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert workflow.success },
+ { assert snapshot(sanitizeOutput(workflow.out, unstableKeys:["summary"])).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - paired-end") {
+
+ when {
+ workflow {
+ """
+ input[0] = channel.of(
+ [
+ [ id:'test', single_end:false ],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true),
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ ]
+ ]
+ )
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert workflow.success },
+ { assert snapshot(sanitizeOutput(workflow.out, unstableKeys:["summary"])).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - single-end - stub") {
+
+ options "-stub"
+
+ when {
+ workflow {
+ """
+ input[0] = channel.of(
+ [
+ [ 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/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ]
+ )
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert workflow.success },
+ { assert snapshot(sanitizeOutput(workflow.out)).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - identical fasta should be indexed only once") {
+
+ when {
+ workflow {
+ """
+ input[0] = channel.of(
+ [
+ [ id:'sample1', single_end:true ],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ],
+ [
+ [ id:'sample2', single_end:true ],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ ]
+ )
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert workflow.success },
+ { assert workflow.out.fastq_filtered.size() == 2 },
+ { assert workflow.out.index.size() == 2 },
+ {
+ def indexRuns = workflow.trace.tasks.count { task ->
+ task.name.contains('DEACON_INDEX')
+ }
+ assert indexRuns == 1
+ },
+ { assert snapshot(sanitizeOutput(workflow.out, unstableKeys:["summary"])).match() }
+ )
+ }
+ }
+}
diff --git a/subworkflows/nf-core/fastq_index_filter_deacon/tests/main.nf.test.snap b/subworkflows/nf-core/fastq_index_filter_deacon/tests/main.nf.test.snap
new file mode 100644
index 0000000..92f58ba
--- /dev/null
+++ b/subworkflows/nf-core/fastq_index_filter_deacon/tests/main.nf.test.snap
@@ -0,0 +1,183 @@
+{
+ "sarscov2 - fastq - single-end - stub": {
+ "content": [
+ {
+ "fastq_filtered": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "index": [
+ [
+ {
+ "id": "test",
+ "single_end": true,
+ "index_id": "genome"
+ },
+ "genome.idx:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "summary": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.json:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-25T09:44:29.66627682",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "sarscov2 - fastq - paired-end": {
+ "content": [
+ {
+ "fastq_filtered": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test_1.fq.gz:md5,bf1aa22249e7e2b462c6be1a53ac55e9",
+ "test_2.fq.gz:md5,d3106ad7395c78a212e6214177d5c054"
+ ]
+ ]
+ ],
+ "index": [
+ [
+ {
+ "id": "test",
+ "single_end": false,
+ "index_id": "genome"
+ },
+ "genome.idx:md5,84e4985c91800686db9c9dca28fabd1a"
+ ]
+ ],
+ "summary": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.json"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-25T09:31:25.930510862",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "sarscov2 - fastq - single-end": {
+ "content": [
+ {
+ "fastq_filtered": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.fq.gz:md5,2250e99ec61fca0e28e2dcb5124d1a16"
+ ]
+ ],
+ "index": [
+ [
+ {
+ "id": "test",
+ "single_end": true,
+ "index_id": "genome"
+ },
+ "genome.idx:md5,84e4985c91800686db9c9dca28fabd1a"
+ ]
+ ],
+ "summary": [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.json"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-25T09:31:18.10054354",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "sarscov2 - identical fasta should be indexed only once": {
+ "content": [
+ {
+ "fastq_filtered": [
+ [
+ {
+ "id": "sample1",
+ "single_end": true
+ },
+ "sample1.fq.gz:md5,2250e99ec61fca0e28e2dcb5124d1a16"
+ ],
+ [
+ {
+ "id": "sample2",
+ "single_end": true
+ },
+ "sample2.fq.gz:md5,fd26c42f54e3316dfc8d082d9b6ed209"
+ ]
+ ],
+ "index": [
+ [
+ {
+ "id": "sample1",
+ "single_end": true,
+ "index_id": "genome"
+ },
+ "genome.idx:md5,84e4985c91800686db9c9dca28fabd1a"
+ ],
+ [
+ {
+ "id": "sample2",
+ "single_end": true,
+ "index_id": "genome"
+ },
+ "genome.idx:md5,84e4985c91800686db9c9dca28fabd1a"
+ ]
+ ],
+ "summary": [
+ [
+ {
+ "id": "sample1",
+ "single_end": true
+ },
+ "sample1.json"
+ ],
+ [
+ {
+ "id": "sample2",
+ "single_end": true
+ },
+ "sample2.json"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-25T09:24:55.20131212",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ }
+}
\ No newline at end of file
diff --git a/subworkflows/nf-core/fastq_preprocess_seqkit/main.nf b/subworkflows/nf-core/fastq_preprocess_seqkit/main.nf
new file mode 100644
index 0000000..0c4e01f
--- /dev/null
+++ b/subworkflows/nf-core/fastq_preprocess_seqkit/main.nf
@@ -0,0 +1,70 @@
+include { FASTQ_SANITISE_SEQKIT } from '../fastq_sanitise_seqkit'
+include { SEQKIT_SEQ } from '../../../modules/nf-core/seqkit/seq'
+include { SEQKIT_REPLACE } from '../../../modules/nf-core/seqkit/replace'
+include { SEQKIT_RMDUP } from '../../../modules/nf-core/seqkit/rmdup'
+
+workflow FASTQ_PREPROCESS_SEQKIT {
+ take:
+ ch_reads // channel: [ val(meta), [ fastq ] ]
+ skip_seqkit_sana_pair // boolean
+ skip_seqkit_seq // boolean
+ skip_seqkit_replace // boolean
+ skip_seqkit_rmdup // boolean
+
+ main:
+ if (!skip_seqkit_sana_pair) {
+ FASTQ_SANITISE_SEQKIT(ch_reads)
+ ch_reads = FASTQ_SANITISE_SEQKIT.out.reads
+ }
+
+ // Split paired-end reads and add strandedness to meta
+ ch_reads_split = ch_reads.flatMap { meta, reads ->
+ if (meta.single_end) {
+ if (reads instanceof List && reads.size() != 1) {
+ error("Error: Check your meta.single_end value. Single-end reads should contain one file only.")
+ }
+ return [[meta + [strandness: 'single'], reads]]
+ }
+ else {
+ if (!(reads instanceof List) || reads.size() != 2) {
+ error("Error: Check your meta.single_end value. Paired-end data should have exactly 2 files.")
+ }
+ return [[meta + [strandness: 'R1'], reads[0]], [meta + [strandness: 'R2'], reads[1]]]
+ }
+ }
+
+ if (!skip_seqkit_seq) {
+ SEQKIT_SEQ(ch_reads_split)
+ ch_reads_split = SEQKIT_SEQ.out.fastx
+ }
+
+ if (!skip_seqkit_replace) {
+ SEQKIT_REPLACE(ch_reads_split, '')
+ ch_reads_split = SEQKIT_REPLACE.out.fastx
+ }
+
+ if (!skip_seqkit_rmdup) {
+ SEQKIT_RMDUP(ch_reads_split)
+ ch_reads_split = SEQKIT_RMDUP.out.fastx
+ }
+
+ ch_reads = ch_reads_split
+ .map { meta, fastq ->
+ // Remove strandness field from meta to merge back together
+ def clean_meta = meta.findAll { key, _value -> key != 'strandness' }
+ return [clean_meta, fastq]
+ }
+ .groupTuple(by: 0)
+ .map { meta, files ->
+ if (meta.single_end) {
+ return [meta, files[0]]
+ }
+ else {
+ def sorted_files = files.flatten().sort { index -> index.name }
+ return [meta, sorted_files]
+ }
+ }
+
+ emit:
+ reads = ch_reads // channel: [ val(meta), [ fastq ] ]
+}
diff --git a/subworkflows/nf-core/fastq_preprocess_seqkit/meta.yml b/subworkflows/nf-core/fastq_preprocess_seqkit/meta.yml
new file mode 100644
index 0000000..a3c6eaa
--- /dev/null
+++ b/subworkflows/nf-core/fastq_preprocess_seqkit/meta.yml
@@ -0,0 +1,58 @@
+# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json
+name: "fastq_preprocess_seqkit"
+description: Subworkflow that preprocesses FASTQ files
+keywords:
+ - fastq
+ - seqkit
+ - preprocessing
+components:
+ - fastq_sanitise_seqkit
+ - seqkit/sana
+ - seqkit/pair
+ - seqkit/seq
+ - seqkit/replace
+ - seqkit/rmdup
+input:
+ - ch_reads:
+ type: channel
+ description: |
+ Channel containing sample metadata and FASTQ files.
+ Structure: [ val(meta), [ fastq ] ]
+ Where meta is a map containing at least:
+ - id: sample identifier
+ - single_end: boolean indicating if data is single-end (true) or paired-end (false)
+ pattern: "*.{fastq,fastq.gz,fq,fq.gz}"
+ - skip_seqkit_sana_pair:
+ type: boolean
+ description: |
+ If true, skips the seqkit_sana_pair subworkflow.
+ - skip_seqkit_seq:
+ type: boolean
+ description: |
+ If true, skips the seqkit_seq process.
+ - skip_seqkit_replace:
+ type: boolean
+ description: |
+ If true, skips the seqkit_replace process.
+ - skip_seqkit_rmdup:
+ type: boolean
+ description: |
+ If true, skips the seqkit_rmdup process.
+output:
+ - reads:
+ type: channel
+ description: |
+ Channel containing filtered FASTQ files.
+ Structure: [ val(meta), [ fastq ] ]
+ pattern: "*.{fastq,fastq.gz,fq,fq.gz}"
+ - versions:
+ type: file
+ description: |
+ File containing software versions
+ Structure: [ path(versions.yml) ]
+ pattern: "versions.yml"
+authors:
+ - "@maia-munteanu"
+maintainers:
+ - "@maia-munteanu"
+ - "@vagkaratzas"
diff --git a/subworkflows/nf-core/fastq_preprocess_seqkit/nextflow.config b/subworkflows/nf-core/fastq_preprocess_seqkit/nextflow.config
new file mode 100644
index 0000000..4e61558
--- /dev/null
+++ b/subworkflows/nf-core/fastq_preprocess_seqkit/nextflow.config
@@ -0,0 +1,6 @@
+// IMPORTANT: This config file should be included to ensure that the subworkflow works properly.
+process {
+ withName: SEQKIT_SANA {
+ ext.prefix = { "${meta.id}_${meta.strandness}" }
+ }
+}
diff --git a/subworkflows/nf-core/fastq_preprocess_seqkit/tests/main.nf.test b/subworkflows/nf-core/fastq_preprocess_seqkit/tests/main.nf.test
new file mode 100644
index 0000000..2495c7e
--- /dev/null
+++ b/subworkflows/nf-core/fastq_preprocess_seqkit/tests/main.nf.test
@@ -0,0 +1,140 @@
+nextflow_workflow {
+
+ name "Test Subworkflow FASTQ_PREPROCESS_SEQKIT"
+ script "../main.nf"
+ workflow "FASTQ_PREPROCESS_SEQKIT"
+ config './nextflow.config'
+
+ tag "subworkflows"
+ tag "subworkflows_nfcore"
+ tag "subworkflows/fastq_sanitise_seqkit"
+ tag "subworkflows/fastq_preprocess_seqkit"
+ tag "seqkit"
+ tag "seqkit/sana"
+ tag "seqkit/pair"
+ tag "seqkit/seq"
+ tag "seqkit/replace"
+ tag "seqkit/rmdup"
+
+ test("sarscov2 - fastq - single_end") {
+
+ when {
+ workflow {
+ """
+ input[0] = Channel.of([
+ [ id:'test_single', single_end:true ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ])
+ input[1] = false // skip_seqkit_sana_pair
+ input[2] = false // skip_seqkit_seq
+ input[3] = false // skip_seqkit_replace
+ input[4] = false // skip_seqkit_rmdup
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert workflow.success },
+ { assert snapshot(
+ sanitizeOutput(workflow.out)
+ ).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - paired_end") {
+
+ when {
+ workflow {
+ """
+ input[0] = Channel.of([
+ [ id:'test_paired', single_end:false ], // meta map
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ ]
+ ])
+ input[1] = false // skip_seqkit_sana_pair
+ input[2] = false // skip_seqkit_seq
+ input[3] = false // skip_seqkit_replace
+ input[4] = false // skip_seqkit_rmdup
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert workflow.success },
+ { assert snapshot(
+ sanitizeOutput(workflow.out)
+ ).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - both with single broken") {
+
+ when {
+ workflow {
+ """
+ input[0] = Channel.of(
+ [
+ [ id:'test_both', single_end:true ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1_broken.fastq.gz', checkIfExists: true)
+ ],
+ [
+ [ id:'test_both', single_end:false ], // meta map
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ ]
+ ]
+ )
+ input[1] = false // skip_seqkit_sana_pair
+ input[2] = false // skip_seqkit_seq
+ input[3] = false // skip_seqkit_replace
+ input[4] = false // skip_seqkit_rmdup
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert workflow.success },
+ { assert snapshot(
+ sanitizeOutput(workflow.out)
+ ).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - stub") {
+
+ options "-stub"
+
+ when {
+ workflow {
+ """
+ input[0] = Channel.of([
+ [ id: 'test_stub', single_end:true ],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1_broken.fastq.gz', checkIfExists: true)
+ ])
+ input[1] = false // skip_seqkit_sana_pair
+ input[2] = false // skip_seqkit_seq
+ input[3] = false // skip_seqkit_replace
+ input[4] = false // skip_seqkit_rmdup
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert workflow.success },
+ { assert snapshot(
+ sanitizeOutput(workflow.out)
+ ).match() }
+ )
+ }
+ }
+}
diff --git a/subworkflows/nf-core/fastq_preprocess_seqkit/tests/main.nf.test.snap b/subworkflows/nf-core/fastq_preprocess_seqkit/tests/main.nf.test.snap
new file mode 100644
index 0000000..4eb01fa
--- /dev/null
+++ b/subworkflows/nf-core/fastq_preprocess_seqkit/tests/main.nf.test.snap
@@ -0,0 +1,95 @@
+{
+ "sarscov2 - fastq - stub": {
+ "content": [
+ {
+ "reads": [
+ [
+ {
+ "id": "test_stub",
+ "single_end": true
+ },
+ "test_stub_single.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-14T17:59:37.811271025",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "sarscov2 - fastq - single_end": {
+ "content": [
+ {
+ "reads": [
+ [
+ {
+ "id": "test_single",
+ "single_end": true
+ },
+ "test_single_single.fastq.gz:md5,c1dbd30da9ad368bb7bf5a117bb765b2"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-12T17:59:36.869448293",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "sarscov2 - fastq - paired_end": {
+ "content": [
+ {
+ "reads": [
+ [
+ {
+ "id": "test_paired",
+ "single_end": false
+ },
+ [
+ "test_paired_R1.fastq.gz:md5,c1dbd30da9ad368bb7bf5a117bb765b2",
+ "test_paired_R2.fastq.gz:md5,e69b523dbbc543b86b078718b55db757"
+ ]
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-12T17:59:44.347549388",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "sarscov2 - fastq - both with single broken": {
+ "content": [
+ {
+ "reads": [
+ [
+ {
+ "id": "test_both",
+ "single_end": false
+ },
+ [
+ "test_both_R1.fastq.gz:md5,c1dbd30da9ad368bb7bf5a117bb765b2",
+ "test_both_R2.fastq.gz:md5,e69b523dbbc543b86b078718b55db757"
+ ]
+ ],
+ [
+ {
+ "id": "test_both",
+ "single_end": true
+ },
+ "test_both_single.fastq.gz:md5,8d33c2a78ed2a29919b44e64b1251d6d"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-12T17:59:52.287204859",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ }
+}
\ No newline at end of file
diff --git a/subworkflows/nf-core/fastq_preprocess_seqkit/tests/nextflow.config b/subworkflows/nf-core/fastq_preprocess_seqkit/tests/nextflow.config
new file mode 100644
index 0000000..5c3717f
--- /dev/null
+++ b/subworkflows/nf-core/fastq_preprocess_seqkit/tests/nextflow.config
@@ -0,0 +1,25 @@
+process {
+ withName: SEQKIT_SANA {
+ ext.prefix = { "${meta.id}_${meta.strandness}" }
+ }
+
+ withName: SEQKIT_SEQ {
+ ext.args = [
+ "--remove-gaps",
+ "--upper-case",
+ "--validate-seq",
+ "--min-len 30",
+ "--max-len 5000"
+ ].join(' ').trim()
+ ext.prefix = { "intermediate_seqkit_seq_${meta.strandness}" }
+ }
+
+ withName: SEQKIT_REPLACE {
+ ext.args = '-p "/" -r "_"'
+ ext.prefix = { "intermediate_seqkit_replace_${meta.strandness}" }
+ }
+
+ withName: SEQKIT_RMDUP {
+ ext.prefix = { "${meta.id}_${meta.strandness}" }
+ }
+}
diff --git a/subworkflows/nf-core/fastq_qc_stats/main.nf b/subworkflows/nf-core/fastq_qc_stats/main.nf
new file mode 100644
index 0000000..c61c289
--- /dev/null
+++ b/subworkflows/nf-core/fastq_qc_stats/main.nf
@@ -0,0 +1,60 @@
+include { FASTQC } from '../../../modules/nf-core/fastqc'
+include { SEQFU_CHECK } from '../../../modules/nf-core/seqfu/check'
+include { SEQFU_STATS } from '../../../modules/nf-core/seqfu/stats'
+include { SEQKIT_STATS } from '../../../modules/nf-core/seqkit/stats'
+include { SEQTK_COMP } from '../../../modules/nf-core/seqtk/comp'
+
+workflow FASTQ_QC_STATS {
+ take:
+ ch_reads // channel: [ val(meta), [ fastq ] ]
+ skip_fastqc // boolean
+ skip_seqfu_check // boolean
+ skip_seqfu_stats // boolean
+ skip_seqkit_stats // boolean
+ skip_seqtk_comp // boolean
+
+ main:
+ ch_fastqc_html = channel.empty()
+ ch_fastqc_zip = channel.empty()
+ ch_seqfu_check = channel.empty()
+ ch_seqfu_stats = channel.empty()
+ ch_seqfu_multiqc = channel.empty()
+ ch_seqkit_stats = channel.empty()
+ ch_seqtk_stats = channel.empty()
+
+ if (!skip_fastqc) {
+ FASTQC(ch_reads)
+ ch_fastqc_html = FASTQC.out.html
+ ch_fastqc_zip = FASTQC.out.zip
+ }
+
+ if (!skip_seqfu_check) {
+ SEQFU_CHECK(ch_reads)
+ ch_seqfu_check = SEQFU_CHECK.out.check
+ }
+
+ if (!skip_seqfu_stats) {
+ SEQFU_STATS(ch_reads)
+ ch_seqfu_stats = SEQFU_STATS.out.stats
+ ch_seqfu_multiqc = SEQFU_STATS.out.multiqc
+ }
+
+ if (!skip_seqkit_stats) {
+ SEQKIT_STATS(ch_reads)
+ ch_seqkit_stats = SEQKIT_STATS.out.stats
+ }
+
+ if (!skip_seqtk_comp) {
+ SEQTK_COMP(ch_reads)
+ ch_seqtk_stats = SEQTK_COMP.out.seqtk_stats
+ }
+
+ emit:
+ fastqc_html = ch_fastqc_html
+ fastqc_zip = ch_fastqc_zip
+ seqfu_check = ch_seqfu_check
+ seqfu_stats = ch_seqfu_stats
+ seqfu_multiqc = ch_seqfu_multiqc
+ seqkit_stats = ch_seqkit_stats
+ seqtk_stats = ch_seqtk_stats
+}
diff --git a/subworkflows/nf-core/fastq_qc_stats/meta.yml b/subworkflows/nf-core/fastq_qc_stats/meta.yml
new file mode 100644
index 0000000..0e8bc30
--- /dev/null
+++ b/subworkflows/nf-core/fastq_qc_stats/meta.yml
@@ -0,0 +1,76 @@
+# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json
+name: "fastq_qc_stats"
+description: Generate statistics for short read sequencing data using multiple tools
+keywords:
+ - fastq
+ - qc
+ - fastqc
+ - seqfu
+ - seqkit
+ - seqtk
+components:
+ - fastqc
+ - seqfu/check
+ - seqfu/stats
+ - seqkit/stats
+ - seqtk/comp
+input:
+ - reads:
+ type: file
+ description: |
+ List of input FastQ files of size 1 and 2 for single-end and paired-end data,
+ respectively.
+ - skip_fastqc:
+ type: boolean
+ description: |
+ Skip fastqc process
+ - skip_seqfu_check:
+ type: boolean
+ description: |
+ Skip seqfu_check process
+ - skip_seqfu_stats:
+ type: boolean
+ description: |
+ Skip seqfu_stats process
+ - skip_seqkit_stats:
+ type: boolean
+ description: |
+ Skip seqkit_stats process
+ - skip_seqtk_comp:
+ type: boolean
+ description: |
+ Skip seqtk_comp process
+output:
+ - fastqc_html:
+ type: file
+ description: FastQC report
+ pattern: "*_fastqc.html"
+ - fastqc_zip:
+ type: file
+ description: FastQC report archive
+ pattern: "*_fastqc.zip"
+ - seqfu_check:
+ type: file
+ description: seqfu check tsv report
+ pattern: "*.tsv"
+ - seqfu_stats:
+ type: file
+ description: seqfu stats tsv report
+ pattern: "*.tsv"
+ - seqfu_multiqc:
+ type: file
+ description: seqfu stats MultiQC report
+ pattern: "*_mqc.txt"
+ - seqkit_stats:
+ type: file
+ description: seqkit stats report
+ pattern: "*.tsv"
+ - versions:
+ type: file
+ description: File containing software versions
+ pattern: "versions.yml"
+authors:
+ - "@pablo-scd"
+maintainers:
+ - "@pablo-scd"
+ - "@vagkaratzas"
diff --git a/subworkflows/nf-core/fastq_qc_stats/tests/main.nf.test b/subworkflows/nf-core/fastq_qc_stats/tests/main.nf.test
new file mode 100644
index 0000000..3706439
--- /dev/null
+++ b/subworkflows/nf-core/fastq_qc_stats/tests/main.nf.test
@@ -0,0 +1,167 @@
+nextflow_workflow {
+ name "Test Subworkflow FASTQ_QC_STATS"
+ script "../main.nf"
+ workflow "FASTQ_QC_STATS"
+
+ tag "subworkflows"
+ tag "subworkflows_nfcore"
+ tag "subworkflows/fastq_qc_stats"
+ tag "fastqc"
+ tag "seqfu"
+ tag "seqfu/check"
+ tag "seqfu/stats"
+ tag "seqkit"
+ tag "seqkit/stats"
+ tag "seqtk"
+ tag "seqtk/comp"
+
+ test("sarscov2 - fastq - single_end") {
+ when {
+ workflow {
+ """
+ input[0] = channel.of([
+ [ id:'test_single', single_end:true ],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ])
+ input[1] = false
+ input[2] = false
+ input[3] = false
+ input[4] = false
+ input[5] = false
+ """
+ }
+ }
+ then {
+ assertAll(
+ { assert workflow.success },
+ { assert snapshot(
+ file(workflow.out.fastqc_html[0][1]).name,
+ file(workflow.out.fastqc_zip[0][1]).name,
+ workflow.out.seqfu_check,
+ workflow.out.seqfu_stats,
+ workflow.out.seqfu_multiqc,
+ workflow.out.seqkit_stats,
+ workflow.out.seqtk_stats
+ ).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - single_end - skip all") {
+ when {
+ workflow {
+ """
+ input[0] = channel.of([
+ [ id:'test_single', single_end:true ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ])
+ input[1] = true
+ input[2] = true
+ input[3] = true
+ input[4] = true
+ input[5] = true
+ """
+ }
+ }
+ then {
+ assertAll(
+ { assert workflow.success },
+ { assert snapshot(workflow.out).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - paired_end") {
+ when {
+ workflow {
+ """
+ input[0] = channel.of([
+ [ id:'test_paired', single_end:false ], // meta map
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ ]
+ ])
+ input[1] = false
+ input[2] = false
+ input[3] = false
+ input[4] = false
+ input[5] = false
+ """
+ }
+ }
+ then {
+ assertAll(
+ { assert workflow.success },
+ { assert snapshot(
+ file(workflow.out.fastqc_html[0][1][0]).name,
+ file(workflow.out.fastqc_zip[0][1][0]).name,
+ file(workflow.out.fastqc_html[0][1][1]).name,
+ file(workflow.out.fastqc_zip[0][1][1]).name,
+ workflow.out.seqfu_check,
+ workflow.out.seqfu_stats,
+ workflow.out.seqfu_multiqc,
+ workflow.out.seqkit_stats,
+ workflow.out.seqtk_stats
+ ).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - single_end - stub") {
+
+ options "-stub"
+
+ when {
+ workflow {
+ """
+ input[0] = channel.of([
+ [ id:'test_single', single_end:true ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ])
+ input[1] = false
+ input[2] = false
+ input[3] = false
+ input[4] = false
+ input[5] = false
+ """
+ }
+ }
+ then {
+ assertAll(
+ { assert workflow.success},
+ { assert snapshot(sanitizeOutput(workflow.out)).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - paired_end - stub") {
+
+ options "-stub"
+
+ when {
+ workflow {
+ """
+ input[0] = channel.of([
+ [ id:'test_paired', single_end:false ], // meta map
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ ]
+ ])
+ input[1] = false
+ input[2] = false
+ input[3] = false
+ input[4] = false
+ input[5] = false
+ """
+ }
+ }
+ then {
+ assertAll(
+ { assert workflow.success},
+ { assert snapshot(sanitizeOutput(workflow.out)).match() }
+ )
+ }
+ }
+}
diff --git a/subworkflows/nf-core/fastq_qc_stats/tests/main.nf.test.snap b/subworkflows/nf-core/fastq_qc_stats/tests/main.nf.test.snap
new file mode 100644
index 0000000..d61689e
--- /dev/null
+++ b/subworkflows/nf-core/fastq_qc_stats/tests/main.nf.test.snap
@@ -0,0 +1,317 @@
+{
+ "sarscov2 - fastq - single_end - stub": {
+ "content": [
+ {
+ "fastqc_html": [
+ [
+ {
+ "id": "test_single",
+ "single_end": true
+ },
+ "test_single.html:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "fastqc_zip": [
+ [
+ {
+ "id": "test_single",
+ "single_end": true
+ },
+ "test_single.zip:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "seqfu_check": [
+ [
+ {
+ "id": "test_single",
+ "single_end": true
+ },
+ "test_single.tsv:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "seqfu_multiqc": [
+ [
+ {
+ "id": "test_single",
+ "single_end": true
+ },
+ "test_single_mqc.txt:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "seqfu_stats": [
+ [
+ {
+ "id": "test_single",
+ "single_end": true
+ },
+ "test_single.tsv:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "seqkit_stats": [
+ [
+ {
+ "id": "test_single",
+ "single_end": true
+ },
+ "test_single.tsv:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "seqtk_stats": [
+ [
+ {
+ "id": "test_single",
+ "single_end": true
+ },
+ "test_single.seqtk_stats.tsv:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-02-20T11:46:39.792312215",
+ "meta": {
+ "nf-test": "0.9.4",
+ "nextflow": "25.10.4"
+ }
+ },
+ "sarscov2 - fastq - single_end - skip all": {
+ "content": [
+ {
+ "0": [
+
+ ],
+ "1": [
+
+ ],
+ "2": [
+
+ ],
+ "3": [
+
+ ],
+ "4": [
+
+ ],
+ "5": [
+
+ ],
+ "6": [
+
+ ],
+ "fastqc_html": [
+
+ ],
+ "fastqc_zip": [
+
+ ],
+ "seqfu_check": [
+
+ ],
+ "seqfu_multiqc": [
+
+ ],
+ "seqfu_stats": [
+
+ ],
+ "seqkit_stats": [
+
+ ],
+ "seqtk_stats": [
+
+ ]
+ }
+ ],
+ "timestamp": "2026-02-20T11:46:19.133405302",
+ "meta": {
+ "nf-test": "0.9.4",
+ "nextflow": "25.10.4"
+ }
+ },
+ "sarscov2 - fastq - single_end": {
+ "content": [
+ "test_single_fastqc.html",
+ "test_single_fastqc.zip",
+ [
+ [
+ {
+ "id": "test_single",
+ "single_end": true
+ },
+ "test_single.tsv:md5,4c6409169772005cfb06be9e41f2c1e2"
+ ]
+ ],
+ [
+ [
+ {
+ "id": "test_single",
+ "single_end": true
+ },
+ "test_single.tsv:md5,7573d0c83cfc9af6e1ced67a45265381"
+ ]
+ ],
+ [
+ [
+ {
+ "id": "test_single",
+ "single_end": true
+ },
+ "test_single_mqc.txt:md5,a039b8c1cc923db88d2484d3abbf00fe"
+ ]
+ ],
+ [
+ [
+ {
+ "id": "test_single",
+ "single_end": true
+ },
+ "test_single.tsv:md5,76d5ab444de880ef443f358841e44cf9"
+ ]
+ ],
+ [
+ [
+ {
+ "id": "test_single",
+ "single_end": true
+ },
+ "test_single.seqtk_stats.tsv:md5,162f75dd3cfce99bd195aa5f8bd467d5"
+ ]
+ ]
+ ],
+ "timestamp": "2026-06-12T17:45:03.575449675",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "sarscov2 - fastq - paired_end": {
+ "content": [
+ "test_paired_1_fastqc.html",
+ "test_paired_1_fastqc.zip",
+ "test_paired_2_fastqc.html",
+ "test_paired_2_fastqc.zip",
+ [
+ [
+ {
+ "id": "test_paired",
+ "single_end": false
+ },
+ "test_paired.tsv:md5,c3e6eae028f23041bd09cac9c371f318"
+ ]
+ ],
+ [
+ [
+ {
+ "id": "test_paired",
+ "single_end": false
+ },
+ "test_paired.tsv:md5,d9277db149ae44112a704b66d72ec07e"
+ ]
+ ],
+ [
+ [
+ {
+ "id": "test_paired",
+ "single_end": false
+ },
+ "test_paired_mqc.txt:md5,082187706aad0e06b86297d4fcd60bec"
+ ]
+ ],
+ [
+ [
+ {
+ "id": "test_paired",
+ "single_end": false
+ },
+ "test_paired.tsv:md5,2d96d9f7d85456090e138b81bf97bcd5"
+ ]
+ ],
+ [
+ [
+ {
+ "id": "test_paired",
+ "single_end": false
+ },
+ "test_paired.seqtk_stats.tsv:md5,162f75dd3cfce99bd195aa5f8bd467d5"
+ ]
+ ]
+ ],
+ "timestamp": "2026-06-12T17:45:19.114777238",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "sarscov2 - fastq - paired_end - stub": {
+ "content": [
+ {
+ "fastqc_html": [
+ [
+ {
+ "id": "test_paired",
+ "single_end": false
+ },
+ "test_paired.html:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "fastqc_zip": [
+ [
+ {
+ "id": "test_paired",
+ "single_end": false
+ },
+ "test_paired.zip:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "seqfu_check": [
+ [
+ {
+ "id": "test_paired",
+ "single_end": false
+ },
+ "test_paired.tsv:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "seqfu_multiqc": [
+ [
+ {
+ "id": "test_paired",
+ "single_end": false
+ },
+ "test_paired_mqc.txt:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "seqfu_stats": [
+ [
+ {
+ "id": "test_paired",
+ "single_end": false
+ },
+ "test_paired.tsv:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "seqkit_stats": [
+ [
+ {
+ "id": "test_paired",
+ "single_end": false
+ },
+ "test_paired.tsv:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "seqtk_stats": [
+ [
+ {
+ "id": "test_paired",
+ "single_end": false
+ },
+ "test_paired.seqtk_stats.tsv:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-02-20T11:46:48.694731294",
+ "meta": {
+ "nf-test": "0.9.4",
+ "nextflow": "25.10.4"
+ }
+ }
+}
\ No newline at end of file
diff --git a/subworkflows/nf-core/fastq_removeadapters_merge/main.nf b/subworkflows/nf-core/fastq_removeadapters_merge/main.nf
new file mode 100644
index 0000000..d67509d
--- /dev/null
+++ b/subworkflows/nf-core/fastq_removeadapters_merge/main.nf
@@ -0,0 +1,133 @@
+// both SE and PE
+include { TRIMMOMATIC } from '../../../modules/nf-core/trimmomatic/main'
+include { CUTADAPT } from '../../../modules/nf-core/cutadapt/main'
+include { TRIMGALORE } from '../../../modules/nf-core/trimgalore/main'
+include { BBMAP_BBDUK } from '../../../modules/nf-core/bbmap/bbduk/main'
+include { LEEHOM } from '../../../modules/nf-core/leehom/main'
+// both SE and PE, plus merging
+include { FASTP } from '../../../modules/nf-core/fastp/main'
+include { ADAPTERREMOVAL as ADAPTERREMOVAL_SE } from '../../../modules/nf-core/adapterremoval/main'
+include { ADAPTERREMOVAL as ADAPTERREMOVAL_PE } from '../../../modules/nf-core/adapterremoval/main'
+// helper module for concatenating adapterremoval paired-end processed reads
+include { CAT_FASTQ } from '../../../modules/nf-core/cat/fastq/main'
+
+workflow FASTQ_REMOVEADAPTERS_MERGE {
+
+ take:
+ ch_input_reads // channel: [mandatory] meta, reads
+ val_adapter_tool // string: [mandatory] tool_name // choose from: ["trimmomatic", "cutadapt", "trimgalore", "bbduk", "leehom", "fastp", "adapterremoval"]
+ ch_custom_adapters_file // channel: [optional] {fasta,txt} // fasta, for bbduk or fastp, or txt, for adapterremoval
+ val_save_merged // boolean: [mandatory] if true, will return the merged reads instead, for fastp and adapterremoval
+ val_fastp_discard_trimmed_pass // boolean: [mandatory] // only for fastp
+ val_fastp_save_trimmed_fail // boolean: [mandatory] // only for fastp
+
+ main:
+
+ ch_discarded_reads = channel.empty() // from trimmomatic, trimgalore, leehom, fastp, adapterremoval
+ ch_log = channel.empty() // from trimmomatic, trimgalore, fastp
+ ch_report = channel.empty() // from trimmomatic, trimgalore, fastp
+ ch_multiqc_files = channel.empty() // from trimmomatic, cutadapt, bbduk, leehom, fastp, adapterremoval
+
+ if (val_adapter_tool == "trimmomatic") {
+ TRIMMOMATIC( ch_input_reads )
+
+ ch_processed_reads = TRIMMOMATIC.out.trimmed_reads
+ ch_discarded_reads = ch_discarded_reads.mix(TRIMMOMATIC.out.unpaired_reads.transpose()) // .transpose() because paired reads will output 2 unpaired files in an array
+ ch_log = TRIMMOMATIC.out.trim_log
+ ch_report = TRIMMOMATIC.out.summary
+ ch_multiqc_files = ch_multiqc_files.mix(TRIMMOMATIC.out.out_log)
+ } else if (val_adapter_tool == "cutadapt") {
+ CUTADAPT( ch_input_reads )
+
+ ch_processed_reads = CUTADAPT.out.reads
+ ch_multiqc_files = ch_multiqc_files.mix(CUTADAPT.out.log)
+ } else if (val_adapter_tool == "trimgalore") {
+ TRIMGALORE( ch_input_reads )
+
+ ch_processed_reads = TRIMGALORE.out.reads
+ ch_discarded_reads = ch_discarded_reads.mix(TRIMGALORE.out.unpaired)
+ ch_log = TRIMGALORE.out.log
+ ch_report = TRIMGALORE.out.html.mix(TRIMGALORE.out.zip)
+ } else if (val_adapter_tool == "bbduk") {
+ BBMAP_BBDUK( ch_input_reads, ch_custom_adapters_file )
+
+ ch_processed_reads = BBMAP_BBDUK.out.reads
+ ch_multiqc_files = ch_multiqc_files.mix(BBMAP_BBDUK.out.log)
+ } else if (val_adapter_tool == "leehom") {
+ LEEHOM( ch_input_reads )
+
+ ch_processed_reads = LEEHOM.out.fq_pass
+ .join(LEEHOM.out.unmerged_r1_fq_pass, by: 0, remainder: true)
+ .join(LEEHOM.out.unmerged_r2_fq_pass, by: 0, remainder: true)
+ .map { meta, single, r1, r2 ->
+ if (meta.single_end) {
+ return [meta, single]
+ } else {
+ return [meta, [r1, r2]]
+ }
+ }
+ ch_discarded_reads = ch_discarded_reads.mix(LEEHOM.out.fq_fail, LEEHOM.out.unmerged_r1_fq_fail, LEEHOM.out.unmerged_r2_fq_fail)
+ ch_multiqc_files = ch_multiqc_files.mix(LEEHOM.out.log)
+ } else if (val_adapter_tool == "fastp") {
+ FASTP(
+ ch_input_reads.map { meta, files -> [ meta, files, ch_custom_adapters_file ] },
+ val_fastp_discard_trimmed_pass,
+ val_fastp_save_trimmed_fail,
+ val_save_merged
+ )
+
+ if (val_save_merged) {
+ ch_processed_reads = FASTP.out.reads_merged
+ } else {
+ ch_processed_reads = FASTP.out.reads
+ }
+ ch_discarded_reads = ch_discarded_reads.mix(FASTP.out.reads_fail.transpose()) // .transpose() because paired reads have 3 fail files in an array
+ ch_log = FASTP.out.log
+ ch_report = FASTP.out.html
+ ch_multiqc_files = ch_multiqc_files.mix(FASTP.out.json)
+ } else if (val_adapter_tool == "adapterremoval") {
+ ch_adapterremoval_in = ch_input_reads
+ .branch { meta, _reads ->
+ single: meta.single_end
+ paired: !meta.single_end
+ }
+
+ ADAPTERREMOVAL_SE( ch_adapterremoval_in.single, ch_custom_adapters_file )
+ ADAPTERREMOVAL_PE( ch_adapterremoval_in.paired, ch_custom_adapters_file )
+
+ if (val_save_merged) { // merge
+ ch_concat_fastq = channel.empty()
+ .mix(
+ ADAPTERREMOVAL_PE.out.collapsed,
+ ADAPTERREMOVAL_PE.out.collapsed_truncated,
+ ADAPTERREMOVAL_PE.out.singles_truncated,
+ )
+ .map { meta, reads ->
+ def meta_new = meta.clone()
+ meta_new.single_end = true
+ [meta_new, reads]
+ }
+ .groupTuple()
+ // Paired-end reads cause a nested tuple during grouping.
+ // We want to present a flat list of files to `CAT_FASTQ`.
+ .map { meta, fastq -> [meta, fastq.flatten()] }
+
+ CAT_FASTQ( ch_concat_fastq )
+
+ ch_processed_reads = CAT_FASTQ.out.reads.mix(ADAPTERREMOVAL_SE.out.singles_truncated)
+ } else { // no merge
+ ch_processed_reads = ADAPTERREMOVAL_PE.out.paired_truncated.mix(ADAPTERREMOVAL_SE.out.singles_truncated)
+ }
+ ch_discarded_reads = ch_discarded_reads.mix(ADAPTERREMOVAL_SE.out.discarded, ADAPTERREMOVAL_PE.out.discarded)
+ ch_multiqc_files = ch_multiqc_files.mix(ADAPTERREMOVAL_PE.out.settings, ADAPTERREMOVAL_SE.out.settings)
+ } else {
+ error('Please choose one of the available adapter removal and merging tools: ["trimmomatic", "cutadapt", "trimgalore", "bbduk", "leehom", "fastp", "adapterremoval"]')
+ }
+
+ emit:
+ processed_reads = ch_processed_reads // channel: [ val(meta), [ fastq.gz ] ]
+ discarded_reads = ch_discarded_reads // channel: [ val(meta), [ fastq.gz ] ]
+ logfile = ch_log // channel: [ val(meta), [ {log,txt} ] ]
+ report = ch_report // channel: [ val(meta), [ {summary,html,zip} ] ]
+ multiqc_files = ch_multiqc_files
+}
diff --git a/subworkflows/nf-core/fastq_removeadapters_merge/meta.yml b/subworkflows/nf-core/fastq_removeadapters_merge/meta.yml
new file mode 100644
index 0000000..f89e7fe
--- /dev/null
+++ b/subworkflows/nf-core/fastq_removeadapters_merge/meta.yml
@@ -0,0 +1,86 @@
+# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json
+name: "fastq_removeadapters_merge"
+description: Remove adapters and merge reads based on various module choices
+keywords:
+ - adapters
+ - removal
+ - short reads
+ - merge
+ - trim
+components:
+ - trimmomatic
+ - cutadapt
+ - trimgalore
+ - bbmap/bbduk
+ - leehom
+ - fastp
+ - adapterremoval
+ - cat/fastq
+input:
+ - ch_input_reads:
+ type: file
+ description: |
+ List of FastQ files of size 1 and 2 for single-end and paired-end data, respectively.
+ Structure: [ val(meta), [ path(reads) ] ]
+ - val_adapter_tool:
+ type: string
+ description: |
+ Choose one of the available adapter removal and/or merging tools
+ enum: ["trimmomatic", "cutadapt", "trimgalore", "bbduk", "leehom", "fastp", "adapterremoval"]
+ - ch_custom_adapters_file:
+ type: file
+ description: |
+ Optional reference files, containing adapter and/or contaminant sequences for removal.
+ In fasta format for bbmap/bbduk and fastp, or in text format for AdapterRemoval (one adapter per line).
+ - val_save_merged:
+ type: boolean
+ description: |
+ Specify true to output merged reads instead
+ Used by fastp and adapterremoval
+ - val_fastp_discard_trimmed_pass:
+ type: boolean
+ description: |
+ Used only by fastp.
+ Specify true to not write any reads that pass trimming thresholds from the fastp process.
+ This can be used to use fastp for the output report only.
+ - val_fastp_save_trimmed_fail:
+ type: boolean
+ description: |
+ Used only by fastp.
+ Specify true to save files that failed to pass fastp trimming thresholds
+output:
+ - processed_reads:
+ type: file
+ description: |
+ Structure: [ val(meta), path(fastq.gz) ]
+ The trimmed/modified single or paired end or merged fastq reads
+ pattern: "*.fastq.gz"
+ - discarded_reads:
+ type: file
+ description: |
+ Structure: [ val(meta), path(fastq.gz) ]
+ The discarded reads
+ pattern: "*.fastq.gz"
+ - logfile:
+ type: file
+ description: |
+ Execution log file
+ (trimmomatic {log}, trimgalore {txt}, fastp {log})
+ pattern: "*.{log,txt}"
+ - report:
+ type: file
+ description: |
+ Execution report
+ (trimmomatic {summary}, trimgalore {html,zip}, fastp {html})
+ pattern: "*.{summary,html,zip}"
+ - multiqc_files:
+ type: file
+ description: |
+ MultiQC-compatible output files from tools used in preprocessing
+ (trimmomatic, cutadapt, bbduk, leehom, fastp, adapterremoval)
+authors:
+ - "@kornkv"
+ - "@vagkaratzas"
+maintainers:
+ - "@kornkv"
+ - "@vagkaratzas"
diff --git a/subworkflows/nf-core/fastq_removeadapters_merge/tests/main.nf.test b/subworkflows/nf-core/fastq_removeadapters_merge/tests/main.nf.test
new file mode 100644
index 0000000..378a939
--- /dev/null
+++ b/subworkflows/nf-core/fastq_removeadapters_merge/tests/main.nf.test
@@ -0,0 +1,300 @@
+nextflow_workflow {
+
+ name "Test Subworkflow FASTQ_REMOVEADAPTERS_MERGE"
+ script "../main.nf"
+ workflow "FASTQ_REMOVEADAPTERS_MERGE"
+
+ tag "subworkflows"
+ tag "subworkflows_nfcore"
+ tag "subworkflows/fastq_removeadapters_merge"
+ tag "trimmomatic"
+ tag "cutadapt"
+ tag "trimgalore"
+ tag "bbmap"
+ tag "bbmap/bbduk"
+ tag "leehom"
+ tag "fastp"
+ tag "adapterremoval"
+ tag "cat"
+ tag "cat/fastq"
+
+ test("sarscov2 - fastq - trimmomatic - single-end") {
+ config "./nextflow_SE.config"
+ when {
+ params {
+ save_merged = false
+ adapterremoval_args = save_merged ? "--collapse" : ""
+ }
+ workflow {
+ """
+ input[0] = channel.of([
+ [ id:'test', single_end:true ],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ])
+ input[1] = "trimmomatic" // val_adapter_tool
+ input[2] = [] // ch_custom_adapters_file
+ input[3] = params.save_merged // val_save_merged
+ input[4] = false // val_fastp_discard_trimmed_pass
+ input[5] = false // val_fastp_save_trimmed_fail
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert workflow.success },
+ { assert snapshot(
+ workflow.out.processed_reads[0][1],
+ workflow.out.logfile[0][1],
+ workflow.out.report[0][1],
+ workflow.out.multiqc_files.collect { file(it[1]).name },
+ ).match()}
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - cutadapt - paired-end") {
+ config "./nextflow.config"
+ when {
+ params {
+ save_merged = false
+ adapterremoval_args = save_merged ? "--collapse" : ""
+ }
+ workflow {
+ """
+ input[0] = channel.of([
+ [ id:'test', single_end:false ],
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ ]
+ ])
+ input[1] = "cutadapt" // val_adapter_tool
+ input[2] = [] // ch_custom_adapters_file
+ input[3] = params.save_merged // val_save_merged
+ input[4] = false // val_fastp_discard_trimmed_pass
+ input[5] = false // val_fastp_save_trimmed_fail
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert workflow.success },
+ { assert snapshot(
+ workflow.out.processed_reads[0][1],
+ workflow.out.multiqc_files.collect { file(it[1]).name }
+ ).match()}
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - trimgalore - single-end") {
+ when {
+ params {
+ save_merged = false
+ adapterremoval_args = save_merged ? "--collapse" : ""
+ }
+ workflow {
+ """
+ input[0] = channel.of([
+ [ id:'test', single_end:true ],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ])
+ input[1] = "trimgalore" // val_adapter_tool
+ input[2] = [] // ch_custom_adapters_file
+ input[3] = params.save_merged // val_save_merged
+ input[4] = false // val_fastp_discard_trimmed_pass
+ input[5] = false // val_fastp_save_trimmed_fail
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert workflow.success },
+ { assert snapshot(
+ workflow.out.processed_reads[0][1],
+ path(workflow.out.logfile[0][1]).readLines().size()
+ ).match()}
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - bbduk - paired-end") {
+ config "./nextflow.config"
+ when {
+ params {
+ save_merged = false
+ adapterremoval_args = save_merged ? "--collapse" : ""
+ }
+ workflow {
+ """
+ input[0] = channel.of([
+ [ id:'test_bbduk', single_end:false ],
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ ]
+ ])
+ input[1] = "bbduk" // val_adapter_tool
+ input[2] = [] // ch_custom_adapters_file
+ input[3] = params.save_merged // val_save_merged
+ input[4] = false // val_fastp_discard_trimmed_pass
+ input[5] = false // val_fastp_save_trimmed_fail
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert workflow.success },
+ { assert snapshot(
+ workflow.out.processed_reads[0][1],
+ workflow.out.multiqc_files.collect { file(it[1]).name }
+ ).match()}
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - leehom - single-end") {
+ when {
+ params {
+ save_merged = false
+ adapterremoval_args = save_merged ? "--collapse" : ""
+ }
+ workflow {
+ """
+ input[0] = channel.of([
+ [ id:'test', single_end:true ],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ])
+ input[1] = "leehom" // val_adapter_tool
+ input[2] = [] // ch_custom_adapters_file
+ input[3] = params.save_merged // val_save_merged
+ input[4] = false // val_fastp_discard_trimmed_pass
+ input[5] = false // val_fastp_save_trimmed_fail
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert workflow.success },
+ { assert snapshot(
+ workflow.out.processed_reads[0][1],
+ workflow.out.discarded_reads.collect { file(it[1]).name },
+ workflow.out.multiqc_files.collect { file(it[1]).name }
+ ).match()}
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - fastp - single-end") {
+ when {
+ params {
+ save_merged = false
+ adapterremoval_args = save_merged ? "--collapse" : ""
+ }
+ workflow {
+ """
+ input[0] = channel.of([
+ [ id:'test', single_end:true ],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ])
+ input[1] = "fastp" // val_adapter_tool
+ input[2] = [] // ch_custom_adapters_file
+ input[3] = params.save_merged // val_save_merged
+ input[4] = false // val_fastp_discard_trimmed_pass
+ input[5] = true // val_fastp_save_trimmed_fail
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert workflow.success },
+ { assert snapshot(
+ workflow.out.processed_reads[0][1],
+ workflow.out.discarded_reads[0][1],
+ path(workflow.out.logfile[0][1]).readLines().size(),
+ path(workflow.out.report[0][1]).readLines().size(),
+ workflow.out.multiqc_files.collect { file(it[1]).name }
+ ).match()}
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - adapterremoval - paired-end - merge") {
+ config "./nextflow_PE.config"
+ when {
+ params {
+ save_merged = true
+ adapterremoval_args = save_merged ? "--collapse" : ""
+ }
+ workflow {
+ """
+ input[0] = channel.of([
+ [ id:'test', single_end:false ],
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ ]
+ ])
+ input[1] = "adapterremoval" // val_adapter_tool
+ input[2] = [] // ch_custom_adapters_file
+ input[3] = params.save_merged // val_save_merged
+ input[4] = false // val_fastp_discard_trimmed_pass
+ input[5] = false // val_fastp_save_trimmed_fail
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert workflow.success },
+ { assert snapshot(
+ path(workflow.out.processed_reads[0][1]).linesGzip.size(),
+ workflow.out.discarded_reads.collect { file(it[1]).name },
+ workflow.out.multiqc_files.collect { file(it[1]).name }
+ ).match()}
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - trimmomatic - paired-end - stub") {
+ config "./nextflow_PE.config"
+ options "-stub"
+
+ when {
+ params {
+ save_merged = false
+ adapterremoval_args = save_merged ? "--collapse" : ""
+ }
+ workflow {
+ """
+ input[0] = [
+ [ id:'test', single_end:false ],
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ ]
+ ]
+ input[1] = "trimmomatic" // val_adapter_tool
+ input[2] = [] // ch_custom_adapters_file
+ input[3] = params.save_merged // val_save_merged
+ input[4] = false // val_fastp_discard_trimmed_pass
+ input[5] = false // val_fastp_save_trimmed_fail
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert workflow.success },
+ { assert snapshot(workflow.out).match() }
+ )
+ }
+ }
+
+}
diff --git a/subworkflows/nf-core/fastq_removeadapters_merge/tests/main.nf.test.snap b/subworkflows/nf-core/fastq_removeadapters_merge/tests/main.nf.test.snap
new file mode 100644
index 0000000..46a5e9f
--- /dev/null
+++ b/subworkflows/nf-core/fastq_removeadapters_merge/tests/main.nf.test.snap
@@ -0,0 +1,229 @@
+{
+ "sarscov2 - fastq - trimmomatic - single-end": {
+ "content": [
+ "test.SE.paired.trim.fastq.gz:md5,e68abbd3b88f7ec12940a4f5c2b8bfb9",
+ "test_trim.log:md5,e4c3f619e9b0e26847f8f3e3d9af319b",
+ "test.summary:md5,24c973237557a1439c775ca19a5deaa5",
+ [
+ "test_out.log"
+ ]
+ ],
+ "timestamp": "2026-02-11T11:27:07.921874",
+ "meta": {
+ "nf-test": "0.9.3",
+ "nextflow": "25.10.3"
+ }
+ },
+ "sarscov2 - fastq - leehom - single-end": {
+ "content": [
+ "test.fq.gz:md5,304af6f5f6bb58c70abf7924eacfa175",
+ [
+ "test.fail.fq.gz"
+ ],
+ [
+ "test.log"
+ ]
+ ],
+ "timestamp": "2026-06-08T19:03:09.749238762",
+ "meta": {
+ "nf-test": "0.9.4",
+ "nextflow": "26.03.0"
+ }
+ },
+ "sarscov2 - fastq - trimmomatic - paired-end - stub": {
+ "content": [
+ {
+ "0": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test.paired.trim_1.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940",
+ "test.paired.trim_2.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ]
+ ],
+ "1": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.unpaired.trim_1.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ],
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.unpaired.trim_2.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "2": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test_trim.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "3": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.summary:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "4": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test_out.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "discarded_reads": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.unpaired.trim_1.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ],
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.unpaired.trim_2.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ],
+ "logfile": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test_trim.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "multiqc_files": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test_out.log:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ],
+ "processed_reads": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ [
+ "test.paired.trim_1.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940",
+ "test.paired.trim_2.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ]
+ ],
+ "report": [
+ [
+ {
+ "id": "test",
+ "single_end": false
+ },
+ "test.summary:md5,d41d8cd98f00b204e9800998ecf8427e"
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-08T19:03:48.202547582",
+ "meta": {
+ "nf-test": "0.9.4",
+ "nextflow": "26.03.0"
+ }
+ },
+ "sarscov2 - fastq - cutadapt - paired-end": {
+ "content": [
+ [
+ "test_1.trim.fastq.gz:md5,682dab8e982563cffac2bd60bf7444f4",
+ "test_2.trim.fastq.gz:md5,1a1e8ab23af40e6474be0cdf82fab907"
+ ],
+ [
+ "test.cutadapt.log"
+ ]
+ ],
+ "timestamp": "2025-12-19T11:19:27.959952445",
+ "meta": {
+ "nf-test": "0.9.3",
+ "nextflow": "25.10.2"
+ }
+ },
+ "sarscov2 - fastq - bbduk - paired-end": {
+ "content": [
+ [
+ "test_bbduk_1.fastq.gz:md5,4161df271f9bfcd25d5845a1e220dbec",
+ "test_bbduk_2.fastq.gz:md5,2ebae722295ea66d84075a3b042e2b42"
+ ],
+ [
+ "test_bbduk.bbduk.log"
+ ]
+ ],
+ "timestamp": "2026-03-12T09:50:13.626050809",
+ "meta": {
+ "nf-test": "0.9.3",
+ "nextflow": "25.10.2"
+ }
+ },
+ "sarscov2 - fastq - trimgalore - single-end": {
+ "content": [
+ "test_trimmed.fq.gz:md5,566d44cca0d22c522d6cf0e50c7165dc",
+ 51
+ ],
+ "timestamp": "2026-05-05T09:08:25.274389209",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.0"
+ }
+ },
+ "sarscov2 - fastq - adapterremoval - paired-end - merge": {
+ "content": [
+ 336,
+ [
+ "test.discarded.fastq.gz"
+ ],
+ [
+ "test.settings"
+ ]
+ ],
+ "timestamp": "2026-03-11T16:49:49.466864",
+ "meta": {
+ "nf-test": "0.9.4",
+ "nextflow": "25.10.4"
+ }
+ },
+ "sarscov2 - fastq - fastp - single-end": {
+ "content": [
+ "test.fastp.fastq.gz:md5,67b2bbae47f073e05a97a9c2edce23c7",
+ "test.fail.fastq.gz:md5,3e4aaadb66a5b8fc9b881bf39c227abd",
+ 33,
+ 2395,
+ [
+ "test.fastp.json"
+ ]
+ ],
+ "timestamp": "2026-06-08T19:03:25.728460818",
+ "meta": {
+ "nf-test": "0.9.4",
+ "nextflow": "26.03.0"
+ }
+ }
+}
\ No newline at end of file
diff --git a/subworkflows/nf-core/fastq_removeadapters_merge/tests/nextflow.config b/subworkflows/nf-core/fastq_removeadapters_merge/tests/nextflow.config
new file mode 100644
index 0000000..e1badf4
--- /dev/null
+++ b/subworkflows/nf-core/fastq_removeadapters_merge/tests/nextflow.config
@@ -0,0 +1,9 @@
+process {
+ withName: 'CUTADAPT' {
+ ext.args = '-q 25'
+ }
+
+ withName: 'BBMAP_BBDUK' {
+ ext.args = 'trimq=10 qtrim=r'
+ }
+}
diff --git a/subworkflows/nf-core/fastq_removeadapters_merge/tests/nextflow_PE.config b/subworkflows/nf-core/fastq_removeadapters_merge/tests/nextflow_PE.config
new file mode 100644
index 0000000..dc4eef5
--- /dev/null
+++ b/subworkflows/nf-core/fastq_removeadapters_merge/tests/nextflow_PE.config
@@ -0,0 +1,9 @@
+process {
+ withName: 'TRIMMOMATIC' {
+ ext.args = 'ILLUMINACLIP:TruSeq3-PE.fa:2:30:10 LEADING:3 TRAILING:3 SLIDINGWINDOW:4:15 MINLEN:36'
+ }
+
+ withName: 'ADAPTERREMOVAL_PE' {
+ ext.args = params.adapterremoval_args
+ }
+}
diff --git a/subworkflows/nf-core/fastq_removeadapters_merge/tests/nextflow_SE.config b/subworkflows/nf-core/fastq_removeadapters_merge/tests/nextflow_SE.config
new file mode 100644
index 0000000..82c1867
--- /dev/null
+++ b/subworkflows/nf-core/fastq_removeadapters_merge/tests/nextflow_SE.config
@@ -0,0 +1,9 @@
+process {
+ withName: 'TRIMMOMATIC' {
+ ext.args = 'ILLUMINACLIP:TruSeq3-SE:2:30:10 LEADING:3 TRAILING:3 SLIDINGWINDOW:4:15 MINLEN:36'
+ }
+
+ withName: 'ADAPTERREMOVAL_SE' {
+ ext.args = params.adapterremoval_args
+ }
+}
diff --git a/subworkflows/nf-core/fastq_sanitise_seqkit/main.nf b/subworkflows/nf-core/fastq_sanitise_seqkit/main.nf
new file mode 100644
index 0000000..f19b73f
--- /dev/null
+++ b/subworkflows/nf-core/fastq_sanitise_seqkit/main.nf
@@ -0,0 +1,47 @@
+include { SEQKIT_SANA } from '../../../modules/nf-core/seqkit/sana'
+include { SEQKIT_PAIR } from '../../../modules/nf-core/seqkit/pair'
+
+workflow FASTQ_SANITISE_SEQKIT {
+ take:
+ ch_reads // channel: [ val(meta), [ fastq ] ]
+
+ main:
+ // Add strandness information to meta
+ ch_reads_with_strandness = ch_reads.flatMap { meta, reads ->
+ if (meta.single_end) {
+ if (reads instanceof List && reads.size() != 1) {
+ error("Error: Check your meta.single_end value. Single-end reads should contain one file only.")
+ }
+ return [[meta + [strandness: 'single'], reads]]
+ }
+ else {
+ if (!(reads instanceof List) || reads.size() != 2) {
+ error("Error: Check your meta.single_end value. Paired-end reads should contain two files; a forward and a reverse.")
+ }
+ return [[meta + [strandness: 'R1'], reads[0]], [meta + [strandness: 'R2'], reads[1]]]
+ }
+ }
+
+ SEQKIT_SANA(ch_reads_with_strandness)
+
+ ch_sanitized_reads = SEQKIT_SANA.out.reads
+ .map { meta, fastq ->
+ // Remove strandness field from meta to merge back together
+ def clean_meta = meta.findAll { key, _value -> key != 'strandness' }
+ return [clean_meta, fastq]
+ }
+ .groupTuple(by: 0)
+ .branch { meta, fastq ->
+ single_end: meta.single_end
+ return [meta, fastq]
+ paired_end: !meta.single_end
+ return [meta, fastq]
+ }
+
+ SEQKIT_PAIR(ch_sanitized_reads.paired_end)
+
+ ch_reads = ch_sanitized_reads.single_end.mix(SEQKIT_PAIR.out.reads, SEQKIT_PAIR.out.unpaired_reads)
+
+ emit:
+ reads = ch_reads // channel: [ val(meta), [ fastq ] ]
+}
diff --git a/subworkflows/nf-core/fastq_sanitise_seqkit/meta.yml b/subworkflows/nf-core/fastq_sanitise_seqkit/meta.yml
new file mode 100644
index 0000000..25d4aa2
--- /dev/null
+++ b/subworkflows/nf-core/fastq_sanitise_seqkit/meta.yml
@@ -0,0 +1,45 @@
+# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json
+name: "fastq_sanitise_seqkit"
+description: |
+ Filters and reports malformed FASTQ sequences with seqkit/sana,
+ and then pairs any paired-end files using seqkit/pair
+keywords:
+ - fastq
+ - quality control
+ - filtering
+ - malformed
+ - pairing
+ - seqkit
+ - preprocessing
+components:
+ - seqkit/sana
+ - seqkit/pair
+input:
+ - ch_reads:
+ type: channel
+ description: |
+ Channel containing sample metadata and FASTQ files.
+ Structure: [ val(meta), [ fastq ] ]
+ Where meta is a map containing at least:
+ - id: sample identifier
+ - single_end: boolean indicating if data is single-end (true) or paired-end (false)
+ pattern: "*.{fastq,fastq.gz,fq,fq.gz}"
+output:
+ - reads:
+ type: channel
+ description: |
+ Channel containing filtered (i.e., non-malformed) and paired FASTQ files.
+ For single-end data: returns filtered reads
+ For paired-end data: returns properly paired reads and any unpaired reads
+ Structure: [ val(meta), [ fastq ] ]
+ pattern: "*.{fastq,fastq.gz,fq,fq.gz}"
+ - versions:
+ type: file
+ description: |
+ File containing software versions
+ Structure: [ path(versions.yml) ]
+ pattern: "versions.yml"
+authors:
+ - "@vagkaratzas"
+maintainers:
+ - "@vagkaratzas"
diff --git a/subworkflows/nf-core/fastq_sanitise_seqkit/nextflow.config b/subworkflows/nf-core/fastq_sanitise_seqkit/nextflow.config
new file mode 100644
index 0000000..0b0a68d
--- /dev/null
+++ b/subworkflows/nf-core/fastq_sanitise_seqkit/nextflow.config
@@ -0,0 +1,8 @@
+// IMPORTANT: This config file should be included to ensure that the subworkflow works properly.
+process {
+
+ withName: SEQKIT_SANA {
+ ext.prefix = { "${meta.id}_${meta.strandness}" }
+ }
+
+}
diff --git a/subworkflows/nf-core/fastq_sanitise_seqkit/tests/main.nf.test b/subworkflows/nf-core/fastq_sanitise_seqkit/tests/main.nf.test
new file mode 100644
index 0000000..85cd246
--- /dev/null
+++ b/subworkflows/nf-core/fastq_sanitise_seqkit/tests/main.nf.test
@@ -0,0 +1,119 @@
+nextflow_workflow {
+
+ name "Test Subworkflow FASTQ_SANITISE_SEQKIT"
+ script "../main.nf"
+ workflow "FASTQ_SANITISE_SEQKIT"
+ config './nextflow.config'
+
+ tag "subworkflows"
+ tag "subworkflows_nfcore"
+ tag "subworkflows/fastq_sanitise_seqkit"
+ tag "seqkit"
+ tag "seqkit/sana"
+ tag "seqkit/pair"
+
+
+ test("sarscov2 - fastq - single_end") {
+
+ when {
+ workflow {
+ """
+ input[0] = Channel.of([
+ [ id:'test_single', single_end:true ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ])
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert workflow.success},
+ { assert snapshot(
+ sanitizeOutput(workflow.out)
+ ).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - paired_end") {
+
+ when {
+ workflow {
+ """
+ input[0] = Channel.of([
+ [ id:'test_paired', single_end:false ], // meta map
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ ]
+ ])
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert workflow.success},
+ { assert snapshot(
+ sanitizeOutput(workflow.out)
+ ).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - both with single broken") {
+
+ when {
+ workflow {
+ """
+ input[0] = Channel.of([
+ [ id:'test_both', single_end:true ], // meta map
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1_broken.fastq.gz', checkIfExists: true)
+ ],
+ [
+ [ id:'test_both', single_end:false ], // meta map
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ ]
+ ])
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert workflow.success},
+ { assert snapshot(
+ sanitizeOutput(workflow.out)
+ ).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - stub") {
+
+ options "-stub"
+
+ when {
+ workflow {
+ """
+ input[0] = Channel.of([
+ [ id: 'test_stub', single_end:true ],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1_broken.fastq.gz', checkIfExists: true)
+ ])
+ """
+ }
+ }
+
+ then {
+ assertAll(
+ { assert workflow.success },
+ { assert snapshot(
+ sanitizeOutput(workflow.out)
+ ).match() }
+ )
+ }
+ }
+}
diff --git a/subworkflows/nf-core/fastq_sanitise_seqkit/tests/main.nf.test.snap b/subworkflows/nf-core/fastq_sanitise_seqkit/tests/main.nf.test.snap
new file mode 100644
index 0000000..2f664b0
--- /dev/null
+++ b/subworkflows/nf-core/fastq_sanitise_seqkit/tests/main.nf.test.snap
@@ -0,0 +1,101 @@
+{
+ "sarscov2 - fastq - stub": {
+ "content": [
+ {
+ "reads": [
+ [
+ {
+ "id": "test_stub",
+ "single_end": true
+ },
+ [
+ "test_stub_single.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940"
+ ]
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-15T10:58:37.652046254",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "sarscov2 - fastq - single_end": {
+ "content": [
+ {
+ "reads": [
+ [
+ {
+ "id": "test_single",
+ "single_end": true
+ },
+ [
+ "test_single_single.fastq.gz:md5,4161df271f9bfcd25d5845a1e220dbec"
+ ]
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-12T17:37:45.293059368",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "sarscov2 - fastq - paired_end": {
+ "content": [
+ {
+ "reads": [
+ [
+ {
+ "id": "test_paired",
+ "single_end": false
+ },
+ [
+ "test_paired_R1.paired.fastq.gz:md5,4161df271f9bfcd25d5845a1e220dbec",
+ "test_paired_R2.paired.fastq.gz:md5,2ebae722295ea66d84075a3b042e2b42"
+ ]
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-12T17:37:50.905358878",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "sarscov2 - fastq - both with single broken": {
+ "content": [
+ {
+ "reads": [
+ [
+ {
+ "id": "test_both",
+ "single_end": false
+ },
+ [
+ "test_both_R1.paired.fastq.gz:md5,4161df271f9bfcd25d5845a1e220dbec",
+ "test_both_R2.paired.fastq.gz:md5,2ebae722295ea66d84075a3b042e2b42"
+ ]
+ ],
+ [
+ {
+ "id": "test_both",
+ "single_end": true
+ },
+ [
+ "test_both_single.fastq.gz:md5,03b43487ca6aeecc5eb8b55bbc1fe2f3"
+ ]
+ ]
+ ]
+ }
+ ],
+ "timestamp": "2026-06-12T17:37:56.877663489",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ }
+}
\ No newline at end of file
diff --git a/subworkflows/nf-core/fastq_sanitise_seqkit/tests/nextflow.config b/subworkflows/nf-core/fastq_sanitise_seqkit/tests/nextflow.config
new file mode 100644
index 0000000..f8debb9
--- /dev/null
+++ b/subworkflows/nf-core/fastq_sanitise_seqkit/tests/nextflow.config
@@ -0,0 +1,11 @@
+process {
+
+ withName: SEQKIT_SANA {
+ ext.prefix = { "${meta.id}_${meta.strandness}" }
+ }
+
+ withName: SEQKIT_PAIR {
+ ext.args = '-u'
+ }
+
+}
diff --git a/subworkflows/nf-core/fastq_shortreads_preprocess_qc/main.nf b/subworkflows/nf-core/fastq_shortreads_preprocess_qc/main.nf
new file mode 100644
index 0000000..3003581
--- /dev/null
+++ b/subworkflows/nf-core/fastq_shortreads_preprocess_qc/main.nf
@@ -0,0 +1,222 @@
+// statistics
+include { FASTQ_QC_STATS as PRE_STATS } from '../fastq_qc_stats'
+include { FASTQ_QC_STATS as POST_STATS } from '../fastq_qc_stats'
+// preprocessing
+include { FASTQ_PREPROCESS_SEQKIT } from '../fastq_preprocess_seqkit'
+// barcoding
+include { UMITOOLS_EXTRACT } from '../../../modules/nf-core/umitools/extract'
+// adapter removal and merging
+include { FASTQ_REMOVEADAPTERS_MERGE } from '../fastq_removeadapters_merge'
+// complexity filtering
+include { FASTQ_COMPLEXITY_FILTER } from '../fastq_complexity_filter'
+// deduplication
+include { BBMAP_CLUMPIFY } from '../../../modules/nf-core/bbmap/clumpify'
+// host decontamination
+include { FASTQ_DECONTAMINATE_DEACON_HOSTILE } from '../fastq_decontaminate_deacon_hostile'
+// final concatenation
+include { CAT_FASTQ } from '../../../modules/nf-core/cat/fastq'
+
+workflow FASTQ_SHORTREADS_PREPROCESS_QC {
+ take:
+ ch_reads // channel: [ val(meta), [ fastq ] ]
+ skip_fastqc // boolean
+ skip_seqfu_check // boolean
+ skip_seqfu_stats // boolean
+ skip_seqkit_stats // boolean
+ skip_seqtk_comp // boolean
+ skip_seqkit_sana_pair // boolean
+ skip_seqkit_seq // boolean
+ skip_seqkit_replace // boolean
+ skip_seqkit_rmdup // boolean
+ skip_umitools_extract // boolean
+ val_umi_discard_read // integer: 0, 1 or 2
+ skip_adapterremoval // boolean
+ val_adapter_tool // string: [mandatory] tool_name // choose from: ["trimmomatic", "cutadapt", "trimgalore", "bbduk", "leehom", "fastp", "adapterremoval"]
+ ch_custom_adapters_file // channel: [optional] [ {fasta,txt} ] // fasta, for bbduk or fastp, or txt, for adapterremoval
+ val_save_merged // boolean: [mandatory] if true, will return the merged reads instead, for fastp and adapterremoval
+ val_fastp_discard_trimmed_pass // boolean: [mandatory] // only for fastp
+ val_fastp_save_trimmed_fail // boolean: [mandatory] // only for fastp
+ skip_complexity_filtering // boolean
+ val_complexity_filter_tool // string: [mandatory] tool_name // choose from: ["prinseqplusplus", "bbduk", "fastp"]
+ skip_deduplication // boolean
+ skip_decontamination // boolean
+ ch_decontamination_fasta // channel: [ val(meta), [ fasta ] ] (optional)
+ ch_decontamination_reference // channel: [ val(reference_name), path(reference_dir) ] (optional)
+ val_decontamination_index_name // val (optional)
+ val_decontamination_tool // string (enum): 'hostile' or 'deacon'
+ skip_final_concatenation // boolean
+
+ main:
+
+ ch_multiqc_files = channel.empty()
+ ch_umi_log = channel.empty()
+ ch_adapterremoval_discarded_reads = channel.empty()
+ ch_adapterremoval_logfile = channel.empty()
+ ch_adapterremoval_report = channel.empty()
+ ch_complexity_filter_log = channel.empty()
+ ch_complexity_filter_report = channel.empty()
+ ch_clumpify_log = channel.empty()
+ ch_hostile_reference = channel.empty()
+ ch_hostile_json = channel.empty()
+ ch_deacon_index = channel.empty()
+ ch_deacon_summary = channel.empty()
+
+ // pre-statistics
+ PRE_STATS(
+ ch_reads,
+ skip_fastqc,
+ skip_seqfu_check,
+ skip_seqfu_stats,
+ skip_seqkit_stats,
+ skip_seqtk_comp,
+ )
+ ch_pre_stats_fastqc_html = PRE_STATS.out.fastqc_html
+ ch_pre_stats_fastqc_zip = PRE_STATS.out.fastqc_zip
+ ch_pre_stats_seqfu_check = PRE_STATS.out.seqfu_check
+ ch_pre_stats_seqfu_stats = PRE_STATS.out.seqfu_stats
+ ch_pre_stats_seqkit_stats = PRE_STATS.out.seqkit_stats
+ ch_pre_stats_seqtk_stats = PRE_STATS.out.seqtk_stats
+ ch_multiqc_files = ch_multiqc_files.mix(PRE_STATS.out.seqfu_multiqc)
+
+ // preprocessing
+ FASTQ_PREPROCESS_SEQKIT(
+ ch_reads,
+ skip_seqkit_sana_pair,
+ skip_seqkit_seq,
+ skip_seqkit_replace,
+ skip_seqkit_rmdup,
+ )
+ ch_reads = FASTQ_PREPROCESS_SEQKIT.out.reads
+
+ // barcoding
+ if (!skip_umitools_extract) {
+ UMITOOLS_EXTRACT(ch_reads)
+ ch_umi_reads = UMITOOLS_EXTRACT.out.reads
+ ch_umi_log = UMITOOLS_EXTRACT.out.log
+
+ // Discard R1 / R2 if required
+ if (val_umi_discard_read in [1, 2]) {
+ ch_umi_reads = UMITOOLS_EXTRACT.out.reads.map { meta, reads ->
+ meta.single_end ? [meta, reads] : [meta + ['single_end': true], reads[val_umi_discard_read % 2]]
+ }
+ }
+
+ ch_reads = ch_umi_reads
+ }
+
+ // adapter removal and merging
+ if (!skip_adapterremoval) {
+ FASTQ_REMOVEADAPTERS_MERGE(
+ ch_reads,
+ val_adapter_tool,
+ ch_custom_adapters_file,
+ val_save_merged,
+ val_fastp_discard_trimmed_pass,
+ val_fastp_save_trimmed_fail,
+ )
+ ch_adapterremoval_discarded_reads = FASTQ_REMOVEADAPTERS_MERGE.out.discarded_reads
+ ch_adapterremoval_logfile = FASTQ_REMOVEADAPTERS_MERGE.out.logfile
+ ch_adapterremoval_report = FASTQ_REMOVEADAPTERS_MERGE.out.report
+ ch_reads = FASTQ_REMOVEADAPTERS_MERGE.out.processed_reads
+ ch_multiqc_files = ch_multiqc_files.mix(FASTQ_REMOVEADAPTERS_MERGE.out.multiqc_files)
+ }
+
+ // complexity filtering
+ if (!skip_complexity_filtering) {
+ FASTQ_COMPLEXITY_FILTER(ch_reads, val_complexity_filter_tool)
+ ch_reads = FASTQ_COMPLEXITY_FILTER.out.filtered_reads
+ ch_complexity_filter_log = FASTQ_COMPLEXITY_FILTER.out.logfile
+ ch_complexity_filter_report = FASTQ_COMPLEXITY_FILTER.out.report
+ ch_multiqc_files = ch_multiqc_files.mix(FASTQ_COMPLEXITY_FILTER.out.multiqc_files)
+ }
+
+ // deduplication
+ if (!skip_deduplication) {
+ BBMAP_CLUMPIFY(ch_reads)
+ ch_reads = BBMAP_CLUMPIFY.out.reads
+ ch_clumpify_log = BBMAP_CLUMPIFY.out.log
+ }
+
+ // host decontamination
+ if (!skip_decontamination) {
+ FASTQ_DECONTAMINATE_DEACON_HOSTILE(
+ ch_reads,
+ ch_decontamination_fasta,
+ ch_decontamination_reference,
+ val_decontamination_index_name,
+ val_decontamination_tool,
+ )
+ ch_reads = FASTQ_DECONTAMINATE_DEACON_HOSTILE.out.fastq_filtered
+ ch_hostile_reference = FASTQ_DECONTAMINATE_DEACON_HOSTILE.out.reference
+ ch_hostile_json = FASTQ_DECONTAMINATE_DEACON_HOSTILE.out.json
+ ch_deacon_index = FASTQ_DECONTAMINATE_DEACON_HOSTILE.out.index
+ ch_deacon_summary = FASTQ_DECONTAMINATE_DEACON_HOSTILE.out.summary
+ }
+
+
+ // final concatenation
+ if (!skip_final_concatenation) {
+ ch_reads_for_cat_branch = ch_reads
+ .groupTuple()
+ .map { meta, reads ->
+ [meta, reads.flatten()]
+ }
+ .branch { meta, reads ->
+ cat: (meta.single_end && reads.size() > 1) || (!meta.single_end && reads.size() > 2)
+ skip: true
+ }
+
+ CAT_FASTQ(ch_reads_for_cat_branch.cat)
+
+ ch_reads = CAT_FASTQ.out.reads
+ .mix(ch_reads_for_cat_branch.skip)
+ .map { meta, reads ->
+ def new_reads = meta.single_end ? reads[0] : reads.flatten()
+ [meta, new_reads]
+ }
+ }
+
+ // post-statistics
+ POST_STATS(
+ ch_reads,
+ skip_fastqc,
+ skip_seqfu_check,
+ skip_seqfu_stats,
+ skip_seqkit_stats,
+ skip_seqtk_comp,
+ )
+ ch_post_stats_fastqc_html = POST_STATS.out.fastqc_html
+ ch_post_stats_fastqc_zip = POST_STATS.out.fastqc_zip
+ ch_post_stats_seqfu_check = POST_STATS.out.seqfu_check
+ ch_post_stats_seqfu_stats = POST_STATS.out.seqfu_stats
+ ch_post_stats_seqkit_stats = POST_STATS.out.seqkit_stats
+ ch_post_stats_seqtk_stats = POST_STATS.out.seqtk_stats
+ ch_multiqc_files = ch_multiqc_files.mix(POST_STATS.out.seqfu_multiqc)
+
+ emit:
+ reads = ch_reads // channel: [ val(meta), [ fastq ] ]
+ pre_stats_fastqc_html = ch_pre_stats_fastqc_html
+ pre_stats_fastqc_zip = ch_pre_stats_fastqc_zip
+ pre_stats_seqfu_check = ch_pre_stats_seqfu_check
+ pre_stats_seqfu_stats = ch_pre_stats_seqfu_stats
+ pre_stats_seqkit_stats = ch_pre_stats_seqkit_stats
+ pre_stats_seqtk_stats = ch_pre_stats_seqtk_stats
+ post_stats_fastqc_html = ch_post_stats_fastqc_html
+ post_stats_fastqc_zip = ch_post_stats_fastqc_zip
+ post_stats_seqfu_check = ch_post_stats_seqfu_check
+ post_stats_seqfu_stats = ch_post_stats_seqfu_stats
+ post_stats_seqkit_stats = ch_post_stats_seqkit_stats
+ post_stats_seqtk_stats = ch_post_stats_seqtk_stats
+ umi_log = ch_umi_log
+ adapterremoval_discarded_reads = ch_adapterremoval_discarded_reads
+ adapterremoval_logfile = ch_adapterremoval_logfile
+ adapterremoval_report = ch_adapterremoval_report
+ complexity_filter_log = ch_complexity_filter_log
+ complexity_filter_report = ch_complexity_filter_report
+ clumpify_log = ch_clumpify_log
+ hostile_reference = ch_hostile_reference
+ hostile_json = ch_hostile_json
+ deacon_index = ch_deacon_index
+ deacon_summary = ch_deacon_summary
+ multiqc_files = ch_multiqc_files
+}
diff --git a/subworkflows/nf-core/fastq_shortreads_preprocess_qc/meta.yml b/subworkflows/nf-core/fastq_shortreads_preprocess_qc/meta.yml
new file mode 100644
index 0000000..c4f70ee
--- /dev/null
+++ b/subworkflows/nf-core/fastq_shortreads_preprocess_qc/meta.yml
@@ -0,0 +1,332 @@
+# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json
+name: "fastq_shortreads_preprocess_qc"
+description: |
+ Quality check and preprocessing subworkflow of Illumina short reads
+ that can do: quality check of input reads and generate statistics,
+ preprocess and validate reads, barcode removal, remove adapters and merge reads,
+ filter by sequence complexity, deduplicate reads, remove host contamination,
+ concatenate reads and generate statistics for post-processing reads.
+ WARNING: requires at least the process configurations from the nextflow.config
+ to be added to the modules.config in the pipeline in order to work as intended.
+keywords:
+ - fastq
+ - illumina
+ - short
+ - reads
+ - qc
+ - stats
+ - preprocessing
+ - barcoding
+ - adapters
+ - merge
+ - complexity
+ - deduplication
+ - host
+ - decontamination
+components:
+ - fastq_qc_stats
+ - fastqc
+ - seqfu/check
+ - seqfu/stats
+ - seqkit/stats
+ - seqtk/comp
+ - fastq_preprocess_seqkit
+ - fastq_sanitise_seqkit
+ - seqkit/sana
+ - seqkit/pair
+ - seqkit/seq
+ - seqkit/replace
+ - seqkit/rmdup
+ - umitools/extract
+ - fastq_removeadapters_merge
+ - trimmomatic
+ - cutadapt
+ - trimgalore
+ - bbmap/bbduk
+ - leehom
+ - fastp
+ - adapterremoval
+ - cat/fastq
+ - fastq_complexity_filter
+ - prinseqplusplus
+ - bbmap/clumpify
+ - fastq_decontaminate_deacon_hostile
+ - fastq_index_filter_deacon
+ - fastq_fetch_clean_hostile
+ - hostile/fetch
+ - hostile/clean
+ - bowtie2/build
+ - deacon/filter
+ - deacon/index
+
+input:
+ - ch_reads:
+ type: file
+ description: |
+ List of FastQ files of size 1 and 2 for single-end and paired-end data, respectively.
+ Structure: [ val(meta), [ path(reads) ] ]
+ pattern: "*.fastq.gz"
+ - skip_fastqc:
+ type: boolean
+ description: |
+ Skip FastQC quality control step
+ - skip_seqfu_check:
+ type: boolean
+ description: |
+ Skip SeqFu check step
+ - skip_seqfu_stats:
+ type: boolean
+ description: |
+ Skip SeqFu statistics step
+ - skip_seqkit_stats:
+ type: boolean
+ description: |
+ Skip SeqKit statistics step
+ - skip_seqtk_comp:
+ type: boolean
+ description: |
+ Skip SeqTk composition analysis step
+ - skip_seqkit_sana_pair:
+ type: boolean
+ description: |
+ Skip SeqKit sanitize and pair step
+ - skip_seqkit_seq:
+ type: boolean
+ description: |
+ Skip SeqKit sequence processing step
+ - skip_seqkit_replace:
+ type: boolean
+ description: |
+ Skip SeqKit replace step
+ - skip_seqkit_rmdup:
+ type: boolean
+ description: |
+ Skip SeqKit remove duplicates step
+ - skip_umitools_extract:
+ type: boolean
+ description: |
+ Skip UMI-tools extract barcoding step
+ - val_umi_discard_read:
+ type: integer
+ description: |
+ Discard R1 or R2 after UMI extraction (0 = keep both, 1 = discard R1, 2 = discard R2)
+ - skip_adapterremoval:
+ type: boolean
+ description: |
+ Skip the adapter removal and merge subworkflow completely
+ - val_adapter_tool:
+ type: string
+ description: |
+ Choose one of the available adapter removal and/or merging tools
+ enum: ["trimmomatic", "cutadapt", "trimgalore", "bbduk", "leehom", "fastp", "adapterremoval"]
+ - ch_custom_adapters_file:
+ type: file
+ description: |
+ Optional reference files, containing adapter and/or contaminant sequences for removal.
+ In fasta format for bbmap/bbduk and fastp, or in text format for AdapterRemoval (one adapter per line).
+ - val_save_merged:
+ type: boolean
+ description: |
+ Specify true to output merged reads instead
+ Used by fastp and adapterremoval
+ - val_fastp_discard_trimmed_pass:
+ type: boolean
+ description: |
+ Used only by fastp.
+ Specify true to not write any reads that pass trimming thresholds from the fastp process.
+ This can be used to use fastp for the output report only.
+ - val_fastp_save_trimmed_fail:
+ type: boolean
+ description: |
+ Used only by fastp.
+ Specify true to save files that failed to pass fastp trimming thresholds
+ - skip_complexity_filtering:
+ type: boolean
+ description: |
+ Skip PRINSEQ++ complexity filtering step
+ - val_complexity_filter_tool:
+ type: string
+ description: |
+ Complexity filtering tool to use.
+ Must be one of: 'prinseqplusplus', 'bbduk', or 'fastp'.
+ - skip_deduplication:
+ type: boolean
+ description: |
+ Skip BBMap Clumpify deduplication step
+ - skip_decontamination:
+ type: boolean
+ description: |
+ Skip host decontamination step
+ - ch_decontamination_fasta:
+ type: file
+ description: |
+ Reference genome FASTA file for decontamination (optional)
+ Structure: [ val(meta), [ path(fasta) ] ]
+ pattern: "*.{fasta,fa,fna}"
+ - ch_decontamination_reference:
+ type: directory
+ description: |
+ Pre-built reference index directory for decontamination (optional)
+ Structure: [ val(reference_name), path(reference_dir) ]
+ - val_decontamination_index_name:
+ type: string
+ description: |
+ Name for the decontamination index (optional)
+ - val_decontamination_tool:
+ type: string
+ description: |
+ Decontamination tool to use ('hostile' or 'deacon')
+ - skip_final_concatenation:
+ type: boolean
+ description: |
+ Skip final FASTQ concatenation step
+
+output:
+ - reads:
+ type: file
+ description: |
+ Channel containing processed short reads
+ Structure: [ val(meta), path(reads) ]
+ pattern: "*.fastq.gz"
+ - pre_stats_fastqc_html:
+ type: file
+ description: |
+ FastQC HTML reports for pre-processing reads
+ Structure: [ val(meta), path(html) ]
+ pattern: "*.html"
+ - pre_stats_fastqc_zip:
+ type: file
+ description: |
+ FastQC ZIP archives for pre-processing reads
+ Structure: [ val(meta), path(zip) ]
+ pattern: "*.zip"
+ - pre_stats_seqfu_check:
+ type: file
+ description: |
+ SeqFu check results for pre-processing reads
+ Structure: [ val(meta), path(check) ]
+ - pre_stats_seqfu_stats:
+ type: file
+ description: |
+ SeqFu statistics for pre-processing reads
+ Structure: [ val(meta), path(stats) ]
+ - pre_stats_seqfu_multiqc:
+ type: file
+ description: |
+ SeqFu MultiQC-compatible stats for pre-processing reads
+ Structure: [ val(meta), path(multiqc) ]
+ - pre_stats_seqkit_stats:
+ type: file
+ description: |
+ SeqKit statistics for pre-processing reads
+ Structure: [ val(meta), path(stats) ]
+ - pre_stats_seqtk_stats:
+ type: file
+ description: |
+ SeqTk composition statistics for pre-processing reads
+ Structure: [ val(meta), path(stats) ]
+ - post_stats_fastqc_html:
+ type: file
+ description: |
+ FastQC HTML reports for post-processing reads
+ Structure: [ val(meta), path(html) ]
+ pattern: "*.html"
+ - post_stats_fastqc_zip:
+ type: file
+ description: |
+ FastQC ZIP archives for post-processing reads
+ Structure: [ val(meta), path(zip) ]
+ pattern: "*.zip"
+ - post_stats_seqfu_check:
+ type: file
+ description: |
+ SeqFu check results for post-processing reads
+ Structure: [ val(meta), path(check) ]
+ - post_stats_seqfu_stats:
+ type: file
+ description: |
+ SeqFu statistics for post-processing reads
+ Structure: [ val(meta), path(stats) ]
+ - post_stats_seqfu_multiqc:
+ type: file
+ description: |
+ SeqFu MultiQC-compatible stats for post-processing reads
+ Structure: [ val(meta), path(multiqc) ]
+ - post_stats_seqkit_stats:
+ type: file
+ description: |
+ SeqKit statistics for post-processing reads
+ Structure: [ val(meta), path(stats) ]
+ - post_stats_seqtk_stats:
+ type: file
+ description: |
+ SeqTk composition statistics for post-processing reads
+ Structure: [ val(meta), path(stats) ]
+ - umi_log:
+ type: file
+ description: |
+ UMI-tools extract log file
+ Structure: [ val(meta), path(log) ]
+ - adapterremoval_discarded_reads:
+ type: file
+ description: |
+ Reads discarded during adapter removal or merging
+ Structure: [ val(meta), path(fastq) ]
+ pattern: "*.fastq.gz"
+ - adapterremoval_logfile:
+ type: file
+ description: |
+ Adapter removal execution log file
+ (trimmomatic {log}, trimgalore {txt}, fastp {log})
+ Structure: [ val(meta), path({log,txt}) ]
+ - adapterremoval_report:
+ type: file
+ description: |
+ Adapter removal report
+ (trimmomatic {summary}, trimgalore {html,zip}, fastp {html})
+ Structure: [ val(meta), path({summary,html,zip}) ]
+ - complexity_filter_log:
+ type: file
+ description: |
+ Log file from complexity filtering
+ Structure: [ val(meta), path(log) ]
+ - complexity_filter_report:
+ type: file
+ description: |
+ Report generated by complexity filtering
+ HTML report generated by fastp. Empty for other tools.
+ Structure: [ val(meta), path(html) ]
+ - clumpify_log:
+ type: file
+ description: |
+ BBMap Clumpify log file
+ Structure: [ val(meta), path(log) ]
+ - hostile_reference:
+ type: file
+ description: |
+ Hostile reference files used for decontamination
+ Structure: [ val(reference_name), path(reference_dir) ]
+ - hostile_json:
+ type: file
+ description: |
+ Hostile JSON report
+ Structure: [ val(meta), path(json) ]
+ - deacon_index:
+ type: directory
+ description: |
+ Deacon index directory
+ Structure: [ val(meta), path(index) ]
+ - deacon_summary:
+ type: file
+ description: |
+ Deacon decontamination summary file
+ Structure: [ val(meta), path(log) ]
+ - multiqc_files:
+ type: file
+ description: |
+ MultiQC compatible files for aggregated reporting
+ Structure: [ path(files) ]
+authors:
+ - "@vagkaratzas"
+maintainers:
+ - "@vagkaratzas"
diff --git a/subworkflows/nf-core/fastq_shortreads_preprocess_qc/nextflow.config b/subworkflows/nf-core/fastq_shortreads_preprocess_qc/nextflow.config
new file mode 100644
index 0000000..b81471f
--- /dev/null
+++ b/subworkflows/nf-core/fastq_shortreads_preprocess_qc/nextflow.config
@@ -0,0 +1,26 @@
+// IMPORTANT: Add this configuration to your modules.config
+
+process {
+ withName: ".*:FASTQ_COMPLEXITY_FILTER:BBMAP_BBDUK" {
+ ext.args = [
+ 'entropy=0.7', // needed to turn on complexity filtering
+ 'minlength=0' // needed, to not discard reads shorter than this
+ ].join(' ')
+ ext.prefix = { "${meta.id}.trim" }
+ }
+
+ // need FASTP to only do complexity filtering
+ withName: ".*:FASTQ_COMPLEXITY_FILTER:FASTP" {
+ ext.args = [
+ '--low_complexity_filter',
+ '--disable_adapter_trimming',
+ '--disable_trim_poly_g',
+ '--disable_quality_filtering',
+ '--disable_length_filtering'
+ ].join(' ')
+ }
+
+ withName: "BBMAP_CLUMPIFY" {
+ ext.args = 'dedupe=t'
+ }
+}
diff --git a/subworkflows/nf-core/fastq_shortreads_preprocess_qc/tests/main.nf.test b/subworkflows/nf-core/fastq_shortreads_preprocess_qc/tests/main.nf.test
new file mode 100644
index 0000000..4f0752b
--- /dev/null
+++ b/subworkflows/nf-core/fastq_shortreads_preprocess_qc/tests/main.nf.test
@@ -0,0 +1,411 @@
+nextflow_workflow {
+
+ name "Test Subworkflow FASTQ_SHORTREADS_PREPROCESS_QC"
+ script "../main.nf"
+ workflow "FASTQ_SHORTREADS_PREPROCESS_QC"
+ config './nextflow.config'
+
+ tag "subworkflows"
+ tag "subworkflows_nfcore"
+ tag "subworkflows/fastq_shortreads_preprocess_qc"
+ tag "subworkflows/fastq_qc_stats"
+ tag "fastqc"
+ tag "seqfu"
+ tag "seqfu/check"
+ tag "seqfu/stats"
+ tag "seqkit"
+ tag "seqkit/stats"
+ tag "seqtk"
+ tag "seqtk/comp"
+ tag "subworkflows/fastq_preprocess_seqkit"
+ tag "subworkflows/fastq_sanitise_seqkit"
+ tag "seqkit"
+ tag "seqkit/sana"
+ tag "seqkit/pair"
+ tag "seqkit/seq"
+ tag "seqkit/replace"
+ tag "seqkit/rmdup"
+ tag "umitools"
+ tag "umitools/extract"
+ tag "subworkflows/fastq_removeadapters_merge"
+ tag "trimmomatic"
+ tag "cutadapt"
+ tag "trimgalore"
+ tag "bbmap"
+ tag "bbmap/bbduk"
+ tag "leehom"
+ tag "fastp"
+ tag "adapterremoval"
+ tag "cat"
+ tag "cat/fastq"
+ tag "subworkflows/fastq_complexity_filter"
+ tag "prinseqplusplus"
+ tag "bbmap/clumpify"
+ tag "subworkflows/fastq_decontaminate_deacon_hostile"
+ tag "subworkflows/fastq_index_filter_deacon"
+ tag "subworkflows/fastq_fetch_clean_hostile"
+ tag "hostile"
+ tag "hostile/fetch"
+ tag "hostile/clean"
+ tag "bowtie2/build"
+ tag "deacon"
+ tag "deacon/filter"
+ tag "deacon/index"
+
+ test("sarscov2 - fastq - seqfu - seqkit - deacon - single_end") {
+
+ when {
+ params {
+ save_merged = false
+ adapterremoval_args = save_merged ? "--collapse" : ""
+ }
+ workflow {
+ """
+ input[0] = channel.of([
+ [ id:'test', single_end:true ],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ])
+ input[1] = true // skip_fastqc
+ input[2] = false // skip_seqfu_check
+ input[3] = false // skip_seqfu_stats
+ input[4] = true // skip_seqkit_stats
+ input[5] = true // skip_seqtk_comp
+ input[6] = false // skip_seqkit_sana_pair
+ input[7] = false // skip_seqkit_seq
+ input[8] = false // skip_seqkit_replace
+ input[9] = false // skip_seqkit_rmdup
+ input[10] = true // skip_umitools_extract
+ input[11] = 0 // val_umi_discard_read
+ input[12] = true // skip_adapterremoval
+ input[13] = "" // val_adapter_tool
+ input[14] = [] // ch_custom_adapters_file
+ input[15] = false // val_save_merged
+ input[16] = false // val_fastp_discard_trimmed_pass
+ input[17] = false // val_fastp_save_trimmed_fail
+ input[18] = true // skip_complexity_filtering
+ input[19] = "" // val_complexity_filter_tool
+ input[20] = true // skip_deduplication
+ input[21] = false // skip_decontamination
+ input[22] = channel.of(
+ [
+ [ id:'test', single_end:true ],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)
+ ]
+ ) // ch_decontamination_fasta
+ input[23] = [] // ch_decontamination_reference
+ input[24] = [] // val_decontamination_index_name
+ input[25] = 'deacon' // val_decontamination_tool
+ input[26] = true // skip_final_concatenation
+ """
+ }
+ }
+ then {
+ assert workflow.success
+ assertAll(
+ { assert snapshot(
+ workflow.out.reads[0][1],
+ workflow.out.pre_stats_seqfu_check,
+ workflow.out.pre_stats_seqfu_stats,
+ workflow.out.post_stats_seqfu_check,
+ workflow.out.post_stats_seqfu_stats,
+ workflow.out.deacon_index,
+ file(workflow.out.deacon_summary[0][1]).name,
+ workflow.out.multiqc_files
+ ).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - umitools - prinseq - clumpify - cat - single_end") {
+
+ when {
+ params {
+ save_merged = false
+ adapterremoval_args = save_merged ? "--collapse" : ""
+ }
+ workflow {
+ """
+ input[0] = channel.of([
+ [ id:'test', single_end:true ],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ])
+ input[1] = true // skip_fastqc
+ input[2] = true // skip_seqfu_check
+ input[3] = true // skip_seqfu_stats
+ input[4] = true // skip_seqkit_stats
+ input[5] = true // skip_seqtk_comp
+ input[6] = true // skip_seqkit_sana_pair
+ input[7] = true // skip_seqkit_seq
+ input[8] = true // skip_seqkit_replace
+ input[9] = true // skip_seqkit_rmdup
+ input[10] = false // skip_umitools_extract
+ input[11] = 0 // val_umi_discard_read
+ input[12] = true // skip_adapterremoval
+ input[13] = "" // val_adapter_tool
+ input[14] = [] // ch_custom_adapters_file
+ input[15] = false // val_save_merged
+ input[16] = false // val_fastp_discard_trimmed_pass
+ input[17] = false // val_fastp_save_trimmed_fail
+ input[18] = false // skip_complexity_filtering
+ input[19] = 'prinseqplusplus' // val_complexity_filter_tool
+ input[20] = false // skip_deduplication
+ input[21] = true // skip_decontamination
+ input[22] = channel.of(
+ [
+ [ id:'test', single_end:true ],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)
+ ]
+ ) // ch_decontamination_fasta
+ input[23] = [] // ch_decontamination_reference
+ input[24] = [] // val_decontamination_index_name
+ input[25] = 'deacon' // val_decontamination_tool
+ input[26] = false // skip_final_concatenation
+ """
+ }
+ }
+ then {
+ assert workflow.success
+ assertAll(
+ { assert snapshot(
+ path(workflow.out.reads[0][1]).linesGzip.size(),
+ path(workflow.out.umi_log[0][1]).readLines().size(),
+ path(workflow.out.clumpify_log[0][1]).readLines().size()
+ ).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - fastqc - seqkit - cutadapt - clumpify - cat - paired_end") {
+ tag "test"
+
+ when {
+ params {
+ save_merged = false
+ adapterremoval_args = save_merged ? "--collapse" : ""
+ }
+ workflow {
+ """
+ input[0] = channel.of([
+ [ id:'test', single_end:false ],
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ ]
+ ])
+ input[1] = false // skip_fastqc
+ input[2] = true // skip_seqfu_check
+ input[3] = true // skip_seqfu_stats
+ input[4] = true // skip_seqkit_stats
+ input[5] = true // skip_seqtk_comp
+ input[6] = false // skip_seqkit_sana_pair
+ input[7] = false // skip_seqkit_seq
+ input[8] = false // skip_seqkit_replace
+ input[9] = false // skip_seqkit_rmdup
+ input[10] = true // skip_umitools_extract
+ input[11] = 0 // val_umi_discard_read
+ input[12] = false // skip_adapterremoval
+ input[13] = "cutadapt" // val_adapter_tool
+ input[14] = [] // ch_custom_adapters_file
+ input[15] = false // val_save_merged
+ input[16] = false // val_fastp_discard_trimmed_pass
+ input[17] = false // val_fastp_save_trimmed_fail
+ input[18] = true // skip_complexity_filtering
+ input[19] = "" // val_complexity_filter_tool
+ input[20] = false // skip_deduplication
+ input[21] = true // skip_decontamination
+ input[22] = [] // ch_decontamination_fasta
+ input[23] = [] // ch_decontamination_reference
+ input[24] = [] // val_decontamination_index_name
+ input[25] = '' // val_decontamination_tool
+ input[26] = false // skip_final_concatenation
+ """
+ }
+ }
+ then {
+ assert workflow.success
+ assertAll(
+ { assert snapshot(
+ path(workflow.out.reads[0][1][0]).linesGzip.size(),
+ path(workflow.out.reads[0][1][1]).linesGzip.size(),
+ file(workflow.out.pre_stats_fastqc_html[0][1][0]).name,
+ file(workflow.out.pre_stats_fastqc_html[0][1][1]).name,
+ file(workflow.out.post_stats_fastqc_html[0][1][0]).name,
+ file(workflow.out.post_stats_fastqc_html[0][1][1]).name,
+ file(workflow.out.pre_stats_fastqc_zip[0][1][0]).name,
+ file(workflow.out.pre_stats_fastqc_zip[0][1][1]).name,
+ file(workflow.out.post_stats_fastqc_zip[0][1][0]).name,
+ file(workflow.out.post_stats_fastqc_zip[0][1][1]).name,
+ path(workflow.out.clumpify_log[0][1]).readLines().size()
+ ).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - adapterremoval - merge - cat more files - paired_end") {
+
+ when {
+ params {
+ save_merged = true
+ adapterremoval_args = save_merged ? "--collapse" : ""
+ }
+ workflow {
+ """
+ input[0] = channel.of(
+ [
+ [ id:'test', single_end:false ],
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)
+ ]
+ ],
+ [
+ [ id:'test2', single_end:false ],
+ [
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test2_1.fastq.gz', checkIfExists: true),
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test2_2.fastq.gz', checkIfExists: true)
+ ]
+ ]
+ )
+ input[1] = true // skip_fastqc
+ input[2] = true // skip_seqfu_check
+ input[3] = true // skip_seqfu_stats
+ input[4] = true // skip_seqkit_stats
+ input[5] = true // skip_seqtk_comp
+ input[6] = true // skip_seqkit_sana_pair
+ input[7] = true // skip_seqkit_seq
+ input[8] = true // skip_seqkit_replace
+ input[9] = true // skip_seqkit_rmdup
+ input[10] = true // skip_umitools_extract
+ input[11] = 0 // val_umi_discard_read
+ input[12] = false // skip_adapterremoval
+ input[13] = "adapterremoval" // val_adapter_tool
+ input[14] = [] // ch_custom_adapters_file
+ input[15] = params.save_merged // val_save_merged
+ input[16] = false // val_fastp_discard_trimmed_pass
+ input[17] = false // val_fastp_save_trimmed_fail
+ input[18] = true // skip_complexity_filtering
+ input[19] = "" // val_complexity_filter_tool
+ input[20] = true // skip_deduplication
+ input[21] = true // skip_decontamination
+ input[22] = [] // ch_decontamination_fasta
+ input[23] = [] // ch_decontamination_reference
+ input[24] = [] // val_decontamination_index_name
+ input[25] = '' // val_decontamination_tool
+ input[26] = false // skip_final_concatenation
+ """
+ }
+ }
+ then {
+ assert workflow.success
+ assertAll(
+ { assert snapshot(
+ workflow.out.reads[0][1],
+ workflow.out.reads[1][1],
+ workflow.out.adapterremoval_discarded_reads.collect { file(it[1]).name }
+ ).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - skip all - single_end") {
+
+ when {
+ params {
+ save_merged = false
+ adapterremoval_args = save_merged ? "--collapse" : ""
+ }
+ workflow {
+ """
+ input[0] = channel.of([
+ [ id:'test', single_end:true ],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ])
+ input[1] = true // skip_fastqc
+ input[2] = true // skip_seqfu_check
+ input[3] = true // skip_seqfu_stats
+ input[4] = true // skip_seqkit_stats
+ input[5] = true // skip_seqtk_comp
+ input[6] = true // skip_seqkit_sana_pair
+ input[7] = true // skip_seqkit_seq
+ input[8] = true // skip_seqkit_replace
+ input[9] = true // skip_seqkit_rmdup
+ input[10] = true // skip_umitools_extract
+ input[11] = 0 // val_umi_discard_read
+ input[12] = true // skip_adapterremoval
+ input[13] = "" // val_adapter_tool
+ input[14] = [] // ch_custom_adapters_file
+ input[15] = false // val_save_merged
+ input[16] = false // val_fastp_discard_trimmed_pass
+ input[17] = false // val_fastp_save_trimmed_fail
+ input[18] = true // skip_complexity_filtering
+ input[19] = "" // val_complexity_filter_tool
+ input[20] = true // skip_deduplication
+ input[21] = true // skip_decontamination
+ input[22] = [] // ch_decontamination_fasta
+ input[23] = [] // ch_decontamination_reference
+ input[24] = [] // val_decontamination_index_name
+ input[25] = "" // val_decontamination_tool
+ input[26] = true // skip_final_concatenation
+ """
+ }
+ }
+ then {
+ assert workflow.success
+ assertAll(
+ { assert snapshot(workflow.out.reads[0][1]).match() }
+ )
+ }
+ }
+
+ test("sarscov2 - fastq - skip all - single_end - stub") {
+
+ options "-stub"
+
+ when {
+ params {
+ save_merged = false
+ adapterremoval_args = save_merged ? "--collapse" : ""
+ }
+ workflow {
+ """
+ input[0] = channel.of([
+ [ id:'test', single_end:true ],
+ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)
+ ])
+ input[1] = true // skip_fastqc
+ input[2] = true // skip_seqfu_check
+ input[3] = true // skip_seqfu_stats
+ input[4] = true // skip_seqkit_stats
+ input[5] = true // skip_seqtk_comp
+ input[6] = true // skip_seqkit_sana_pair
+ input[7] = true // skip_seqkit_seq
+ input[8] = true // skip_seqkit_replace
+ input[9] = true // skip_seqkit_rmdup
+ input[10] = true // skip_umitools_extract
+ input[11] = 0 // val_umi_discard_read
+ input[12] = true // skip_adapterremoval
+ input[13] = "" // val_adapter_tool
+ input[14] = [] // ch_custom_adapters_file
+ input[15] = false // val_save_merged
+ input[16] = false // val_fastp_discard_trimmed_pass
+ input[17] = false // val_fastp_save_trimmed_fail
+ input[18] = true // skip_complexity_filtering
+ input[19] = "" // val_complexity_filter_tool
+ input[20] = true // skip_deduplication
+ input[21] = true // skip_decontamination
+ input[22] = [] // ch_decontamination_fasta
+ input[23] = [] // ch_decontamination_reference
+ input[24] = [] // val_decontamination_index_name
+ input[25] = "" // val_decontamination_tool
+ input[26] = true // skip_final_concatenation
+ """
+ }
+ }
+ then {
+ assert workflow.success
+ assertAll(
+ { assert snapshot(workflow.out.reads).match() }
+ )
+ }
+ }
+}
diff --git a/subworkflows/nf-core/fastq_shortreads_preprocess_qc/tests/main.nf.test.snap b/subworkflows/nf-core/fastq_shortreads_preprocess_qc/tests/main.nf.test.snap
new file mode 100644
index 0000000..d1e0d3f
--- /dev/null
+++ b/subworkflows/nf-core/fastq_shortreads_preprocess_qc/tests/main.nf.test.snap
@@ -0,0 +1,150 @@
+{
+ "sarscov2 - fastq - seqfu - seqkit - deacon - single_end": {
+ "content": [
+ "test.fq.gz:md5,f3a7626275fad3775b6005fad9c13c27",
+ [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.tsv:md5,4c6409169772005cfb06be9e41f2c1e2"
+ ]
+ ],
+ [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.tsv:md5,7573d0c83cfc9af6e1ced67a45265381"
+ ]
+ ],
+ [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.tsv:md5,24dd7cfbb9ae0034d0bd804f464f11fa"
+ ]
+ ],
+ [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test.tsv:md5,35dd18aff6780370b48027fec9c7d900"
+ ]
+ ],
+ [
+ [
+ {
+ "id": "test",
+ "single_end": true,
+ "index_id": "genome"
+ },
+ "genome.idx:md5,84e4985c91800686db9c9dca28fabd1a"
+ ]
+ ],
+ "test.json",
+ [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test_mqc.txt:md5,1facba42f81058e557e3d85dcff2a6f3"
+ ],
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "test_mqc.txt:md5,a039b8c1cc923db88d2484d3abbf00fe"
+ ]
+ ]
+ ],
+ "timestamp": "2026-06-25T10:02:19.793626706",
+ "meta": {
+ "nf-test": "0.9.5",
+ "nextflow": "26.04.3"
+ }
+ },
+ "sarscov2 - fastq - adapterremoval - merge - cat more files - paired_end": {
+ "content": [
+ "test.merged.fastq.gz:md5,369452751050a7f1e31b839702d61417",
+ "test2.merged.fastq.gz:md5,369452751050a7f1e31b839702d61417",
+ [
+ "test.discarded.fastq.gz",
+ "test2.discarded.fastq.gz"
+ ]
+ ],
+ "timestamp": "2026-06-08T19:07:29.023690862",
+ "meta": {
+ "nf-test": "0.9.4",
+ "nextflow": "26.03.0"
+ }
+ },
+ "sarscov2 - fastq - fastqc - seqkit - cutadapt - clumpify - cat - paired_end": {
+ "content": [
+ 400,
+ 400,
+ "test_1_fastqc.html",
+ "test_2_fastqc.html",
+ "test_1_fastqc.html",
+ "test_2_fastqc.html",
+ "test_1_fastqc.zip",
+ "test_2_fastqc.zip",
+ "test_1_fastqc.zip",
+ "test_2_fastqc.zip",
+ 41
+ ],
+ "timestamp": "2026-06-08T19:13:23.478389692",
+ "meta": {
+ "nf-test": "0.9.4",
+ "nextflow": "26.03.0"
+ }
+ },
+ "sarscov2 - fastq - skip all - single_end": {
+ "content": [
+ "/nf-core/test-datasets/modules/data/genomics/sarscov2/illumina/fastq/test_1.fastq.gz"
+ ],
+ "timestamp": "2026-06-08T19:07:33.894706204",
+ "meta": {
+ "nf-test": "0.9.4",
+ "nextflow": "26.03.0"
+ }
+ },
+ "sarscov2 - fastq - umitools - prinseq - clumpify - cat - single_end": {
+ "content": [
+ 400,
+ 51,
+ 41
+ ],
+ "timestamp": "2026-06-08T19:07:12.173965635",
+ "meta": {
+ "nf-test": "0.9.4",
+ "nextflow": "26.03.0"
+ }
+ },
+ "sarscov2 - fastq - skip all - single_end - stub": {
+ "content": [
+ [
+ [
+ {
+ "id": "test",
+ "single_end": true
+ },
+ "/nf-core/test-datasets/modules/data/genomics/sarscov2/illumina/fastq/test_1.fastq.gz"
+ ]
+ ]
+ ],
+ "timestamp": "2026-01-13T12:17:19.455574032",
+ "meta": {
+ "nf-test": "0.9.3",
+ "nextflow": "25.10.2"
+ }
+ }
+}
\ No newline at end of file
diff --git a/subworkflows/nf-core/fastq_shortreads_preprocess_qc/tests/nextflow.config b/subworkflows/nf-core/fastq_shortreads_preprocess_qc/tests/nextflow.config
new file mode 100644
index 0000000..ab4478f
--- /dev/null
+++ b/subworkflows/nf-core/fastq_shortreads_preprocess_qc/tests/nextflow.config
@@ -0,0 +1,60 @@
+process {
+ withName: "SEQKIT_SANA" {
+ ext.prefix = { "${meta.id}_${meta.strandness}" }
+ }
+
+ withName: "SEQKIT_SEQ" {
+ ext.args = [
+ "--remove-gaps",
+ "--upper-case",
+ "--validate-seq",
+ "--min-len 30",
+ "--max-len 5000"
+ ].join(' ').trim()
+ ext.prefix = { "intermediate_seqkit_seq_${meta.strandness}" }
+ }
+
+ withName: "SEQKIT_REPLACE" {
+ ext.args = '-p "/" -r "_"'
+ ext.prefix = { "intermediate_seqkit_replace_${meta.strandness}" }
+ }
+
+ withName: "SEQKIT_RMDUP" {
+ ext.prefix = { "${meta.id}_${meta.strandness}" }
+ }
+
+ withName: "UMITOOLS_EXTRACT" {
+ ext.args = '--bc-pattern="NNNN"'
+ }
+
+ withName: "CUTADAPT" {
+ ext.args = '-q 25'
+ }
+
+ withName: "ADAPTERREMOVAL_PE" {
+ ext.args = params.adapterremoval_args
+ }
+
+ withName: ".*:FASTQ_COMPLEXITY_FILTER:BBMAP_BBDUK" {
+ ext.args = [
+ 'entropy=0.7', // needed to turn on complexity filtering
+ 'minlength=0' // needed, to not discard reads shorter than this
+ ].join(' ')
+ ext.prefix = { "${meta.id}.trim" }
+ }
+
+ // need FASTP to only do complexity filtering
+ withName: ".*:FASTQ_COMPLEXITY_FILTER:FASTP" {
+ ext.args = [
+ '--low_complexity_filter',
+ '--disable_adapter_trimming',
+ '--disable_trim_poly_g',
+ '--disable_quality_filtering',
+ '--disable_length_filtering'
+ ].join(' ')
+ }
+
+ withName: "BBMAP_CLUMPIFY" {
+ ext.args = 'dedupe=t'
+ }
+}
diff --git a/workflows/funcprofiler.nf b/workflows/funcprofiler.nf
index d6ded93..6f3a9d3 100644
--- a/workflows/funcprofiler.nf
+++ b/workflows/funcprofiler.nf
@@ -3,7 +3,6 @@
IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
-include { FASTQC } from '../modules/nf-core/fastqc/main'
include { MULTIQC } from '../modules/nf-core/multiqc/main'
include { paramsSummaryMap } from 'plugin/nf-schema'
include { paramsSummaryMultiqc } from '../subworkflows/nf-core/utils_nfcore_pipeline'
@@ -62,12 +61,7 @@ workflow FUNCPROFILER {
samplesheet
)
- if (!params.skip_preprocessing_qc) {
- FASTQC(
- DATAPREP.out.reads
- )
- ch_multiqc_files = ch_multiqc_files.mix(FASTQC.out.zip.map { _meta, zip -> zip })
- }
+ ch_multiqc_files = ch_multiqc_files.mix(DATAPREP.out.multiqc_files)
DBPREP(
databases
From 6613458f327f2112e0987379cb153784e43e1356 Mon Sep 17 00:00:00 2001
From: vinisalazar <17276653+vinisalazar@users.noreply.github.com>
Date: Wed, 5 Aug 2026 17:44:14 +1000
Subject: [PATCH 3/4] Drop the orphaned run-merging schema group and param
The dev merge kept an allOf entry pointing at preprocessing_run_merging_options
after dev had deleted the group itself, which failed schema_lint on #76 with
"Subschema `preprocessing_run_merging_options` found in `allOf` but not in
`$defs`". Drop the dangling ref, and the save_runmerged_reads param it described,
which dev had already removed as unused.
---
nextflow.config | 1 -
nextflow_schema.json | 3 ---
2 files changed, 4 deletions(-)
diff --git a/nextflow.config b/nextflow.config
index 23e0b9b..5bb7f45 100644
--- a/nextflow.config
+++ b/nextflow.config
@@ -51,7 +51,6 @@ params {
// fastq_shortreads_preprocess_qc subworkflow
// FASTQ preprocessing
skip_preprocessing_qc = false
- save_runmerged_reads = false
save_preprocessed_reads = false
// Short read adapter trimming / quality filtering
diff --git a/nextflow_schema.json b/nextflow_schema.json
index 25d9f4a..ab2fe2f 100644
--- a/nextflow_schema.json
+++ b/nextflow_schema.json
@@ -410,9 +410,6 @@
{
"$ref": "#/$defs/preprocessing_hostremoval_options"
},
- {
- "$ref": "#/$defs/preprocessing_run_merging_options"
- },
{
"$ref": "#/$defs/profiling_options"
},
From b46d2aaa6d23f225d439cd5a9b04f9508c072efc Mon Sep 17 00:00:00 2001
From: vinisalazar <17276653+vinisalazar@users.noreply.github.com>
Date: Wed, 5 Aug 2026 17:53:58 +1000
Subject: [PATCH 4/4] Test FastQC output and document database version
compatibility
The default test disabled FASTQC with ext.when = false, so docs/output.md
advertised a fastqc/ directory the snapshot never contained. tests/.nftignore
already ignored FastQC report contents, so dropping the gate records the paths
without depending on unstable content. Runtime goes from ~170s to ~366s.
Also note that the pipeline never checks db_path against the tool version, with
the combinations exercised on the pilot cohort.
---
docs/usage.md | 14 ++++++++++++++
tests/default.nf.test.snap | 11 ++++++++++-
tests/nextflow.config | 3 ---
3 files changed, 24 insertions(+), 4 deletions(-)
diff --git a/docs/usage.md b/docs/usage.md
index f26e62d..86af88b 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -110,6 +110,20 @@ Use the `db_name` column to record the database release or version used for the
| `db_params` | No | Additional parameters to pass to the profiler (no quotes allowed). |
| `db_path` | Yes | Absolute path to the database file or directory. Gzipped TAR archives (`.tar.gz`) are automatically decompressed. |
+### Database versions and compatibility
+
+The pipeline passes `db_path` straight to the profiler and never checks it against the tool version, so pairing a database with a compatible tool release is up to you. The versions a run actually used are recorded in `/pipeline_info/nf_core_funcprofiler_software_mqc_versions.yml`.
+
+These combinations were exercised on a human gut metagenome cohort during development; RGI, DIAMOND and HUMAnN v4 were only tested against the small CI databases.
+
+| Tool | Tool version | Database |
+| --------------- | ------------ | ----------------------------------------------------------------------------------------------------------------- |
+| HUMAnN v3 | 3.6.1 | ChocoPhlAn full `v201901_v31`, UniRef90 `201901b` full, `utility_mapping` full |
+| MetaPhlAn | 4.0.6 | `mpa_vJan21_CHOCOPhlAnSGB_202103` |
+| FMH FunProfiler | 1.1.1 | KO sketches from [Zenodo record 10045253](https://zenodo.org/records/10045253), scaled 1000 / k=11 and scaled 500 |
+| mi-faser | 1.64 | `GS-24-all`, shipped inside the mi-faser biocontainer rather than downloaded separately |
+| eggNOG-mapper | 2.1.13 | eggNOG 5.0.2 data directory |
+
### HUMANn databases
HUMANn requires four database components per named database, each as a separate row with the same `db_name`. The example below uses a HUMANn v3-compatible UniRef90 database set; replace `uniref90_v3` with the exact release or version used in your analysis.
diff --git a/tests/default.nf.test.snap b/tests/default.nf.test.snap
index 34c585e..8ba63e6 100644
--- a/tests/default.nf.test.snap
+++ b/tests/default.nf.test.snap
@@ -4,6 +4,15 @@
[
"cat",
"cat/minigut.merged.fastq.gz",
+ "fastqc",
+ "fastqc/minigut_1_processed_1_fastqc.html",
+ "fastqc/minigut_1_processed_1_fastqc.zip",
+ "fastqc/minigut_1_processed_2_fastqc.html",
+ "fastqc/minigut_1_processed_2_fastqc.zip",
+ "fastqc/minigut_1_raw_1_fastqc.html",
+ "fastqc/minigut_1_raw_1_fastqc.zip",
+ "fastqc/minigut_1_raw_2_fastqc.html",
+ "fastqc/minigut_1_raw_2_fastqc.zip",
"fmhfunprofiler",
"fmhfunprofiler/KOs_sketched_scaled_1000_demo.sig.zip",
"fmhfunprofiler/KOs_sketched_scaled_1000_demo.sig.zip/minigut_KOs_sketched_scaled_1000_demo.sig.fmhfunprofiler.csv",
@@ -92,7 +101,7 @@
"snps.txt:md5,529d4fdb74c8940c0930ec1a7a6d4980"
]
],
- "timestamp": "2026-07-31T09:50:44.24986845",
+ "timestamp": "2026-08-05T17:53:23.842325056",
"meta": {
"nf-test": "0.9.5",
"nextflow": "26.04.6"
diff --git a/tests/nextflow.config b/tests/nextflow.config
index cbac3c9..3afd5ca 100644
--- a/tests/nextflow.config
+++ b/tests/nextflow.config
@@ -25,9 +25,6 @@ process {
saveAs: { filename -> filename.equals('versions.yml') ? null : filename }
]
- withName: 'FASTQC' {
- ext.when = false
- }
withName: 'MULTIQC' {
ext.when = false
}