diff --git a/modules/nf-core/fastaguard/environment.yml b/modules/nf-core/fastaguard/environment.yml new file mode 100644 index 000000000000..0448a46b6d29 --- /dev/null +++ b/modules/nf-core/fastaguard/environment.yml @@ -0,0 +1,5 @@ +channels: + - conda-forge + - bioconda +dependencies: + - bioconda::fastaguard=0.6.0 diff --git a/modules/nf-core/fastaguard/main.nf b/modules/nf-core/fastaguard/main.nf new file mode 100644 index 000000000000..27e1d4b8eb45 --- /dev/null +++ b/modules/nf-core/fastaguard/main.nf @@ -0,0 +1,34 @@ +process FASTAGUARD { + 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/fastaguard:0.6.0--hfa8f182_0': + 'quay.io/biocontainers/fastaguard:0.6.0--hfa8f182_0' }" + + input: + tuple val(meta), path(fasta) + + output: + tuple val(meta), path("*.fastaguard.html"), emit: html + tuple val(meta), path("*.fastaguard.json"), emit: json + tuple val(meta), path("*.fastaguard.tsv"), emit: tsv + tuple val(meta), path("*.fastaguard_mqc.json"), emit: mqc + tuple val("${task.process}"), val('fastaguard'), eval('fastaguard --version | cut -d " " -f 2'), emit: versions_fastaguard, topic: versions + + when: + task.ext.when == null || task.ext.when + + script: + def prefix = task.ext.prefix ?: "${meta.id}" + def args = task.ext.args ?: '' + """ + fastaguard ${fasta} \ + ${args} \ + --out ${prefix}.fastaguard.html \ + --json ${prefix}.fastaguard.json \ + --tsv ${prefix}.fastaguard.tsv \ + --multiqc ${prefix}.fastaguard_mqc.json + """ +} diff --git a/modules/nf-core/fastaguard/meta.yml b/modules/nf-core/fastaguard/meta.yml new file mode 100644 index 000000000000..f54895873ff9 --- /dev/null +++ b/modules/nf-core/fastaguard/meta.yml @@ -0,0 +1,97 @@ +name: "fastaguard" +description: FASTA preflight QC for assembly pipelines +keywords: + - fasta + - assembly + - qc +tools: + - "fastaguard": + description: FASTA preflight QC for assembly pipelines + homepage: "https://github.com/ehsanestaji/FastaGuard" + documentation: "https://github.com/ehsanestaji/FastaGuard" + tool_dev_url: "https://github.com/ehsanestaji/FastaGuard" + licence: + - "MIT" + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'sample' ] + - fasta: + type: file + description: Assembly FASTA file + pattern: "*.{fa,fasta,fa.gz,fasta.gz}" + ontologies: [] +output: + html: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'sample' ] + - "*.fastaguard.html": + type: file + description: FastaGuard HTML report + pattern: "*.fastaguard.html" + ontologies: [] + json: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'sample' ] + - "*.fastaguard.json": + type: file + description: FastaGuard JSON report + pattern: "*.fastaguard.json" + ontologies: + - edam: http://edamontology.org/format_3464 + tsv: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'sample' ] + - "*.fastaguard.tsv": + type: file + description: FastaGuard TSV summary + pattern: "*.fastaguard.tsv" + ontologies: + - edam: http://edamontology.org/format_3475 + mqc: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'sample' ] + - "*.fastaguard_mqc.json": + type: file + description: MultiQC custom-content JSON + pattern: "*.fastaguard_mqc.json" + ontologies: + - edam: http://edamontology.org/format_3464 + versions_fastaguard: + - - ${task.process}: + type: string + description: The process the versions were collected from + - fastaguard: + type: string + description: The tool name + - fastaguard --version | cut -d " " -f 2: + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - fastaguard: + type: string + description: The tool name + - fastaguard --version | cut -d " " -f 2: + type: eval + description: The expression to obtain the version of the tool +authors: + - "@ehsanestaji" diff --git a/modules/nf-core/fastaguard/tests/main.nf.test b/modules/nf-core/fastaguard/tests/main.nf.test new file mode 100644 index 000000000000..24304bd0d08d --- /dev/null +++ b/modules/nf-core/fastaguard/tests/main.nf.test @@ -0,0 +1,123 @@ +nextflow_process { + + name "Test FASTAGUARD" + script "../main.nf" + process "FASTAGUARD" + config "./nextflow.config" + + tag "modules" + tag "modules_nfcore" + tag "fastaguard" + tag "fastaguard_single" + + test("pass FASTA emits all reports") { + when { + process { + """ + def fasta = file(workDir.resolve("pass.fa").toString()) + fasta.text = [ + ">clean", + ("ACGT" * 60) + ].join("\\n") + "\\n" + + input[0] = [ + [ id:'pass' ], + fasta + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out, unstableKeys: ["html", "json", "tsv", "mqc"])).match() } + ) + } + } + + test("warn FASTA preserves reports") { + when { + process { + """ + def fasta = file(workDir.resolve("warn.fa").toString()) + fasta.text = [ + ">long", + ("ACGT" * 60), + ">tiny", + "ACGT" + ].join("\\n") + "\\n" + + input[0] = [ + [ id:'warn' ], + fasta + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out, unstableKeys: ["html", "json", "tsv", "mqc"])).match() } + ) + } + } + + test("fail FASTA preserves reports for gate review") { + when { + process { + """ + def fasta = file(workDir.resolve("fail.fa").toString()) + fasta.text = [ + ">dup", + "ACGTACGT", + ">dup", + "ACGTACGT", + ">bad", + "ACGTXYZ" + ].join("\\n") + "\\n" + + input[0] = [ + [ id:'fail' ], + fasta + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out, unstableKeys: ["html", "json", "tsv", "mqc"])).match() } + ) + } + } + + test("invalid FASTA is represented in the evidence path") { + when { + process { + """ + def fasta = file(workDir.resolve("invalid.fa").toString()) + fasta.text = [ + ">empty_record", + ">next_record", + "ACGT" + ].join("\\n") + "\\n" + + input[0] = [ + [ id:'invalid' ], + fasta + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out, unstableKeys: ["html", "json", "tsv", "mqc"])).match() } + ) + } + } +} diff --git a/modules/nf-core/fastaguard/tests/main.nf.test.snap b/modules/nf-core/fastaguard/tests/main.nf.test.snap new file mode 100644 index 000000000000..d702507ca36a --- /dev/null +++ b/modules/nf-core/fastaguard/tests/main.nf.test.snap @@ -0,0 +1,202 @@ +{ + "invalid FASTA is represented in the evidence path": { + "content": [ + { + "html": [ + [ + { + "id": "invalid" + }, + "invalid.fastaguard.html" + ] + ], + "json": [ + [ + { + "id": "invalid" + }, + "invalid.fastaguard.json" + ] + ], + "mqc": [ + [ + { + "id": "invalid" + }, + "invalid.fastaguard_mqc.json" + ] + ], + "tsv": [ + [ + { + "id": "invalid" + }, + "invalid.fastaguard.tsv" + ] + ], + "versions_fastaguard": [ + [ + "FASTAGUARD", + "fastaguard", + "0.6.0" + ] + ] + } + ], + "timestamp": "2026-07-23T12:35:19.837744", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.04.6" + } + }, + "fail FASTA preserves reports for gate review": { + "content": [ + { + "html": [ + [ + { + "id": "fail" + }, + "fail.fastaguard.html" + ] + ], + "json": [ + [ + { + "id": "fail" + }, + "fail.fastaguard.json" + ] + ], + "mqc": [ + [ + { + "id": "fail" + }, + "fail.fastaguard_mqc.json" + ] + ], + "tsv": [ + [ + { + "id": "fail" + }, + "fail.fastaguard.tsv" + ] + ], + "versions_fastaguard": [ + [ + "FASTAGUARD", + "fastaguard", + "0.6.0" + ] + ] + } + ], + "timestamp": "2026-07-23T12:34:59.917502", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.04.6" + } + }, + "warn FASTA preserves reports": { + "content": [ + { + "html": [ + [ + { + "id": "warn" + }, + "warn.fastaguard.html" + ] + ], + "json": [ + [ + { + "id": "warn" + }, + "warn.fastaguard.json" + ] + ], + "mqc": [ + [ + { + "id": "warn" + }, + "warn.fastaguard_mqc.json" + ] + ], + "tsv": [ + [ + { + "id": "warn" + }, + "warn.fastaguard.tsv" + ] + ], + "versions_fastaguard": [ + [ + "FASTAGUARD", + "fastaguard", + "0.6.0" + ] + ] + } + ], + "timestamp": "2026-07-23T12:34:47.135258", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.04.6" + } + }, + "pass FASTA emits all reports": { + "content": [ + { + "html": [ + [ + { + "id": "pass" + }, + "pass.fastaguard.html" + ] + ], + "json": [ + [ + { + "id": "pass" + }, + "pass.fastaguard.json" + ] + ], + "mqc": [ + [ + { + "id": "pass" + }, + "pass.fastaguard_mqc.json" + ] + ], + "tsv": [ + [ + { + "id": "pass" + }, + "pass.fastaguard.tsv" + ] + ], + "versions_fastaguard": [ + [ + "FASTAGUARD", + "fastaguard", + "0.6.0" + ] + ] + } + ], + "timestamp": "2026-07-23T12:34:36.226584", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.04.6" + } + } +} \ No newline at end of file diff --git a/modules/nf-core/fastaguard/tests/nextflow.config b/modules/nf-core/fastaguard/tests/nextflow.config new file mode 100644 index 000000000000..245733682dcc --- /dev/null +++ b/modules/nf-core/fastaguard/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: 'FASTAGUARD' { + ext.args = '--profile assembly --gate pipeline' + } +}