kkonganti@17: // Hold methods to print: kkonganti@17: // 1. Colored logo. kkonganti@17: // 2. Summary of parameters. kkonganti@17: // 3. Single dashed line. kkonganti@17: // 4. Double dashed line. kkonganti@17: // kkonganti@17: kkonganti@17: import groovy.json.JsonSlurper kkonganti@17: import nextflow.config.ConfigParser kkonganti@17: // import groovy.json.JsonOutput kkonganti@17: kkonganti@17: // ASCII logo kkonganti@17: def pipelineBanner() { kkonganti@17: kkonganti@17: def padding = (params.pad) ?: 30 kkonganti@17: Map fgcolors = getANSIColors() kkonganti@17: kkonganti@17: def banner = [ kkonganti@17: name: "${fgcolors.magenta}${workflow.manifest.name}${fgcolors.reset}", kkonganti@17: author: "${fgcolors.cyan}${workflow.manifest.author}${fgcolors.reset}", kkonganti@17: // workflow: "${fgcolors.magenta}${params.pipeline}${fgcolors.reset}", kkonganti@17: version: "${fgcolors.green}${workflow.manifest.version}${fgcolors.reset}", kkonganti@17: center: "${fgcolors.green}${params.center}${fgcolors.reset}", kkonganti@17: pad: padding kkonganti@17: ] kkonganti@17: kkonganti@17: manifest = addPadding(banner) kkonganti@17: kkonganti@17: return """${fgcolors.white}${dashedLine(type: '=')}${fgcolors.magenta} kkonganti@17: (o) kkonganti@17: ___ _ __ _ _ __ ___ ___ kkonganti@17: / __|| '_ \\ | || '_ \\ / _ \\/ __| kkonganti@17: | (__ | |_) || || |_) || __/\\__ \\ kkonganti@17: \\___|| .__/ |_|| .__/ \\___||___/ kkonganti@17: | | | | kkonganti@17: |_| |_|${fgcolors.reset} kkonganti@17: ${dashedLine()} kkonganti@17: ${fgcolors.blue}A collection of modular pipelines at CFSAN, FDA.${fgcolors.reset} kkonganti@17: ${dashedLine()} kkonganti@17: ${manifest} kkonganti@17: ${dashedLine(type: '=')} kkonganti@17: """.stripIndent() kkonganti@17: } kkonganti@17: kkonganti@17: // Add padding to keys so that kkonganti@17: // they indent nicely on the kkonganti@17: // terminal kkonganti@17: def addPadding(values) { kkonganti@17: kkonganti@17: def pad = (params.pad) ?: 30 kkonganti@17: values.pad = pad kkonganti@17: kkonganti@17: def padding = values.pad.toInteger() kkonganti@17: def nocapitalize = values.nocapitalize kkonganti@17: def stopnow = values.stopNow kkonganti@17: def help = values.help kkonganti@17: kkonganti@17: values.removeAll { kkonganti@17: k, v -> [ kkonganti@17: 'nocapitalize', kkonganti@17: 'pad', kkonganti@17: 'stopNow', kkonganti@17: 'help' kkonganti@17: ].contains(k) kkonganti@17: } kkonganti@17: kkonganti@17: values.keySet().each { k -> kkonganti@17: v = values[k] kkonganti@17: s = params.linewidth - (pad + 5) kkonganti@17: if (v.toString().size() > s && !stopnow) { kkonganti@17: def sen = '' kkonganti@17: // v.toString().findAll(/.{1,${s}}\b(?:\W*|\s*)/).each { kkonganti@17: // sen += ' '.multiply(padding + 2) + it + '\n' kkonganti@17: // } kkonganti@17: v.toString().eachMatch(/.{1,${s}}(?=.*)\b|\w+/) { kkonganti@17: sen += ' '.multiply(padding + 2) + it.trim() + '\n' kkonganti@17: } kkonganti@17: values[k] = ( kkonganti@17: help ? sen.replaceAll(/^(\n|\s)*/, '') : sen.trim() kkonganti@17: ) kkonganti@17: } else { kkonganti@17: values[k] = (help ? v + "\n" : v) kkonganti@17: } kkonganti@17: k = k.replaceAll(/\./, '_') kkonganti@17: } kkonganti@17: kkonganti@17: return values.findResults { kkonganti@17: k, v -> nocapitalize ? kkonganti@17: k.padRight(padding) + ': ' + v : kkonganti@17: k.capitalize().padRight(padding) + ': ' + v kkonganti@17: }.join("\n") kkonganti@17: } kkonganti@17: kkonganti@17: // Method for error messages kkonganti@17: def stopNow(msg) { kkonganti@17: kkonganti@17: Map fgcolors = getANSIColors() kkonganti@17: Map errors = [:] kkonganti@17: kkonganti@17: if (msg == null) { kkonganti@17: msg = "Unknown error" kkonganti@17: } kkonganti@17: kkonganti@17: errors['stopNow'] = true kkonganti@17: errors["${params.cfsanpipename} - ${params.pipeline} - ERROR"] = """ kkonganti@17: ${fgcolors.reset}${dashedLine()} kkonganti@17: ${fgcolors.red}${msg}${fgcolors.reset} kkonganti@17: ${dashedLine()} kkonganti@17: """.stripIndent() kkonganti@17: // println dashedLine() // defaults to stdout kkonganti@17: // log.info addPadding(errors) // prints to stdout kkonganti@17: exit 1, "\n" + dashedLine() + kkonganti@17: "${fgcolors.red}\n" + addPadding(errors) kkonganti@17: } kkonganti@17: kkonganti@17: // Method to validate 4 required parameters kkonganti@17: // if input for entry point is FASTQ files kkonganti@17: def validateParamsForFASTQ() { kkonganti@17: switch (params) { kkonganti@17: case { params.metadata == null && params.input == null }: kkonganti@17: stopNow("Either metadata CSV file with 5 required columns\n" + kkonganti@17: "in order: sample, fq1, fq2, strandedness, single_end or \n" + kkonganti@17: "input directory of only FASTQ files (gzipped or unzipped) should be provided\n" + kkonganti@17: "using --metadata or --input options.\n" + kkonganti@17: "None of these two options were provided!") kkonganti@17: break kkonganti@17: case { params.metadata != null && params.input != null }: kkonganti@17: stopNow("Either metadata or input directory of FASTQ files\n" + kkonganti@17: "should be provided using --metadata or --input options.\n" + kkonganti@17: "Using both these options is not allowed!") kkonganti@17: break kkonganti@17: case { params.output == null }: kkonganti@17: stopNow("Please mention output directory to store all results " + kkonganti@17: "using --output option!") kkonganti@17: break kkonganti@17: } kkonganti@17: return 1 kkonganti@17: } kkonganti@17: kkonganti@17: // Method to print summary of parameters kkonganti@17: // before running kkonganti@17: def summaryOfParams() { kkonganti@17: kkonganti@17: def pipeline_specific_config = new ConfigParser().setIgnoreIncludes(true).parse( kkonganti@17: file("${params.workflowsconf}${params.fs}${params.pipeline}.config").text kkonganti@17: ) kkonganti@17: Map fgcolors = getANSIColors() kkonganti@17: Map globalparams = [:] kkonganti@17: Map localparams = params.subMap( kkonganti@17: pipeline_specific_config.params.keySet().toList() + params.logtheseparams kkonganti@17: ) kkonganti@17: kkonganti@17: if (localparams !instanceof Map) { kkonganti@17: stopNow("Need a Map of paramters. We got: " + localparams.getClass()) kkonganti@17: } kkonganti@17: kkonganti@17: if (localparams.size() != 0) { kkonganti@17: localparams['nocapitalize'] = true kkonganti@17: globalparams['nocapitalize'] = true kkonganti@17: globalparams['nextflow_version'] = "${nextflow.version}" kkonganti@17: globalparams['nextflow_build'] = "${nextflow.build}" kkonganti@17: globalparams['nextflow_timestamp'] = "${nextflow.timestamp}" kkonganti@17: globalparams['workflow_projectDir'] = "${workflow.projectDir}" kkonganti@17: globalparams['workflow_launchDir'] = "${workflow.launchDir}" kkonganti@17: globalparams['workflow_workDir'] = "${workflow.workDir}" kkonganti@17: globalparams['workflow_container'] = "${workflow.container}" kkonganti@17: globalparams['workflow_containerEngine'] = "${workflow.containerEngine}" kkonganti@17: globalparams['workflow_runName'] = "${workflow.runName}" kkonganti@17: globalparams['workflow_sessionId'] = "${workflow.sessionId}" kkonganti@17: globalparams['workflow_profile'] = "${workflow.profile}" kkonganti@17: globalparams['workflow_start'] = "${workflow.start}" kkonganti@17: globalparams['workflow_commandLine'] = "${workflow.commandLine}" kkonganti@17: return """${dashedLine()} kkonganti@17: Summary of the current workflow (${fgcolors.magenta}${params.pipeline}${fgcolors.reset}) parameters kkonganti@17: ${dashedLine()} kkonganti@17: ${addPadding(localparams)} kkonganti@17: ${dashedLine()} kkonganti@17: ${fgcolors.cyan}N E X T F L O W${fgcolors.reset} - ${fgcolors.magenta}${params.cfsanpipename}${fgcolors.reset} - Runtime metadata kkonganti@17: ${dashedLine()} kkonganti@17: ${addPadding(globalparams)} kkonganti@17: ${dashedLine()}""".stripIndent() kkonganti@17: } kkonganti@17: return 1 kkonganti@17: } kkonganti@17: kkonganti@17: // Method to display kkonganti@17: // Return dashed line either '-' kkonganti@17: // type or '=' type kkonganti@17: def dashedLine(Map defaults = [:]) { kkonganti@17: kkonganti@17: Map fgcolors = getANSIColors() kkonganti@17: def line = [color: 'white', type: '-'] kkonganti@17: kkonganti@17: if (!defaults.isEmpty()) { kkonganti@17: line.putAll(defaults) kkonganti@17: } kkonganti@17: kkonganti@17: return fgcolors."${line.color}" + kkonganti@17: "${line.type}".multiply(params.linewidth) + kkonganti@17: fgcolors.reset kkonganti@17: } kkonganti@17: kkonganti@17: // Return slurped keys parsed from JSON kkonganti@17: def slurpJson(file) { kkonganti@17: def slurped = null kkonganti@17: def jsonInst = new JsonSlurper() kkonganti@17: kkonganti@17: try { kkonganti@17: slurped = jsonInst.parse(new File ("${file}")) kkonganti@17: } kkonganti@17: catch (Exception e) { kkonganti@17: log.error 'Please check your JSON schema. Invalid JSON file: ' + file kkonganti@17: } kkonganti@17: kkonganti@17: // Declare globals for the nanofactory kkonganti@17: // workflow. kkonganti@17: return [keys: slurped.keySet().toList(), cparams: slurped] kkonganti@17: } kkonganti@17: kkonganti@17: // Default help text in a map if the entry point kkonganti@17: // to a pipeline is FASTQ files. kkonganti@17: def fastqEntryPointHelp() { kkonganti@17: kkonganti@17: Map helptext = [:] kkonganti@17: Map fgcolors = getANSIColors() kkonganti@17: kkonganti@17: helptext['Workflow'] = "${fgcolors.magenta}${params.pipeline}${fgcolors.reset}" kkonganti@17: helptext['Author'] = "${fgcolors.cyan}${params.workflow_built_by}${fgcolors.reset}" kkonganti@17: helptext['Version'] = "${fgcolors.green}${params.workflow_version}${fgcolors.reset}\n" kkonganti@17: helptext['Usage'] = "cpipes --pipeline ${params.pipeline} [options]\n" kkonganti@17: helptext['Required'] = "" kkonganti@17: helptext['--input'] = "Absolute path to directory containing FASTQ files. " + kkonganti@17: "The directory should contain only FASTQ files as all the " + kkonganti@17: "files within the mentioned directory will be read. " + kkonganti@17: "Ex: --input /path/to/fastq_pass" kkonganti@17: helptext['--output'] = "Absolute path to directory where all the pipeline " + kkonganti@17: "outputs should be stored. Ex: --output /path/to/output" kkonganti@17: helptext['Other options'] = "" kkonganti@17: helptext['--metadata'] = "Absolute path to metadata CSV file containing five " + kkonganti@17: "mandatory columns: sample,fq1,fq2,strandedness,single_end. The fq1 and fq2 " + kkonganti@17: "columns contain absolute paths to the FASTQ files. This option can be used in place " + kkonganti@17: "of --input option. This is rare. Ex: --metadata samplesheet.csv" kkonganti@17: helptext['--fq_suffix'] = "The suffix of FASTQ files (Unpaired reads or R1 reads or Long reads) if " + kkonganti@17: "an input directory is mentioned via --input option. Default: ${params.fq_suffix}" kkonganti@17: helptext['--fq2_suffix'] = "The suffix of FASTQ files (Paired-end reads or R2 reads) if an input directory is mentioned via " + kkonganti@17: "--input option. Default: ${params.fq2_suffix}" kkonganti@17: helptext['--fq_filter_by_len'] = "Remove FASTQ reads that are less than this many bases. " + kkonganti@17: "Default: ${params.fq_filter_by_len}" kkonganti@17: helptext['--fq_strandedness'] = "The strandedness of the sequencing run. This is mostly needed " + kkonganti@17: "if your sequencing run is RNA-SEQ. For most of the other runs, it is probably safe to use " + kkonganti@17: "unstranded for the option. Default: ${params.fq_strandedness}" kkonganti@17: helptext['--fq_single_end'] = "SINGLE-END information will be auto-detected but this option forces " + kkonganti@17: "PAIRED-END FASTQ files to be treated as SINGLE-END so only read 1 information is included in " + kkonganti@17: "auto-generated samplesheet. Default: ${params.fq_single_end}" kkonganti@17: helptext['--fq_filename_delim'] = "Delimiter by which the file name is split to obtain sample name. " + kkonganti@17: "Default: ${params.fq_filename_delim}" kkonganti@17: helptext['--fq_filename_delim_idx'] = "After splitting FASTQ file name by using the --fq_filename_delim option," + kkonganti@17: " all elements before this index (1-based) will be joined to create final sample name." + kkonganti@17: " Default: ${params.fq_filename_delim_idx}" kkonganti@17: kkonganti@17: return helptext kkonganti@17: } kkonganti@17: kkonganti@17: // Show concise help text if configured within the main workflow. kkonganti@17: def conciseHelp(def tool = null) { kkonganti@17: Map fgcolors = getANSIColors() kkonganti@17: kkonganti@17: tool ?= "fastp" kkonganti@17: tools = tool?.tokenize(',') kkonganti@17: kkonganti@17: return """ kkonganti@17: ${dashedLine()} kkonganti@17: Show configurable CLI options for each tool within ${fgcolors.magenta}${params.pipeline}${fgcolors.reset} kkonganti@17: ${dashedLine()} kkonganti@17: Ex: cpipes --pipeline ${params.pipeline} --help kkonganti@17: """ + (tools.size() > 1 ? "Ex: cpipes --pipeline ${params.pipeline} --help ${tools[0]}" kkonganti@17: + """ kkonganti@17: Ex: cpipes --pipeline ${params.pipeline} --help ${tools[0]},${tools[1]} kkonganti@17: ${dashedLine()}""".stripIndent() : """Ex: cpipes --pipeline ${params.pipeline} --help ${tool} kkonganti@17: ${dashedLine()}""".stripIndent()) kkonganti@17: kkonganti@17: } kkonganti@17: kkonganti@17: // Wrap help text with the following options kkonganti@17: def wrapUpHelp() { kkonganti@17: kkonganti@17: return [ kkonganti@17: 'Help options' : "", kkonganti@17: '--help': "Display this message.\n", kkonganti@17: 'help': true, kkonganti@17: 'nocapitalize': true kkonganti@17: ] kkonganti@17: } kkonganti@17: kkonganti@17: // Method to send email on workflow complete. kkonganti@17: def sendMail() { kkonganti@17: kkonganti@17: if (params.user_email == null) { kkonganti@17: return 1 kkonganti@17: } kkonganti@17: kkonganti@17: def pad = (params.pad) ?: 30 kkonganti@17: def contact_emails = [ kkonganti@17: stakeholder: (params.workflow_blueprint_by ?: 'Not defined'), kkonganti@17: author: (params.workflow_built_by ?: 'Not defined') kkonganti@17: ] kkonganti@17: def msg = """ kkonganti@17: ${pipelineBanner()} kkonganti@17: ${summaryOfParams()} kkonganti@17: ${params.cfsanpipename} - ${params.pipeline} kkonganti@17: ${dashedLine()} kkonganti@17: Please check the following directory for N E X T F L O W kkonganti@17: reports. You can view the HTML files directly by double clicking kkonganti@17: them on your workstation. kkonganti@17: ${dashedLine()} kkonganti@17: ${params.tracereportsdir} kkonganti@17: ${dashedLine()} kkonganti@17: Please send any bug reports to CFSAN Dev Team or the author or kkonganti@17: the stakeholder of the current pipeline. kkonganti@17: ${dashedLine()} kkonganti@17: Error messages (if any) kkonganti@17: ${dashedLine()} kkonganti@17: ${workflow.errorMessage} kkonganti@17: ${workflow.errorReport} kkonganti@17: ${dashedLine()} kkonganti@17: Contact emails kkonganti@17: ${dashedLine()} kkonganti@17: ${addPadding(contact_emails)} kkonganti@17: ${dashedLine()} kkonganti@17: Thank you for using ${params.cfsanpipename} - ${params.pipeline}! kkonganti@17: ${dashedLine()} kkonganti@17: """.stripIndent() kkonganti@17: kkonganti@17: def mail_cmd = [ kkonganti@17: 'sendmail', kkonganti@17: '-f', 'noreply@gmail.com', kkonganti@17: '-F', 'noreply', kkonganti@17: '-t', "${params.user_email}" kkonganti@17: ] kkonganti@17: kkonganti@17: def email_subject = "${params.cfsanpipename} - ${params.pipeline}" kkonganti@17: Map fgcolors = getANSIColors() kkonganti@17: kkonganti@17: if (workflow.success) { kkonganti@17: email_subject += ' completed successfully!' kkonganti@17: } kkonganti@17: else if (!workflow.success) { kkonganti@17: email_subject += ' has failed!' kkonganti@17: } kkonganti@17: kkonganti@17: try { kkonganti@17: ['env', 'bash'].execute() << """${mail_cmd.join(' ')} kkonganti@17: Subject: ${email_subject} kkonganti@17: Mime-Version: 1.0 kkonganti@17: Content-Type: text/html kkonganti@17:
kkonganti@17: ${msg.replaceAll(/\x1b\[[0-9;]*m/, '')} kkonganti@17:kkonganti@17: """.stripIndent() kkonganti@17: } catch (all) { kkonganti@17: def warning_msg = "${fgcolors.yellow}${params.cfsanpipename} - ${params.pipeline} - WARNING" kkonganti@17: .padRight(pad) + ':' kkonganti@17: log.info """ kkonganti@17: ${dashedLine()} kkonganti@17: ${warning_msg} kkonganti@17: ${dashedLine()} kkonganti@17: Could not send mail with the sendmail command! kkonganti@17: ${dashedLine()} kkonganti@17: """.stripIndent() kkonganti@17: } kkonganti@17: return 1 kkonganti@17: } kkonganti@17: kkonganti@17: // Set ANSI colors for any and all kkonganti@17: // STDOUT or STDERR kkonganti@17: def getANSIColors() { kkonganti@17: kkonganti@17: Map fgcolors = [:] kkonganti@17: kkonganti@17: fgcolors['reset'] = "\033[0m" kkonganti@17: fgcolors['black'] = "\033[0;30m" kkonganti@17: fgcolors['red'] = "\033[0;31m" kkonganti@17: fgcolors['green'] = "\033[0;32m" kkonganti@17: fgcolors['yellow'] = "\033[0;33m" kkonganti@17: fgcolors['blue'] = "\033[0;34m" kkonganti@17: fgcolors['magenta'] = "\033[0;35m" kkonganti@17: fgcolors['cyan'] = "\033[0;36m" kkonganti@17: fgcolors['white'] = "\033[0;37m" kkonganti@17: kkonganti@17: return fgcolors kkonganti@17: }