Mercurial > repos > galaxytrakr > hfp_bettercallsal_awsbatch
comparison 1.0.0/lib/routines.nf @ 0:801b85b03a17 draft default tip
planemo upload
| author | galaxytrakr |
|---|---|
| date | Thu, 28 May 2026 20:31:42 +0000 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:801b85b03a17 |
|---|---|
| 1 // Hold methods to print: | |
| 2 // 1. Colored logo. | |
| 3 // 2. Summary of parameters. | |
| 4 // 3. Single dashed line. | |
| 5 // 4. Double dashed line. | |
| 6 // | |
| 7 | |
| 8 import groovy.json.JsonSlurper | |
| 9 import groovy.util.ConfigSlurper | |
| 10 // import nextflow.config.ConfigParser | |
| 11 // import nextflow.config.ConfigBuilder | |
| 12 // import groovy.json.JsonOutput | |
| 13 | |
| 14 // ASCII logo | |
| 15 def pipelineBanner() { | |
| 16 | |
| 17 def padding = (params.pad) ?: 30 | |
| 18 Map fgcolors = getANSIColors() | |
| 19 | |
| 20 def banner = [ | |
| 21 name: "${fgcolors.magenta}${workflow.manifest.name}${fgcolors.reset}", | |
| 22 author: "${fgcolors.cyan}${workflow.manifest.author}${fgcolors.reset}", | |
| 23 // workflow: "${fgcolors.magenta}${params.pipeline}${fgcolors.reset}", | |
| 24 version: "${fgcolors.green}${workflow.manifest.version}${fgcolors.reset}", | |
| 25 center: "${fgcolors.green}${params.center}${fgcolors.reset}", | |
| 26 pad: padding | |
| 27 ] | |
| 28 | |
| 29 manifest = addPadding(banner) | |
| 30 | |
| 31 return """${fgcolors.white}${dashedLine(type: '=')}${fgcolors.magenta} | |
| 32 (o) | |
| 33 ___ _ __ _ _ __ ___ ___ | |
| 34 / __|| '_ \\ | || '_ \\ / _ \\/ __| | |
| 35 | (__ | |_) || || |_) || __/\\__ \\ | |
| 36 \\___|| .__/ |_|| .__/ \\___||___/ | |
| 37 | | | | | |
| 38 |_| |_|${fgcolors.reset} | |
| 39 ${dashedLine()} | |
| 40 ${fgcolors.blue}A collection of modular pipelines at CFSAN, FDA.${fgcolors.reset} | |
| 41 ${dashedLine()} | |
| 42 ${manifest} | |
| 43 ${dashedLine(type: '=')} | |
| 44 """.stripIndent() | |
| 45 } | |
| 46 | |
| 47 // Add padding to keys so that | |
| 48 // they indent nicely on the | |
| 49 // terminal | |
| 50 def addPadding(values) { | |
| 51 | |
| 52 def pad = (params.pad) ?: 30 | |
| 53 values.pad = pad | |
| 54 | |
| 55 def padding = values.pad.toInteger() | |
| 56 def nocapitalize = values.nocapitalize | |
| 57 def stopnow = values.stopNow | |
| 58 def help = values.help | |
| 59 | |
| 60 values.removeAll { | |
| 61 k, v -> [ | |
| 62 'nocapitalize', | |
| 63 'pad', | |
| 64 'stopNow', | |
| 65 'help' | |
| 66 ].contains(k) | |
| 67 } | |
| 68 | |
| 69 values.keySet().each { k -> | |
| 70 v = values[k] | |
| 71 s = params.linewidth - (pad + 5) | |
| 72 if (v.toString().size() > s && !stopnow) { | |
| 73 def sen = '' | |
| 74 // v.toString().findAll(/.{1,${s}}\b(?:\W*|\s*)/).each { | |
| 75 // sen += ' '.multiply(padding + 2) + it + '\n' | |
| 76 // } | |
| 77 v.toString().eachMatch(/.{1,${s}}(?=.*)\b|\w+/) { | |
| 78 sen += ' '.multiply(padding + 2) + it.trim() + '\n' | |
| 79 } | |
| 80 values[k] = ( | |
| 81 help ? sen.replaceAll(/^(\n|\s)*/, '') : sen.trim() | |
| 82 ) | |
| 83 } else { | |
| 84 values[k] = (help ? v + "\n" : v) | |
| 85 } | |
| 86 k = k.replaceAll(/\./, '_') | |
| 87 } | |
| 88 | |
| 89 return values.findResults { | |
| 90 k, v -> nocapitalize ? | |
| 91 k.padRight(padding) + ': ' + v : | |
| 92 k.capitalize().padRight(padding) + ': ' + v | |
| 93 }.join("\n") | |
| 94 } | |
| 95 | |
| 96 // Method for error messages | |
| 97 def stopNow(msg) { | |
| 98 | |
| 99 Map fgcolors = getANSIColors() | |
| 100 Map errors = [:] | |
| 101 | |
| 102 if (msg == null) { | |
| 103 msg = "Unknown error" | |
| 104 } | |
| 105 | |
| 106 errors['stopNow'] = true | |
| 107 errors["${params.cfsanpipename} - ${params.pipeline} - ERROR"] = """ | |
| 108 ${fgcolors.reset}${dashedLine()} | |
| 109 ${fgcolors.red}${msg}${fgcolors.reset} | |
| 110 ${dashedLine()} | |
| 111 """.stripIndent() | |
| 112 // println dashedLine() // defaults to stdout | |
| 113 // log.info addPadding(errors) // prints to stdout | |
| 114 exit 1, "\n" + dashedLine() + | |
| 115 "${fgcolors.red}\n" + addPadding(errors) | |
| 116 } | |
| 117 | |
| 118 // Method to validate 4 required parameters | |
| 119 // if input for entry point is FASTQ files | |
| 120 def validateParamsForFASTQ() { | |
| 121 switch (params) { | |
| 122 case { params.metadata == null && params.input == null }: | |
| 123 stopNow("Either metadata CSV file with 5 required columns\n" + | |
| 124 "in order: sample, fq1, fq2, strandedness, single_end or \n" + | |
| 125 "input directory of only FASTQ files (gzipped or unzipped) should be provided\n" + | |
| 126 "using --metadata or --input options.\n" + | |
| 127 "None of these two options were provided!") | |
| 128 break | |
| 129 case { params.metadata != null && params.input != null }: | |
| 130 stopNow("Either metadata or input directory of FASTQ files\n" + | |
| 131 "should be provided using --metadata or --input options.\n" + | |
| 132 "Using both these options is not allowed!") | |
| 133 break | |
| 134 case { params.output == null }: | |
| 135 stopNow("Please mention output directory to store all results " + | |
| 136 "using --output option!") | |
| 137 break | |
| 138 } | |
| 139 return 1 | |
| 140 } | |
| 141 | |
| 142 // Method to print summary of parameters | |
| 143 // before running | |
| 144 def summaryOfParams() { | |
| 145 | |
| 146 // def pipeline_specific_config = pipeline_specific_config = new ConfigParser().setIgnoreIncludes(true).parse( | |
| 147 // file("${params.workflowsconf}${params.fs}${params.pipeline}.config").text | |
| 148 // ) | |
| 149 def pipeline_specific_config = new ConfigSlurper().parse( | |
| 150 file("${params.workflowsconf}${params.fs}${params.pipeline}.config").text | |
| 151 ) | |
| 152 | |
| 153 Map fgcolors = getANSIColors() | |
| 154 Map globalparams = [:] | |
| 155 Map localparams = params.subMap( | |
| 156 pipeline_specific_config.params.keySet().toList() + params.logtheseparams | |
| 157 ) | |
| 158 | |
| 159 if (localparams !instanceof Map) { | |
| 160 stopNow("Need a Map of paramters. We got: " + localparams.getClass()) | |
| 161 } | |
| 162 | |
| 163 if (localparams.size() != 0) { | |
| 164 localparams['nocapitalize'] = true | |
| 165 globalparams['nocapitalize'] = true | |
| 166 globalparams['nextflow_version'] = "${nextflow.version}" | |
| 167 globalparams['nextflow_build'] = "${nextflow.build}" | |
| 168 globalparams['nextflow_timestamp'] = "${nextflow.timestamp}" | |
| 169 globalparams['workflow_projectDir'] = "${workflow.projectDir}" | |
| 170 globalparams['workflow_launchDir'] = "${workflow.launchDir}" | |
| 171 globalparams['workflow_workDir'] = "${workflow.workDir}" | |
| 172 globalparams['workflow_container'] = "${workflow.container}" | |
| 173 globalparams['workflow_containerEngine'] = "${workflow.containerEngine}" | |
| 174 globalparams['workflow_runName'] = "${workflow.runName}" | |
| 175 globalparams['workflow_sessionId'] = "${workflow.sessionId}" | |
| 176 globalparams['workflow_profile'] = "${workflow.profile}" | |
| 177 globalparams['workflow_start'] = "${workflow.start}" | |
| 178 globalparams['workflow_commandLine'] = "${workflow.commandLine}" | |
| 179 return """${dashedLine()} | |
| 180 Summary of the current workflow (${fgcolors.magenta}${params.pipeline}${fgcolors.reset}) parameters | |
| 181 ${dashedLine()} | |
| 182 ${addPadding(localparams)} | |
| 183 ${dashedLine()} | |
| 184 ${fgcolors.cyan}N E X T F L O W${fgcolors.reset} - ${fgcolors.magenta}${params.cfsanpipename}${fgcolors.reset} - Runtime metadata | |
| 185 ${dashedLine()} | |
| 186 ${addPadding(globalparams)} | |
| 187 ${dashedLine()}""".stripIndent() | |
| 188 } | |
| 189 return 1 | |
| 190 } | |
| 191 | |
| 192 // Method to display | |
| 193 // Return dashed line either '-' | |
| 194 // type or '=' type | |
| 195 def dashedLine(Map defaults = [:]) { | |
| 196 | |
| 197 Map fgcolors = getANSIColors() | |
| 198 def line = [color: 'white', type: '-'] | |
| 199 | |
| 200 if (!defaults.isEmpty()) { | |
| 201 line.putAll(defaults) | |
| 202 } | |
| 203 | |
| 204 return fgcolors."${line.color}" + | |
| 205 "${line.type}".multiply(params.linewidth) + | |
| 206 fgcolors.reset | |
| 207 } | |
| 208 | |
| 209 // Return slurped keys parsed from JSON | |
| 210 def slurpJson(file) { | |
| 211 def slurped = null | |
| 212 def jsonInst = new JsonSlurper() | |
| 213 | |
| 214 try { | |
| 215 slurped = jsonInst.parse(new File ("${file}")) | |
| 216 } | |
| 217 catch (Exception e) { | |
| 218 log.error 'Please check your JSON schema. Invalid JSON file: ' + file | |
| 219 } | |
| 220 | |
| 221 // Declare globals for the nanofactory | |
| 222 // workflow. | |
| 223 return [keys: slurped.keySet().toList(), cparams: slurped] | |
| 224 } | |
| 225 | |
| 226 // Default help text in a map if the entry point | |
| 227 // to a pipeline is FASTQ files. | |
| 228 def fastqEntryPointHelp() { | |
| 229 | |
| 230 Map helptext = [:] | |
| 231 Map fgcolors = getANSIColors() | |
| 232 | |
| 233 helptext['Workflow'] = "${fgcolors.magenta}${params.pipeline}${fgcolors.reset}" | |
| 234 helptext['Author'] = "${fgcolors.cyan}${params.workflow_built_by}${fgcolors.reset}" | |
| 235 helptext['Version'] = "${fgcolors.green}${params.workflow_version}${fgcolors.reset}\n" | |
| 236 helptext['Usage'] = "cpipes --pipeline ${params.pipeline} [options]\n" | |
| 237 helptext['Required'] = "" | |
| 238 helptext['--input'] = "Absolute path to directory containing FASTQ files. " + | |
| 239 "The directory should contain only FASTQ files as all the " + | |
| 240 "files within the mentioned directory will be read. " + | |
| 241 "Ex: --input /path/to/fastq_pass" | |
| 242 helptext['--output'] = "Absolute path to directory where all the pipeline " + | |
| 243 "outputs should be stored. Ex: --output /path/to/output" | |
| 244 helptext['Other options'] = "" | |
| 245 helptext['--metadata'] = "Absolute path to metadata CSV file containing five " + | |
| 246 "mandatory columns: sample,fq1,fq2,strandedness,single_end. The fq1 and fq2 " + | |
| 247 "columns contain absolute paths to the FASTQ files. This option can be used in place " + | |
| 248 "of --input option. This is rare. Ex: --metadata samplesheet.csv" | |
| 249 helptext['--fq_suffix'] = "The suffix of FASTQ files (Unpaired reads or R1 reads or Long reads) if " + | |
| 250 "an input directory is mentioned via --input option. Default: ${params.fq_suffix}" | |
| 251 helptext['--fq2_suffix'] = "The suffix of FASTQ files (Paired-end reads or R2 reads) if an input directory is mentioned via " + | |
| 252 "--input option. Default: ${params.fq2_suffix}" | |
| 253 helptext['--fq_filter_by_len'] = "Remove FASTQ reads that are less than this many bases. " + | |
| 254 "Default: ${params.fq_filter_by_len}" | |
| 255 helptext['--fq_strandedness'] = "The strandedness of the sequencing run. This is mostly needed " + | |
| 256 "if your sequencing run is RNA-SEQ. For most of the other runs, it is probably safe to use " + | |
| 257 "unstranded for the option. Default: ${params.fq_strandedness}" | |
| 258 helptext['--fq_single_end'] = "SINGLE-END information will be auto-detected but this option forces " + | |
| 259 "PAIRED-END FASTQ files to be treated as SINGLE-END so only read 1 information is included in " + | |
| 260 "auto-generated samplesheet. Default: ${params.fq_single_end}" | |
| 261 helptext['--fq_filename_delim'] = "Delimiter by which the file name is split to obtain sample name. " + | |
| 262 "Default: ${params.fq_filename_delim}" | |
| 263 helptext['--fq_filename_delim_idx'] = "After splitting FASTQ file name by using the --fq_filename_delim option," + | |
| 264 " all elements before this index (1-based) will be joined to create final sample name." + | |
| 265 " Default: ${params.fq_filename_delim_idx}" | |
| 266 | |
| 267 return helptext | |
| 268 } | |
| 269 | |
| 270 // Show concise help text if configured within the main workflow. | |
| 271 def conciseHelp(def tool = null) { | |
| 272 Map fgcolors = getANSIColors() | |
| 273 | |
| 274 tool ?= "fastp" | |
| 275 tools = tool?.tokenize(',') | |
| 276 | |
| 277 return """ | |
| 278 ${dashedLine()} | |
| 279 Show configurable CLI options for each tool within ${fgcolors.magenta}${params.pipeline}${fgcolors.reset} | |
| 280 ${dashedLine()} | |
| 281 Ex: cpipes --pipeline ${params.pipeline} --help | |
| 282 """ + (tools.size() > 1 ? "Ex: cpipes --pipeline ${params.pipeline} --help ${tools[0]}" | |
| 283 + """ | |
| 284 Ex: cpipes --pipeline ${params.pipeline} --help ${tools[0]},${tools[1]} | |
| 285 ${dashedLine()}""".stripIndent() : """Ex: cpipes --pipeline ${params.pipeline} --help ${tool} | |
| 286 ${dashedLine()}""".stripIndent()) | |
| 287 | |
| 288 } | |
| 289 | |
| 290 // Wrap help text with the following options | |
| 291 def wrapUpHelp() { | |
| 292 | |
| 293 return [ | |
| 294 'Help options' : "", | |
| 295 '--help': "Display this message.\n", | |
| 296 'help': true, | |
| 297 'nocapitalize': true | |
| 298 ] | |
| 299 } | |
| 300 | |
| 301 // Method to send email on workflow complete. | |
| 302 def sendMail() { | |
| 303 | |
| 304 if (params.user_email == null) { | |
| 305 return 1 | |
| 306 } | |
| 307 | |
| 308 def pad = (params.pad) ?: 30 | |
| 309 def contact_emails = [ | |
| 310 stakeholder: (params.workflow_blueprint_by ?: 'Not defined'), | |
| 311 author: (params.workflow_built_by ?: 'Not defined') | |
| 312 ] | |
| 313 def msg = """ | |
| 314 ${pipelineBanner()} | |
| 315 ${summaryOfParams()} | |
| 316 ${params.cfsanpipename} - ${params.pipeline} | |
| 317 ${dashedLine()} | |
| 318 Please check the following directory for N E X T F L O W | |
| 319 reports. You can view the HTML files directly by double clicking | |
| 320 them on your workstation. | |
| 321 ${dashedLine()} | |
| 322 ${params.tracereportsdir} | |
| 323 ${dashedLine()} | |
| 324 Please send any bug reports to CFSAN Dev Team or the author or | |
| 325 the stakeholder of the current pipeline. | |
| 326 ${dashedLine()} | |
| 327 Error messages (if any) | |
| 328 ${dashedLine()} | |
| 329 ${workflow.errorMessage} | |
| 330 ${workflow.errorReport} | |
| 331 ${dashedLine()} | |
| 332 Contact emails | |
| 333 ${dashedLine()} | |
| 334 ${addPadding(contact_emails)} | |
| 335 ${dashedLine()} | |
| 336 Thank you for using ${params.cfsanpipename} - ${params.pipeline}! | |
| 337 ${dashedLine()} | |
| 338 """.stripIndent() | |
| 339 | |
| 340 def mail_cmd = [ | |
| 341 'sendmail', | |
| 342 '-f', 'noreply@gmail.com', | |
| 343 '-F', 'noreply', | |
| 344 '-t', "${params.user_email}" | |
| 345 ] | |
| 346 | |
| 347 def email_subject = "${params.cfsanpipename} - ${params.pipeline}" | |
| 348 Map fgcolors = getANSIColors() | |
| 349 | |
| 350 if (workflow.success) { | |
| 351 email_subject += ' completed successfully!' | |
| 352 } | |
| 353 else if (!workflow.success) { | |
| 354 email_subject += ' has failed!' | |
| 355 } | |
| 356 | |
| 357 try { | |
| 358 ['env', 'bash'].execute() << """${mail_cmd.join(' ')} | |
| 359 Subject: ${email_subject} | |
| 360 Mime-Version: 1.0 | |
| 361 Content-Type: text/html | |
| 362 <pre> | |
| 363 ${msg.replaceAll(/\x1b\[[0-9;]*m/, '')} | |
| 364 </pre> | |
| 365 """.stripIndent() | |
| 366 } catch (all) { | |
| 367 def warning_msg = "${fgcolors.yellow}${params.cfsanpipename} - ${params.pipeline} - WARNING" | |
| 368 .padRight(pad) + ':' | |
| 369 log.info """ | |
| 370 ${dashedLine()} | |
| 371 ${warning_msg} | |
| 372 ${dashedLine()} | |
| 373 Could not send mail with the sendmail command! | |
| 374 ${dashedLine()} | |
| 375 """.stripIndent() | |
| 376 } | |
| 377 return 1 | |
| 378 } | |
| 379 | |
| 380 // Set ANSI colors for any and all | |
| 381 // STDOUT or STDERR | |
| 382 def getANSIColors() { | |
| 383 | |
| 384 Map fgcolors = [:] | |
| 385 | |
| 386 fgcolors['reset'] = "\033[0m" | |
| 387 fgcolors['black'] = "\033[0;30m" | |
| 388 fgcolors['red'] = "\033[0;31m" | |
| 389 fgcolors['green'] = "\033[0;32m" | |
| 390 fgcolors['yellow'] = "\033[0;33m" | |
| 391 fgcolors['blue'] = "\033[0;34m" | |
| 392 fgcolors['magenta'] = "\033[0;35m" | |
| 393 fgcolors['cyan'] = "\033[0;36m" | |
| 394 fgcolors['white'] = "\033[0;37m" | |
| 395 | |
| 396 return fgcolors | |
| 397 } |
