diff --git a/modules/nf-core/custom/geneticmapconvert/environment.yml b/modules/nf-core/custom/geneticmapconvert/environment.yml index 2e5dc8f801d9..f32233e80763 100644 --- a/modules/nf-core/custom/geneticmapconvert/environment.yml +++ b/modules/nf-core/custom/geneticmapconvert/environment.yml @@ -6,4 +6,5 @@ channels: dependencies: - conda-forge::r-data.table=1.17.8 - conda-forge::r-janitor=2.2.1 + - conda-forge::r-nfcore.utils=0.0.3 - conda-forge::r-r.utils=2.13.0 diff --git a/modules/nf-core/custom/geneticmapconvert/main.nf b/modules/nf-core/custom/geneticmapconvert/main.nf index 4f9070bb0376..d34b6bad33ac 100644 --- a/modules/nf-core/custom/geneticmapconvert/main.nf +++ b/modules/nf-core/custom/geneticmapconvert/main.nf @@ -4,17 +4,18 @@ process CUSTOM_GENETICMAPCONVERT { 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/06/062aabd31ebac6f139125e485d5566e928c1b79caf488daa596df02bd1ccbf23/data': - 'community.wave.seqera.io/library/r-data.table_r-janitor_r-r.utils:c8ebef5bb002374e' }" + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/b3/b314d622eb495f740bcfa163ceda8f65cb9c77afa069f533d41c810c33eeecdb/data': + 'community.wave.seqera.io/library/r-data.table_r-janitor_r-nfcore.utils_r-r.utils:27d6c57bc9981f04' }" input: tuple val(meta), path(map_file) output: - tuple val(meta), path("${prefix}.glimpse.map"), emit: glimpse_map - tuple val(meta), path("${prefix}.plink.map") , emit: plink_map - tuple val(meta), path("${prefix}.stitch.map") , emit: stitch_map - tuple val(meta), path("${prefix}.minimac.map"), emit: minimac_map + tuple val(meta), path("${prefix}.glimpse.map") , emit: glimpse_map + tuple val(meta), path("${prefix}.plink.map") , emit: plink_map + tuple val(meta), path("${prefix}.stitch.map") , emit: stitch_map + tuple val(meta), path("${prefix}.minimac.map") , emit: minimac_map + tuple val(meta), path("${prefix}.R_sessionInfo.log"), emit: session_info path "versions.yml", emit: versions, topic: versions when: @@ -39,11 +40,11 @@ process CUSTOM_GENETICMAPCONVERT { touch ${prefix}.minimac.map touch ${prefix}.eagle.map - cat <<-END_VERSIONS > versions.yml - "${task.process}": - r-base: \$(R --version | sed '1!d; s/.*version //; s/ .*//') - r-data.table: \$(Rscript -e "cat(as.character(packageVersion('data.table')))") - r-janitor: \$(Rscript -e "cat(as.character(packageVersion('janitor')))") - END_VERSIONS + Rscript -e "nfcore.utils::process_end( + packages = list('r-data.table' = 'data.table', 'r-janitor' = 'janitor'), + task_name = '${task.process}', + versions_path = 'versions.yml', + log_path = '${prefix}.R_sessionInfo.log' + )" """ } diff --git a/modules/nf-core/custom/geneticmapconvert/meta.yml b/modules/nf-core/custom/geneticmapconvert/meta.yml index 0eee0c8775d8..18d4d791e4e4 100644 --- a/modules/nf-core/custom/geneticmapconvert/meta.yml +++ b/modules/nf-core/custom/geneticmapconvert/meta.yml @@ -52,6 +52,7 @@ output: tab-delimited file with header and columns: pos, chr, cM pattern: "*.glimpse.map" + ontologies: [] plink_map: - - meta: type: map @@ -65,6 +66,7 @@ output: space-delimited file without header and columns: chr, id, cM, pos pattern: "*.plink.map" + ontologies: [] stitch_map: - - meta: type: map @@ -78,6 +80,7 @@ output: space-delimited file with header and columns: pos, rate, cM pattern: "*.stitch.map" + ontologies: [] minimac_map: - - meta: type: map @@ -91,6 +94,7 @@ output: tab-delimited file with header and columns: chr, pos, cM pattern: "*.minimac.map" + ontologies: [] versions: - versions.yml: type: file @@ -98,6 +102,18 @@ output: pattern: "versions.yml" ontologies: - edam: http://edamontology.org/format_3750 # YAML + session_info: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - ${prefix}.R_sessionInfo.log: + type: file + description: dump of R SessionInfo + pattern: "*.log" + ontologies: + - edam: "http://edamontology.org/data_1678" # Log topics: versions: - versions.yml: diff --git a/modules/nf-core/custom/geneticmapconvert/templates/geneticmapconvert.R b/modules/nf-core/custom/geneticmapconvert/templates/geneticmapconvert.R index 096dfe4493b7..5f5bcd1f1b19 100644 --- a/modules/nf-core/custom/geneticmapconvert/templates/geneticmapconvert.R +++ b/modules/nf-core/custom/geneticmapconvert/templates/geneticmapconvert.R @@ -1,9 +1,15 @@ #!/usr/bin/env Rscript +# Needed to avoid the error from lubridate in janitor +# Error: (converted from warning) +# Your system is mis-configured: ‘/etc/localtime’ is not a symlink +Sys.setenv(TZ = "UTC") + # Load necessary libraries library(data.table) library(stringr) library(janitor) +library(nfcore.utils) ################################################ ################################################ @@ -11,59 +17,6 @@ library(janitor) ################################################ ################################################ -#' Check for Non-Empty, Non-Whitespace String -#' -#' This function checks if the input is non-NULL and contains more than -#' just whitespace. -#' It returns TRUE if the input is a non-empty, non-whitespace string, -#' and FALSE otherwise. -#' -#' @param input A variable to check. -#' @return A logical value: TRUE if the input is a valid, non-empty, -#' non-whitespace string; FALSE otherwise. -#' @examples -#' is_valid_string("Hello World") # Returns TRUE -#' is_valid_string(" ") # Returns FALSE -#' is_valid_string(NULL) # Returns FALSE -is_valid_string <- function(input) { - !is.null(input) && nzchar(trimws(input)) -} - -#' Parse out options from a string without recourse to optparse -#' -#' @param x Long-form argument list like --opt1 val1 --opt2 val2 -#' -#' @return named list of options and values similar to optparse -parse_args <- function(x) { - args_list <- unlist(strsplit(x, " ?--")[[1]])[-1] - args_vals <- lapply( - args_list, - function(x) scan(text = x, what = "character", quiet = TRUE) - ) - - # Ensure the option vectors are length 2 (key/ value) to catch empty ones - args_vals <- lapply(args_vals, function(z) { - length(z) <- 2 - z - }) - - parsed_args <- structure( - lapply(args_vals, function(x) x[2]), - names = lapply(args_vals, function(x) x[1]) - ) - parsed_args[! is.na(parsed_args)] -} - -#' Turn “null” or empty strings into actual NULL -#' -#' @param x Input option -#' -#' @return NULL or x -#' -nullify <- function(x) { - if (is.character(x) && (tolower(x) == "null" || x == "")) NULL else x -} - #' Parse tolerance value #' #' @param x Tolerance to check @@ -269,12 +222,6 @@ process_map_file <- function( ################################################ ################################################ -# I've defined these in a single array like this so that we could go back to an -# optparse-driven method in future with module bin/ directories, rather than -# the template - -# Set defaults and classes - opt <- list( output_prefix = "${prefix}", map_file = "${map_file}", @@ -282,52 +229,28 @@ opt <- list( tolerance = NULL ) -opt_types <- lapply(opt, class) - -# Apply parameter overrides -args_opt <- parse_args("${args}") -for (ao in names(args_opt)) { - if (! ao %in% names(opt)) { - stop(paste("Invalid option:", ao)) - } else { - # Preserve classes from defaults where possible - if (! is.null(opt[[ao]])) { - args_opt[[ao]] <- as(args_opt[[ao]], opt_types[[ao]]) - } - opt[[ao]] <- args_opt[[ao]] - } -} - -keys <- c("tolerance", "chr") -opt[keys] <- lapply(opt[keys], nullify) - -for (file_input in c("map_file")) { - if (! is_valid_string(opt[[file_input]])) { - stop(paste("Please provide", file_input), call. = FALSE) - } - - if (! file.exists(opt[[file_input]])) { - stop(paste0( - "Value of ", file_input, ": ", - opt[[file_input]], " is not a valid file" - )) - } -} +opt_valid <- process_inputs( + opt, + args = '${args}', + keys_to_nullify = c("output_prefix", "chr", "tolerance"), + expected_files = c("map_file"), + expected_double = c("tolerance"), + required_opts = c("map_file", "output_prefix") +) process_map_file( - file_path = opt[["map_file"]], - chr = opt[["chr"]], - prefix = opt[["output_prefix"]], - tolerance = parse_tolerance(opt[["tolerance"]]) + file_path = opt_valid[["map_file"]], + chr = opt_valid[["chr"]], + prefix = opt_valid[["output_prefix"]], + tolerance = parse_tolerance(opt_valid[["tolerance"]]) ) -version_rbase <- paste(R.version[["major"]], R.version[["minor"]], sep = ".") -version_datatable <- packageVersion("data.table") -version_janitor <- packageVersion("janitor") - -writeLines(c( - '"${task.process}":', - paste(" r-base:", version_rbase), - paste(" r-data.table:", version_datatable), - paste(" r-janitor:", version_janitor) -), "versions.yml") +process_end( + packages = list( + "r-data.table" = "data.table", + "r-janitor" = "janitor" + ), + task_name = "${task.process}", + versions_path = "versions.yml", + log_path = "${prefix}.R_sessionInfo.log" +) diff --git a/modules/nf-core/custom/geneticmapconvert/tests/main.nf.test b/modules/nf-core/custom/geneticmapconvert/tests/main.nf.test index c231b37d51b4..c9d1023c3b4a 100644 --- a/modules/nf-core/custom/geneticmapconvert/tests/main.nf.test +++ b/modules/nf-core/custom/geneticmapconvert/tests/main.nf.test @@ -31,7 +31,7 @@ nextflow_process { assertAll( { assert process.success }, { assert snapshot( - sanitizeOutput(process.out), + sanitizeOutput(process.out, unstableKeys:["session_info"]), path(process.out.versions[0]).yaml, path(process.out.glimpse_map[0][1]).readLines()[0..2], path(process.out.plink_map[0][1]).readLines()[0..2], @@ -76,7 +76,7 @@ nextflow_process { assertAll( { assert process.success }, { assert snapshot( - sanitizeOutput(process.out), + sanitizeOutput(process.out, unstableKeys:["session_info"]), path(process.out.versions[0]).yaml, path(process.out.glimpse_map[0][1]).readLines()[0..2], path(process.out.plink_map[0][1]).readLines()[0..2], @@ -106,7 +106,7 @@ nextflow_process { assertAll( { assert process.success }, { assert snapshot( - sanitizeOutput(process.out), + sanitizeOutput(process.out, unstableKeys:["session_info"]), path(process.out.versions[0]).yaml, path(process.out.glimpse_map[0][1]).readLines()[0..2], path(process.out.plink_map[0][1]).readLines()[0..2], @@ -136,7 +136,7 @@ nextflow_process { assertAll( { assert process.success }, { assert snapshot( - sanitizeOutput(process.out), + sanitizeOutput(process.out, unstableKeys:["session_info"]), path(process.out.versions[0]).yaml, path(process.out.glimpse_map[0][1]).readLines()[0..2], path(process.out.plink_map[0][1]).readLines()[0..2], @@ -235,7 +235,7 @@ nextflow_process { assertAll( { assert process.success }, { assert snapshot( - sanitizeOutput(process.out), + sanitizeOutput(process.out, unstableKeys:["session_info"]), path(process.out.versions[0]).yaml, path(process.out.glimpse_map[0][1]).readLines()[0..2], path(process.out.plink_map[0][1]).readLines()[0..2], @@ -392,7 +392,7 @@ nextflow_process { assertAll( { assert process.success }, { assert snapshot( - sanitizeOutput(process.out), + sanitizeOutput(process.out, unstableKeys:["session_info"]), path(process.out.versions[0]).yaml, path(process.out.glimpse_map[0][1]).readLines()[0..2], path(process.out.plink_map[0][1]).readLines()[0..2], @@ -450,7 +450,7 @@ nextflow_process { then { assert snapshot( - sanitizeOutput(process.out), + sanitizeOutput(process.out, unstableKeys:["session_info"]), path(process.out.versions[0]).yaml ).match() } diff --git a/modules/nf-core/custom/geneticmapconvert/tests/main.nf.test.snap b/modules/nf-core/custom/geneticmapconvert/tests/main.nf.test.snap index 2baf6f2f9564..fefabeb0a617 100644 --- a/modules/nf-core/custom/geneticmapconvert/tests/main.nf.test.snap +++ b/modules/nf-core/custom/geneticmapconvert/tests/main.nf.test.snap @@ -29,6 +29,15 @@ "test.plink.map:md5,21f0a2f5803c03c285b172a2cb8b9028" ] ], + "session_info": [ + [ + { + "chr": "chr21", + "id": "test" + }, + "test.R_sessionInfo.log" + ] + ], "stitch_map": [ [ { @@ -39,12 +48,13 @@ ] ], "versions": [ - "versions.yml:md5,9e24243bcd40c742e200d57da746a8b9" + "versions.yml:md5,ef388d742c1736752d23249043987aa8" ] }, { "CUSTOM_GENETICMAPCONVERT": { "r-base": "4.5.3", + "r-nfcore.utils": "0.0.3", "r-data.table": "1.17.8", "r-janitor": "2.2.1" } @@ -70,10 +80,10 @@ "chr21\t12970435\t0.00133" ] ], - "timestamp": "2026-04-21T17:17:09.741694079", + "timestamp": "2026-06-10T10:01:04.636233231", "meta": { "nf-test": "0.9.5", - "nextflow": "25.10.4" + "nextflow": "26.04.1" } }, "Convert map with chr\\tpos\\tcm - with header - meta.chr (minimac format)": { @@ -106,6 +116,15 @@ "test.plink.map:md5,21f0a2f5803c03c285b172a2cb8b9028" ] ], + "session_info": [ + [ + { + "chr": "chr21", + "id": "test" + }, + "test.R_sessionInfo.log" + ] + ], "stitch_map": [ [ { @@ -116,12 +135,13 @@ ] ], "versions": [ - "versions.yml:md5,9e24243bcd40c742e200d57da746a8b9" + "versions.yml:md5,ef388d742c1736752d23249043987aa8" ] }, { "CUSTOM_GENETICMAPCONVERT": { "r-base": "4.5.3", + "r-nfcore.utils": "0.0.3", "r-data.table": "1.17.8", "r-janitor": "2.2.1" } @@ -147,10 +167,10 @@ "chr21\t12970435\t0.00133" ] ], - "timestamp": "2026-04-21T17:17:40.527355799", + "timestamp": "2026-06-10T10:01:23.280555558", "meta": { "nf-test": "0.9.5", - "nextflow": "25.10.4" + "nextflow": "26.04.1" } }, "Convert map with pos rate cm - with header - meta.chr (stitch format)": { @@ -183,6 +203,15 @@ "test.plink.map:md5,feb793cc9b1f958a3cc5722cef2c3faa" ] ], + "session_info": [ + [ + { + "chr": "chr21", + "id": "test" + }, + "test.R_sessionInfo.log" + ] + ], "stitch_map": [ [ { @@ -193,12 +222,13 @@ ] ], "versions": [ - "versions.yml:md5,9e24243bcd40c742e200d57da746a8b9" + "versions.yml:md5,ef388d742c1736752d23249043987aa8" ] }, { "CUSTOM_GENETICMAPCONVERT": { "r-base": "4.5.3", + "r-nfcore.utils": "0.0.3", "r-data.table": "1.17.8", "r-janitor": "2.2.1" } @@ -224,10 +254,10 @@ "chr21\t9928594\t0.0418492" ] ], - "timestamp": "2026-04-21T16:56:42.982751661", + "timestamp": "2026-06-10T10:01:32.079941731", "meta": { "nf-test": "0.9.5", - "nextflow": "25.10.4" + "nextflow": "26.04.1" } }, "Convert map with chr id cm pos - no header - meta.chr (plink format)": { @@ -260,6 +290,15 @@ "test.plink.map:md5,57966816771960670e5149fa5c3cec60" ] ], + "session_info": [ + [ + { + "chr": "chr21", + "id": "test" + }, + "test.R_sessionInfo.log" + ] + ], "stitch_map": [ [ { @@ -270,12 +309,13 @@ ] ], "versions": [ - "versions.yml:md5,9e24243bcd40c742e200d57da746a8b9" + "versions.yml:md5,ef388d742c1736752d23249043987aa8" ] }, { "CUSTOM_GENETICMAPCONVERT": { "r-base": "4.5.3", + "r-nfcore.utils": "0.0.3", "r-data.table": "1.17.8", "r-janitor": "2.2.1" } @@ -301,10 +341,10 @@ "chr21\t12968320\t0" ] ], - "timestamp": "2026-04-21T17:18:47.986410489", + "timestamp": "2026-06-10T10:02:06.34686166", "meta": { "nf-test": "0.9.5", - "nextflow": "25.10.4" + "nextflow": "26.04.1" } }, "Test with comma separator and keep id": { @@ -334,6 +374,14 @@ "test.plink.map:md5,f181dc5717646db4298c174c3642c7f7" ] ], + "session_info": [ + [ + { + "id": "test" + }, + "test.R_sessionInfo.log" + ] + ], "stitch_map": [ [ { @@ -343,12 +391,13 @@ ] ], "versions": [ - "versions.yml:md5,9e24243bcd40c742e200d57da746a8b9" + "versions.yml:md5,ef388d742c1736752d23249043987aa8" ] }, { "CUSTOM_GENETICMAPCONVERT": { "r-base": "4.5.3", + "r-nfcore.utils": "0.0.3", "r-data.table": "1.17.8", "r-janitor": "2.2.1" } @@ -374,10 +423,10 @@ "chr21\t12970435\t0.00133" ] ], - "timestamp": "2026-04-21T17:19:48.646532306", + "timestamp": "2026-06-10T10:02:43.983464614", "meta": { "nf-test": "0.9.5", - "nextflow": "25.10.4" + "nextflow": "26.04.1" } }, "Convert map with pos\\tchr\\tcM - with header - meta.chr (glimpse compressed format)": { @@ -410,6 +459,15 @@ "test.plink.map:md5,21f0a2f5803c03c285b172a2cb8b9028" ] ], + "session_info": [ + [ + { + "chr": "chr21", + "id": "test" + }, + "test.R_sessionInfo.log" + ] + ], "stitch_map": [ [ { @@ -420,12 +478,13 @@ ] ], "versions": [ - "versions.yml:md5,9e24243bcd40c742e200d57da746a8b9" + "versions.yml:md5,ef388d742c1736752d23249043987aa8" ] }, { "CUSTOM_GENETICMAPCONVERT": { "r-base": "4.5.3", + "r-nfcore.utils": "0.0.3", "r-data.table": "1.17.8", "r-janitor": "2.2.1" } @@ -451,10 +510,10 @@ "chr21\t12970435\t0.00133" ] ], - "timestamp": "2026-04-21T17:17:25.458379776", + "timestamp": "2026-06-10T10:01:14.625561006", "meta": { "nf-test": "0.9.5", - "nextflow": "25.10.4" + "nextflow": "26.04.1" } }, "Test stub": { @@ -487,6 +546,15 @@ "test.plink.map:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], + "session_info": [ + [ + { + "chr": "chr21", + "id": "test" + }, + "test.R_sessionInfo.log" + ] + ], "stitch_map": [ [ { @@ -497,21 +565,22 @@ ] ], "versions": [ - "versions.yml:md5,9e24243bcd40c742e200d57da746a8b9" + "versions.yml:md5,ef388d742c1736752d23249043987aa8" ] }, { "CUSTOM_GENETICMAPCONVERT": { "r-base": "4.5.3", + "r-nfcore.utils": "0.0.3", "r-data.table": "1.17.8", "r-janitor": "2.2.1" } } ], - "timestamp": "2026-04-21T17:20:09.156535388", + "timestamp": "2026-06-10T11:13:49.938258653", "meta": { "nf-test": "0.9.5", - "nextflow": "25.10.4" + "nextflow": "26.04.1" } } } \ No newline at end of file