From 0a3699210ef67e0f44bbc152698a853ef557ff2d Mon Sep 17 00:00:00 2001 From: vagkaratzas Date: Tue, 12 May 2026 10:55:47 +0100 Subject: [PATCH 1/5] bots initial attempt --- .../analyzeprimers/environment.yml | 7 ++ .../primerprospector/analyzeprimers/main.nf | 73 +++++++++++++++ .../primerprospector/analyzeprimers/meta.yml | 89 +++++++++++++++++++ .../analyzeprimers/tests/main.nf.test | 77 ++++++++++++++++ .../analyzeprimers/tests/main.nf.test.snap | 63 +++++++++++++ 5 files changed, 309 insertions(+) create mode 100644 modules/nf-core/primerprospector/analyzeprimers/environment.yml create mode 100644 modules/nf-core/primerprospector/analyzeprimers/main.nf create mode 100644 modules/nf-core/primerprospector/analyzeprimers/meta.yml create mode 100644 modules/nf-core/primerprospector/analyzeprimers/tests/main.nf.test create mode 100644 modules/nf-core/primerprospector/analyzeprimers/tests/main.nf.test.snap diff --git a/modules/nf-core/primerprospector/analyzeprimers/environment.yml b/modules/nf-core/primerprospector/analyzeprimers/environment.yml new file mode 100644 index 00000000000..fd12537c4f9 --- /dev/null +++ b/modules/nf-core/primerprospector/analyzeprimers/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::primerprospector=1.0.1 diff --git a/modules/nf-core/primerprospector/analyzeprimers/main.nf b/modules/nf-core/primerprospector/analyzeprimers/main.nf new file mode 100644 index 00000000000..f32a01f2987 --- /dev/null +++ b/modules/nf-core/primerprospector/analyzeprimers/main.nf @@ -0,0 +1,73 @@ +process PRIMERPROSPECTOR_ANALYZEPRIMERS { + 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/primerprospector:1.0.1--py27_0' : + 'quay.io/biocontainers/primerprospector:1.0.1--py27_0' }" + + input: + tuple val(meta), path(fasta), path(primers) + + output: + tuple val(meta), path("${prefix}.*_hits.txt"), emit: hits + tuple val(meta), path("${prefix}.*.ps") , emit: plots + tuple val("${task.process}"), val('primerprospector'), eval("analyze_primers.py --version 2>&1 | sed 's/.* //; s/-release//'"), topic: versions, emit: versions_primerprospector + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + def fasta_arg = fasta instanceof List ? fasta.join(':') : fasta + def primer_arg = primers ? "-P \"${primers}\"" : '' + def arg_tokens = args.tokenize() + if (arg_tokens.any { arg -> arg == '-f' || arg.startsWith('--fasta_seqs') || arg == '-P' || arg.startsWith('--primers_filepath') || arg == '-o' || arg.startsWith('--output_dir') }) { + error "'-f/--fasta_seqs', '-P/--primers_filepath' and '-o/--output_dir' are reserved by this module. Use input files for fasta/primers and task.ext.prefix to control output file prefixes." + } + if (!primers && !(arg_tokens.any { arg -> arg == '-p' || arg.startsWith('--primer_name') } && arg_tokens.any { arg -> arg == '-s' || arg.startsWith('--primer_sequence') })) { + error "Provide a primers file in the input tuple, or specify a single primer with '-p/--primer_name' and '-s/--primer_sequence' in task.ext.args." + } + """ + mkdir -p primerprospector_out + + cat <<'PY' > analyze_primers_compat.py + import __builtin__ + _range = __builtin__.range + def _int_range(*args): + return _range(*[int(arg) for arg in args]) + __builtin__.range = _int_range + execfile('/usr/local/bin/analyze_primers.py') + PY + + python analyze_primers_compat.py \\ + $args \\ + -f "${fasta_arg}" \\ + ${primer_arg} \\ + -o primerprospector_out + + for file in primerprospector_out/*_hits.txt primerprospector_out/*.ps; do + [ -e "\$file" ] || continue + mv "\$file" "${prefix}.\$(basename "\$file")" + done + """ + + stub: + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + def arg_tokens = args.tokenize() + if (arg_tokens.any { arg -> arg == '-f' || arg.startsWith('--fasta_seqs') || arg == '-P' || arg.startsWith('--primers_filepath') || arg == '-o' || arg.startsWith('--output_dir') }) { + error "'-f/--fasta_seqs', '-P/--primers_filepath' and '-o/--output_dir' are reserved by this module. Use input files for fasta/primers and task.ext.prefix to control output file prefixes." + } + if (!primers && !(arg_tokens.any { arg -> arg == '-p' || arg.startsWith('--primer_name') } && arg_tokens.any { arg -> arg == '-s' || arg.startsWith('--primer_sequence') })) { + error "Provide a primers file in the input tuple, or specify a single primer with '-p/--primer_name' and '-s/--primer_sequence' in task.ext.args." + } + """ + echo "$args" + + touch ${prefix}.primer_fasta_hits.txt + touch ${prefix}.primer_fasta.ps + """ +} diff --git a/modules/nf-core/primerprospector/analyzeprimers/meta.yml b/modules/nf-core/primerprospector/analyzeprimers/meta.yml new file mode 100644 index 00000000000..ebf3f64e6de --- /dev/null +++ b/modules/nf-core/primerprospector/analyzeprimers/meta.yml @@ -0,0 +1,89 @@ +name: "primerprospector_analyzeprimers" +description: Score PCR primers for binding to target sequences +keywords: + - primer design + - pcr + - fasta + - sequence analysis + - primer scoring +tools: + - "primerprospector": + description: "Primer Prospector is a pipeline of programs to design and analyze PCR primers." + homepage: "https://pprospector.sourceforge.net/" + documentation: "https://pprospector.sourceforge.net/scripts/analyze_primers.html" + tool_dev_url: "https://sourceforge.net/p/pprospector/code/" + doi: "10.1093/bioinformatics/btr087" + licence: + - "GPL" + identifier: "" + args_id: "$args" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information. Mandatory. + e.g. `[ id:'sample1' ]` + - fasta: + type: file + description: Target FASTA file or files to score primers against. Mandatory. + pattern: "*.{fasta,fa,fna}" + ontologies: + - edam: "http://edamontology.org/format_1929" # FASTA + - primers: + type: file + description: | + Tab-delimited primers file containing primer names and sequences. Optional + when primer name and sequence are supplied with task.ext.args. + pattern: "*.{txt,tsv}" + ontologies: + - edam: "http://edamontology.org/format_3475" # TSV +output: + hits: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - ${prefix}.*_hits.txt: + type: file + description: Primer hit details, mismatches, gaps, positions, and weighted scores. + pattern: "*_hits.txt" + ontologies: + - edam: "http://edamontology.org/format_3752" # CSV + plots: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - ${prefix}.*.ps: + type: file + description: PostScript summary plots of primer mismatch and weighted score information. + pattern: "*.ps" + ontologies: + - edam: "http://edamontology.org/format_2330" # Textual format + versions_primerprospector: + - - ${task.process}: + type: string + description: The name of the process + - primerprospector: + type: string + description: The name of the tool + - analyze_primers.py --version 2>&1 | sed 's/.* //; s/-release//': + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - primerprospector: + type: string + description: The name of the tool + - analyze_primers.py --version 2>&1 | sed 's/.* //; s/-release//': + type: eval + description: The expression to obtain the version of the tool +authors: + - "@vagkaratzas" +maintainers: + - "@vagkaratzas" diff --git a/modules/nf-core/primerprospector/analyzeprimers/tests/main.nf.test b/modules/nf-core/primerprospector/analyzeprimers/tests/main.nf.test new file mode 100644 index 00000000000..7be5cfc6ad2 --- /dev/null +++ b/modules/nf-core/primerprospector/analyzeprimers/tests/main.nf.test @@ -0,0 +1,77 @@ +nextflow_process { + + name "Test Process PRIMERPROSPECTOR_ANALYZEPRIMERS" + script "../main.nf" + process "PRIMERPROSPECTOR_ANALYZEPRIMERS" + + tag "modules" + tag "modules_nfcore" + tag "primerprospector" + tag "primerprospector/analyzeprimers" + + test("synthetic - fasta primers - hits plots") { + + when { + process { + """ + def fasta = channel.of( + '>seq1', + 'AAACCCGGGTTTAAACCCGGGTTT', + '>seq2', + 'GGGTTTAAACCCGGGTTTAAA' + ).collectFile(name: 'test.fasta', newLine: true, sort: false) + def primers = channel.of( + 'testf\\tAAACCCGGGTTT' + ).collectFile(name: 'primers.txt', newLine: true, sort: false) + + input[0] = fasta.combine(primers).map { fasta_file, primers_file -> + [[ id:'test' ], fasta_file, primers_file] + } + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.hits, + path(process.out.plots[0][1]).exists(), + process.out.findAll { key, val -> key.startsWith("versions") } + ).match() } + ) + } + } + + test("synthetic - fasta primers - hits plots - stub") { + + options "-stub" + + when { + process { + """ + def fasta = channel.of( + '>seq1', + 'AAACCCGGGTTTAAACCCGGGTTT', + '>seq2', + 'GGGTTTAAACCCGGGTTTAAA' + ).collectFile(name: 'test.fasta', newLine: true, sort: false) + def primers = channel.of( + 'testf\\tAAACCCGGGTTT' + ).collectFile(name: 'primers.txt', newLine: true, sort: false) + + input[0] = fasta.combine(primers).map { fasta_file, primers_file -> + [[ id:'test' ], fasta_file, primers_file] + } + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } +} diff --git a/modules/nf-core/primerprospector/analyzeprimers/tests/main.nf.test.snap b/modules/nf-core/primerprospector/analyzeprimers/tests/main.nf.test.snap new file mode 100644 index 00000000000..014d0a15793 --- /dev/null +++ b/modules/nf-core/primerprospector/analyzeprimers/tests/main.nf.test.snap @@ -0,0 +1,63 @@ +{ + "synthetic - fasta primers - hits plots - stub": { + "content": [ + { + "hits": [ + [ + { + "id": "test" + }, + "test.primer_fasta_hits.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "plots": [ + [ + { + "id": "test" + }, + "test.primer_fasta.ps:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_primerprospector": [ + [ + "PRIMERPROSPECTOR_ANALYZEPRIMERS", + "primerprospector", + "1.0.1" + ] + ] + } + ], + "timestamp": "2026-05-12T10:39:03.713196736", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.0" + } + }, + "synthetic - fasta primers - hits plots": { + "content": [ + [ + [ + { + "id": "test" + }, + "test.testf_test_hits.txt:md5,5ad64787f36ad9ecea34d8bcd912d164" + ] + ], + true, + { + "versions_primerprospector": [ + [ + "PRIMERPROSPECTOR_ANALYZEPRIMERS", + "primerprospector", + "1.0.1" + ] + ] + } + ], + "timestamp": "2026-05-12T10:50:06.050073353", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.0" + } + } +} \ No newline at end of file From b0310630115044d6591a36442971cd937f4b59c8 Mon Sep 17 00:00:00 2001 From: vagkaratzas Date: Tue, 12 May 2026 11:18:05 +0100 Subject: [PATCH 2/5] removed unnecessary temp output dir, and renamed output files, added comment for bug workaround --- .../primerprospector/analyzeprimers/main.nf | 21 ++++++++----------- .../primerprospector/analyzeprimers/meta.yml | 6 +++--- .../analyzeprimers/tests/main.nf.test.snap | 10 ++++----- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/modules/nf-core/primerprospector/analyzeprimers/main.nf b/modules/nf-core/primerprospector/analyzeprimers/main.nf index f32a01f2987..6d19e03512d 100644 --- a/modules/nf-core/primerprospector/analyzeprimers/main.nf +++ b/modules/nf-core/primerprospector/analyzeprimers/main.nf @@ -11,8 +11,8 @@ process PRIMERPROSPECTOR_ANALYZEPRIMERS { tuple val(meta), path(fasta), path(primers) output: - tuple val(meta), path("${prefix}.*_hits.txt"), emit: hits - tuple val(meta), path("${prefix}.*.ps") , emit: plots + tuple val(meta), path("${prefix}_hits.txt"), emit: hits + tuple val(meta), path("${prefix}.ps") , emit: plots tuple val("${task.process}"), val('primerprospector'), eval("analyze_primers.py --version 2>&1 | sed 's/.* //; s/-release//'"), topic: versions, emit: versions_primerprospector when: @@ -31,8 +31,8 @@ process PRIMERPROSPECTOR_ANALYZEPRIMERS { error "Provide a primers file in the input tuple, or specify a single primer with '-p/--primer_name' and '-s/--primer_sequence' in task.ext.args." } """ - mkdir -p primerprospector_out - + # Primer Prospector 1.0.1 passes numpy floats to range() while plotting. + # This shim avoids a Python 2 TypeError that otherwise stops .ps output generation. cat <<'PY' > analyze_primers_compat.py import __builtin__ _range = __builtin__.range @@ -45,13 +45,10 @@ process PRIMERPROSPECTOR_ANALYZEPRIMERS { python analyze_primers_compat.py \\ $args \\ -f "${fasta_arg}" \\ - ${primer_arg} \\ - -o primerprospector_out + ${primer_arg} - for file in primerprospector_out/*_hits.txt primerprospector_out/*.ps; do - [ -e "\$file" ] || continue - mv "\$file" "${prefix}.\$(basename "\$file")" - done + mv *_hits.txt "${prefix}_hits.txt" + mv *.ps "${prefix}.ps" """ stub: @@ -67,7 +64,7 @@ process PRIMERPROSPECTOR_ANALYZEPRIMERS { """ echo "$args" - touch ${prefix}.primer_fasta_hits.txt - touch ${prefix}.primer_fasta.ps + touch ${prefix}_hits.txt + touch ${prefix}.ps """ } diff --git a/modules/nf-core/primerprospector/analyzeprimers/meta.yml b/modules/nf-core/primerprospector/analyzeprimers/meta.yml index ebf3f64e6de..dd636bc5485 100644 --- a/modules/nf-core/primerprospector/analyzeprimers/meta.yml +++ b/modules/nf-core/primerprospector/analyzeprimers/meta.yml @@ -15,8 +15,8 @@ tools: doi: "10.1093/bioinformatics/btr087" licence: - "GPL" - identifier: "" args_id: "$args" + identifier: "" input: - - meta: type: map @@ -44,7 +44,7 @@ output: description: | Groovy Map containing sample information e.g. `[ id:'sample1' ]` - - ${prefix}.*_hits.txt: + - ${prefix}_hits.txt: type: file description: Primer hit details, mismatches, gaps, positions, and weighted scores. pattern: "*_hits.txt" @@ -56,7 +56,7 @@ output: description: | Groovy Map containing sample information e.g. `[ id:'sample1' ]` - - ${prefix}.*.ps: + - ${prefix}.ps: type: file description: PostScript summary plots of primer mismatch and weighted score information. pattern: "*.ps" diff --git a/modules/nf-core/primerprospector/analyzeprimers/tests/main.nf.test.snap b/modules/nf-core/primerprospector/analyzeprimers/tests/main.nf.test.snap index 014d0a15793..2094eaf51c3 100644 --- a/modules/nf-core/primerprospector/analyzeprimers/tests/main.nf.test.snap +++ b/modules/nf-core/primerprospector/analyzeprimers/tests/main.nf.test.snap @@ -7,7 +7,7 @@ { "id": "test" }, - "test.primer_fasta_hits.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + "test_hits.txt:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], "plots": [ @@ -15,7 +15,7 @@ { "id": "test" }, - "test.primer_fasta.ps:md5,d41d8cd98f00b204e9800998ecf8427e" + "test.ps:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], "versions_primerprospector": [ @@ -27,7 +27,7 @@ ] } ], - "timestamp": "2026-05-12T10:39:03.713196736", + "timestamp": "2026-05-12T11:08:06.296032028", "meta": { "nf-test": "0.9.5", "nextflow": "26.04.0" @@ -40,7 +40,7 @@ { "id": "test" }, - "test.testf_test_hits.txt:md5,5ad64787f36ad9ecea34d8bcd912d164" + "test_hits.txt:md5,5ad64787f36ad9ecea34d8bcd912d164" ] ], true, @@ -54,7 +54,7 @@ ] } ], - "timestamp": "2026-05-12T10:50:06.050073353", + "timestamp": "2026-05-12T11:07:58.777832191", "meta": { "nf-test": "0.9.5", "nextflow": "26.04.0" From 1c38824970d1847c85155ec84036597788615186 Mon Sep 17 00:00:00 2001 From: vagkaratzas Date: Tue, 12 May 2026 11:21:33 +0100 Subject: [PATCH 3/5] flagged the bug in the comment --- modules/nf-core/primerprospector/analyzeprimers/main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/nf-core/primerprospector/analyzeprimers/main.nf b/modules/nf-core/primerprospector/analyzeprimers/main.nf index 6d19e03512d..6b5e597798f 100644 --- a/modules/nf-core/primerprospector/analyzeprimers/main.nf +++ b/modules/nf-core/primerprospector/analyzeprimers/main.nf @@ -31,7 +31,7 @@ process PRIMERPROSPECTOR_ANALYZEPRIMERS { error "Provide a primers file in the input tuple, or specify a single primer with '-p/--primer_name' and '-s/--primer_sequence' in task.ext.args." } """ - # Primer Prospector 1.0.1 passes numpy floats to range() while plotting. + # Bug: Primer Prospector 1.0.1 passes numpy floats to range(), which requires integer arguments, while plotting. # This shim avoids a Python 2 TypeError that otherwise stops .ps output generation. cat <<'PY' > analyze_primers_compat.py import __builtin__ From 6df4059888fbd30c53218f8258e0c3e8e768dfe5 Mon Sep 17 00:00:00 2001 From: vagkaratzas Date: Tue, 12 May 2026 11:40:18 +0100 Subject: [PATCH 4/5] execfile on PATH for shim, instead of local user/ path --- .../nf-core/primerprospector/analyzeprimers/main.nf | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/modules/nf-core/primerprospector/analyzeprimers/main.nf b/modules/nf-core/primerprospector/analyzeprimers/main.nf index 6b5e597798f..b6e931f7cf9 100644 --- a/modules/nf-core/primerprospector/analyzeprimers/main.nf +++ b/modules/nf-core/primerprospector/analyzeprimers/main.nf @@ -35,11 +35,20 @@ process PRIMERPROSPECTOR_ANALYZEPRIMERS { # This shim avoids a Python 2 TypeError that otherwise stops .ps output generation. cat <<'PY' > analyze_primers_compat.py import __builtin__ + import os + import sys _range = __builtin__.range def _int_range(*args): return _range(*[int(arg) for arg in args]) __builtin__.range = _int_range - execfile('/usr/local/bin/analyze_primers.py') + for path_dir in os.environ.get('PATH', '').split(os.pathsep): + script_path = os.path.join(path_dir, 'analyze_primers.py') + if os.path.exists(script_path): + sys.argv[0] = script_path + execfile(script_path) + break + else: + raise RuntimeError('Could not find analyze_primers.py on PATH') PY python analyze_primers_compat.py \\ From 0a30c9e8869f95516d5b85a82bcce0c194243914 Mon Sep 17 00:00:00 2001 From: vagkaratzas Date: Tue, 12 May 2026 11:58:11 +0100 Subject: [PATCH 5/5] stricter version eval to pass GitHub runners warnings --- modules/nf-core/primerprospector/analyzeprimers/main.nf | 2 +- modules/nf-core/primerprospector/analyzeprimers/meta.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/nf-core/primerprospector/analyzeprimers/main.nf b/modules/nf-core/primerprospector/analyzeprimers/main.nf index b6e931f7cf9..59cf1a8dca2 100644 --- a/modules/nf-core/primerprospector/analyzeprimers/main.nf +++ b/modules/nf-core/primerprospector/analyzeprimers/main.nf @@ -13,7 +13,7 @@ process PRIMERPROSPECTOR_ANALYZEPRIMERS { output: tuple val(meta), path("${prefix}_hits.txt"), emit: hits tuple val(meta), path("${prefix}.ps") , emit: plots - tuple val("${task.process}"), val('primerprospector'), eval("analyze_primers.py --version 2>&1 | sed 's/.* //; s/-release//'"), topic: versions, emit: versions_primerprospector + tuple val("${task.process}"), val('primerprospector'), eval("analyze_primers.py --version 2>&1 | grep -Eo '[0-9]+(\\.[0-9]+)+' | tail -n 1"), topic: versions, emit: versions_primerprospector when: task.ext.when == null || task.ext.when diff --git a/modules/nf-core/primerprospector/analyzeprimers/meta.yml b/modules/nf-core/primerprospector/analyzeprimers/meta.yml index dd636bc5485..bb412eec114 100644 --- a/modules/nf-core/primerprospector/analyzeprimers/meta.yml +++ b/modules/nf-core/primerprospector/analyzeprimers/meta.yml @@ -69,7 +69,7 @@ output: - primerprospector: type: string description: The name of the tool - - analyze_primers.py --version 2>&1 | sed 's/.* //; s/-release//': + - analyze_primers.py --version 2>&1 | grep -Eo '[0-9]+(\.[0-9]+)+' | tail -n 1: type: eval description: The expression to obtain the version of the tool topics: @@ -80,7 +80,7 @@ topics: - primerprospector: type: string description: The name of the tool - - analyze_primers.py --version 2>&1 | sed 's/.* //; s/-release//': + - analyze_primers.py --version 2>&1 | grep -Eo '[0-9]+(\.[0-9]+)+' | tail -n 1: type: eval description: The expression to obtain the version of the tool authors: