Skip to content

Commit a5e32db

Browse files
authored
Merge pull request #680 from nf-core/add-fancy-help-json-for-james
JSON Help + Lib Functions adding
2 parents aed30d9 + 06ef70d commit a5e32db

9 files changed

Lines changed: 805 additions & 522 deletions

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
77

88
### `Added`
99

10+
- [#676](https://github.com/nf-core/eager/issues/676) - Added Lib Checks and automatic help message / summary message formatting
11+
1012
### `Fixed`
1113

1214
- [#666](https://github.com/nf-core/eager/issues/666) - Fixed input file staging for `print_nuclear_contamination`

lib/Checks.groovy

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import org.yaml.snakeyaml.Yaml
2+
3+
/*
4+
* This file holds several functions used to perform standard checks for the nf-core pipeline template.
5+
*/
6+
7+
class Checks {
8+
9+
static void check_conda_channels(log) {
10+
Yaml parser = new Yaml()
11+
def channels = []
12+
try {
13+
def config = parser.load("conda config --show channels".execute().text)
14+
channels = config.channels
15+
} catch(NullPointerException | IOException e) {
16+
log.warn "Could not verify conda channel configuration."
17+
return
18+
}
19+
20+
// Check that all channels are present
21+
def required_channels = ['conda-forge', 'bioconda', 'defaults']
22+
def conda_check_failed = !required_channels.every { ch -> ch in channels }
23+
24+
// Check that they are in the right order
25+
conda_check_failed |= !(channels.indexOf('conda-forge') < channels.indexOf('bioconda'))
26+
conda_check_failed |= !(channels.indexOf('bioconda') < channels.indexOf('defaults'))
27+
28+
if (conda_check_failed) {
29+
log.warn "=============================================================================\n" +
30+
" There is a problem with your Conda configuration!\n\n" +
31+
" You will need to set-up the conda-forge and bioconda channels correctly.\n" +
32+
" Please refer to https://bioconda.github.io/user/install.html#set-up-channels\n" +
33+
" NB: The order of the channels matters!\n" +
34+
"==================================================================================="
35+
}
36+
}
37+
38+
static void aws_batch(workflow, params) {
39+
if (workflow.profile.contains('awsbatch')) {
40+
assert (params.awsqueue && params.awsregion) : "Specify correct --awsqueue and --awsregion parameters on AWSBatch!"
41+
// Check outdir paths to be S3 buckets if running on AWSBatch
42+
// related: https://github.com/nextflow-io/nextflow/issues/813
43+
assert params.outdir.startsWith('s3:') : "Outdir not on S3 - specify S3 Bucket to run on AWSBatch!"
44+
// Prevent trace files to be stored on S3 since S3 does not support rolling files.
45+
assert !params.tracedir.startsWith('s3:') : "Specify a local tracedir or run without trace! S3 cannot be used for tracefiles."
46+
}
47+
}
48+
49+
static void hostname(workflow, params, log) {
50+
Map colors = Headers.log_colours(params.monochrome_logs)
51+
if (params.hostnames) {
52+
def hostname = "hostname".execute().text.trim()
53+
params.hostnames.each { prof, hnames ->
54+
hnames.each { hname ->
55+
if (hostname.contains(hname) && !workflow.profile.contains(prof)) {
56+
log.info "=${colors.yellow}====================================================${colors.reset}=\n" +
57+
"${colors.yellow}WARN: You are running with `-profile $workflow.profile`\n" +
58+
" but your machine hostname is ${colors.white}'$hostname'${colors.reset}.\n" +
59+
" ${colors.yellow_bold}Please use `-profile $prof${colors.reset}`\n" +
60+
"=${colors.yellow}====================================================${colors.reset}="
61+
}
62+
}
63+
}
64+
}
65+
}
66+
67+
// Citation string
68+
private static String citation(workflow) {
69+
return "If you use ${workflow.manifest.name} for your analysis please cite:\n\n" +
70+
"* The pipeline\n" +
71+
" https://doi.org/10.1101/2020.06.11.145615\n\n" +
72+
"* The nf-core framework\n" +
73+
" https://dx.doi.org/10.1038/s41587-020-0439-x\n" +
74+
" https://rdcu.be/b1GjZ\n\n" +
75+
"* Software dependencies\n" +
76+
" https://github.com/${workflow.manifest.name}/blob/master/CITATIONS.md"
77+
}
78+
79+
80+
81+
82+
83+
84+
85+
}

lib/Completion.groovy

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Functions to be run on completion of pipeline
3+
*/
4+
5+
class Completion {
6+
static void email(workflow, params, summary_params, projectDir, log, multiqc_report=[]) {
7+
8+
// Set up the e-mail variables
9+
def subject = "[$workflow.manifest.name] Successful: $workflow.runName"
10+
11+
if (!workflow.success) {
12+
subject = "[$workflow.manifest.name] FAILED: $workflow.runName"
13+
}
14+
15+
def summary = [:]
16+
for (group in summary_params.keySet()) {
17+
summary << summary_params[group]
18+
}
19+
20+
def misc_fields = [:]
21+
misc_fields['Date Started'] = workflow.start
22+
misc_fields['Date Completed'] = workflow.complete
23+
misc_fields['Pipeline script file path'] = workflow.scriptFile
24+
misc_fields['Pipeline script hash ID'] = workflow.scriptId
25+
if (workflow.repository) misc_fields['Pipeline repository Git URL'] = workflow.repository
26+
if (workflow.commitId) misc_fields['Pipeline repository Git Commit'] = workflow.commitId
27+
if (workflow.revision) misc_fields['Pipeline Git branch/tag'] = workflow.revision
28+
misc_fields['Nextflow Version'] = workflow.nextflow.version
29+
misc_fields['Nextflow Build'] = workflow.nextflow.build
30+
misc_fields['Nextflow Compile Timestamp'] = workflow.nextflow.timestamp
31+
32+
def email_fields = [:]
33+
email_fields['version'] = workflow.manifest.version
34+
email_fields['runName'] = workflow.runName
35+
email_fields['success'] = workflow.success
36+
email_fields['dateComplete'] = workflow.complete
37+
email_fields['duration'] = workflow.duration
38+
email_fields['exitStatus'] = workflow.exitStatus
39+
email_fields['errorMessage'] = (workflow.errorMessage ?: 'None')
40+
email_fields['errorReport'] = (workflow.errorReport ?: 'None')
41+
email_fields['commandLine'] = workflow.commandLine
42+
email_fields['projectDir'] = workflow.projectDir
43+
email_fields['summary'] = summary << misc_fields
44+
45+
// On success try attach the multiqc report
46+
def mqc_report = null
47+
try {
48+
if (workflow.success) {
49+
mqc_report = multiqc_report.getVal()
50+
if (mqc_report.getClass() == ArrayList && mqc_report.size() >= 1) {
51+
if (mqc_report.size() > 1) {
52+
log.warn "[$workflow.manifest.name] Found multiple reports from process 'MULTIQC', will use only one"
53+
}
54+
mqc_report = mqc_report[0]
55+
}
56+
}
57+
} catch (all) {
58+
log.warn "[$workflow.manifest.name] Could not attach MultiQC report to summary email"
59+
}
60+
61+
// Check if we are only sending emails on failure
62+
def email_address = params.email
63+
if (!params.email && params.email_on_fail && !workflow.success) {
64+
email_address = params.email_on_fail
65+
}
66+
67+
// Render the TXT template
68+
def engine = new groovy.text.GStringTemplateEngine()
69+
def tf = new File("$projectDir/assets/email_template.txt")
70+
def txt_template = engine.createTemplate(tf).make(email_fields)
71+
def email_txt = txt_template.toString()
72+
73+
// Render the HTML template
74+
def hf = new File("$projectDir/assets/email_template.html")
75+
def html_template = engine.createTemplate(hf).make(email_fields)
76+
def email_html = html_template.toString()
77+
78+
// Render the sendmail template
79+
def max_multiqc_email_size = params.max_multiqc_email_size as nextflow.util.MemoryUnit
80+
def smail_fields = [ email: email_address, subject: subject, email_txt: email_txt, email_html: email_html, projectDir: "$projectDir", mqcFile: mqc_report, mqcMaxSize: max_multiqc_email_size.toBytes()]
81+
def sf = new File("$projectDir/assets/sendmail_template.txt")
82+
def sendmail_template = engine.createTemplate(sf).make(smail_fields)
83+
def sendmail_html = sendmail_template.toString()
84+
85+
// Send the HTML e-mail
86+
Map colors = Headers.log_colours(params.monochrome_logs)
87+
if (email_address) {
88+
try {
89+
if (params.plaintext_email) { throw GroovyException('Send plaintext e-mail, not HTML') }
90+
// Try to send HTML e-mail using sendmail
91+
[ 'sendmail', '-t' ].execute() << sendmail_html
92+
log.info "-${colors.purple}[$workflow.manifest.name]${colors.green} Sent summary e-mail to $email_address (sendmail)-"
93+
} catch (all) {
94+
// Catch failures and try with plaintext
95+
def mail_cmd = [ 'mail', '-s', subject, '--content-type=text/html', email_address ]
96+
if ( mqc_report.size() <= max_multiqc_email_size.toBytes() ) {
97+
mail_cmd += [ '-A', mqc_report ]
98+
}
99+
mail_cmd.execute() << email_html
100+
log.info "-${colors.purple}[$workflow.manifest.name]${colors.green} Sent summary e-mail to $email_address (mail)-"
101+
}
102+
}
103+
104+
// Write summary e-mail HTML to a file
105+
def output_d = new File("${params.outdir}/pipeline_info/")
106+
if (!output_d.exists()) {
107+
output_d.mkdirs()
108+
}
109+
def output_hf = new File(output_d, "pipeline_report.html")
110+
output_hf.withWriter { w -> w << email_html }
111+
def output_tf = new File(output_d, "pipeline_report.txt")
112+
output_tf.withWriter { w -> w << email_txt }
113+
}
114+
115+
static void summary(workflow, params, log, fail_percent_mapped=[:], pass_percent_mapped=[:]) {
116+
Map colors = Headers.log_colours(params.monochrome_logs)
117+
118+
if (workflow.success) {
119+
if (workflow.stats.ignoredCount == 0) {
120+
log.info "-${colors.purple}[$workflow.manifest.name]${colors.green} Pipeline completed successfully${colors.reset}-"
121+
} else {
122+
log.info "-${colors.purple}[$workflow.manifest.name]${colors.red} Pipeline completed successfully, but with errored process(es) ${colors.reset}-"
123+
}
124+
} else {
125+
Checks.hostname(workflow, params, log)
126+
log.info "-${colors.purple}[$workflow.manifest.name]${colors.red} Pipeline completed with errors${colors.reset}-"
127+
}
128+
}
129+
}

lib/Headers.groovy

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* This file holds several functions used to render the nf-core ANSI header.
3+
*/
4+
5+
class Headers {
6+
7+
private static Map log_colours(Boolean monochrome_logs) {
8+
Map colorcodes = [:]
9+
colorcodes['reset'] = monochrome_logs ? '' : "\033[0m"
10+
colorcodes['dim'] = monochrome_logs ? '' : "\033[2m"
11+
colorcodes['black'] = monochrome_logs ? '' : "\033[0;30m"
12+
colorcodes['green'] = monochrome_logs ? '' : "\033[0;32m"
13+
colorcodes['yellow'] = monochrome_logs ? '' : "\033[0;33m"
14+
colorcodes['yellow_bold'] = monochrome_logs ? '' : "\033[1;93m"
15+
colorcodes['blue'] = monochrome_logs ? '' : "\033[0;34m"
16+
colorcodes['purple'] = monochrome_logs ? '' : "\033[0;35m"
17+
colorcodes['cyan'] = monochrome_logs ? '' : "\033[0;36m"
18+
colorcodes['white'] = monochrome_logs ? '' : "\033[0;37m"
19+
colorcodes['red'] = monochrome_logs ? '' : "\033[1;91m"
20+
return colorcodes
21+
}
22+
23+
static String dashed_line(monochrome_logs) {
24+
Map colors = log_colours(monochrome_logs)
25+
return "-${colors.dim}----------------------------------------------------${colors.reset}-"
26+
}
27+
28+
static String nf_core(workflow, monochrome_logs) {
29+
Map colors = log_colours(monochrome_logs)
30+
String.format(
31+
"""\n
32+
${dashed_line(monochrome_logs)}
33+
${colors.green},--.${colors.black}/${colors.green},-.${colors.reset}
34+
${colors.blue} ___ __ __ __ ___ ${colors.green}/,-._.--~\'${colors.reset}
35+
${colors.blue} |\\ | |__ __ / ` / \\ |__) |__ ${colors.yellow}} {${colors.reset}
36+
${colors.blue} | \\| | \\__, \\__/ | \\ |___ ${colors.green}\\`-._,-`-,${colors.reset}
37+
${colors.green}`._,._,\'${colors.reset}
38+
${colors.purple} ${workflow.manifest.name} v${workflow.manifest.version}${colors.reset}
39+
${dashed_line(monochrome_logs)}
40+
""".stripIndent()
41+
)
42+
}
43+
}

0 commit comments

Comments
 (0)