Merge branch 'schema' into 'dev'
add schema See merge request epi2melabs/workflow-containers/wf-template!25
This commit is contained in:
commit
4a1b5cd50b
541
lib/NfcoreSchema.groovy
Normal file
541
lib/NfcoreSchema.groovy
Normal file
@ -0,0 +1,541 @@
|
||||
//
|
||||
// This file holds several functions used to perform JSON parameter validation, help and summary rendering for the nf-core pipeline template.
|
||||
//
|
||||
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2018 nf-core
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
|
||||
|
||||
import org.everit.json.schema.Schema
|
||||
import org.everit.json.schema.loader.SchemaLoader
|
||||
import org.everit.json.schema.ValidationException
|
||||
import org.json.JSONObject
|
||||
import org.json.JSONTokener
|
||||
import org.json.JSONArray
|
||||
import groovy.json.JsonSlurper
|
||||
import groovy.json.JsonBuilder
|
||||
|
||||
class NfcoreSchema {
|
||||
|
||||
//
|
||||
// Resolve Schema path relative to main workflow directory
|
||||
//
|
||||
public static String getSchemaPath(workflow, schema_filename='nextflow_schema.json') {
|
||||
return "${workflow.projectDir}/${schema_filename}"
|
||||
}
|
||||
|
||||
//
|
||||
// Function to loop over all parameters defined in schema and check
|
||||
// whether the given parameters adhere to the specifications
|
||||
//
|
||||
/* groovylint-disable-next-line UnusedPrivateMethodParameter */
|
||||
public static void validateParameters(workflow, params, log, schema_filename='nextflow_schema.json') {
|
||||
def has_error = false
|
||||
//=====================================================================//
|
||||
// Check for nextflow core params and unexpected params
|
||||
def json = new File(getSchemaPath(workflow, schema_filename=schema_filename)).text
|
||||
def Map schemaParams = (Map) new JsonSlurper().parseText(json).get('definitions')
|
||||
def nf_params = [
|
||||
// Options for base `nextflow` command
|
||||
'bg',
|
||||
'c',
|
||||
'C',
|
||||
'config',
|
||||
'd',
|
||||
'D',
|
||||
'dockerize',
|
||||
'h',
|
||||
'log',
|
||||
'q',
|
||||
'quiet',
|
||||
'syslog',
|
||||
'v',
|
||||
'version',
|
||||
|
||||
// Options for `nextflow run` command
|
||||
'ansi',
|
||||
'ansi-log',
|
||||
'bg',
|
||||
'bucket-dir',
|
||||
'c',
|
||||
'cache',
|
||||
'config',
|
||||
'dsl2',
|
||||
'dump-channels',
|
||||
'dump-hashes',
|
||||
'E',
|
||||
'entry',
|
||||
'latest',
|
||||
'lib',
|
||||
'main-script',
|
||||
'N',
|
||||
'name',
|
||||
'offline',
|
||||
'params-file',
|
||||
'pi',
|
||||
'plugins',
|
||||
'poll-interval',
|
||||
'pool-size',
|
||||
'profile',
|
||||
'ps',
|
||||
'qs',
|
||||
'queue-size',
|
||||
'r',
|
||||
'resume',
|
||||
'revision',
|
||||
'stdin',
|
||||
'stub',
|
||||
'stub-run',
|
||||
'test',
|
||||
'w',
|
||||
'with-charliecloud',
|
||||
'with-conda',
|
||||
'with-dag',
|
||||
'with-docker',
|
||||
'with-mpi',
|
||||
'with-notification',
|
||||
'with-podman',
|
||||
'with-report',
|
||||
'with-singularity',
|
||||
'with-timeline',
|
||||
'with-tower',
|
||||
'with-trace',
|
||||
'with-weblog',
|
||||
'without-docker',
|
||||
'without-podman',
|
||||
'work-dir'
|
||||
]
|
||||
def unexpectedParams = []
|
||||
|
||||
// Collect expected parameters from the schema
|
||||
def expectedParams = []
|
||||
for (group in schemaParams) {
|
||||
for (p in group.value['properties']) {
|
||||
expectedParams.push(p.key)
|
||||
}
|
||||
}
|
||||
|
||||
for (specifiedParam in params.keySet()) {
|
||||
// nextflow params
|
||||
if (nf_params.contains(specifiedParam)) {
|
||||
log.error "ERROR: You used a core Nextflow option with two hyphens: '--${specifiedParam}'. Please resubmit with '-${specifiedParam}'"
|
||||
has_error = true
|
||||
}
|
||||
// unexpected params
|
||||
def params_ignore = params.schema_ignore_params.split(',') + 'schema_ignore_params'
|
||||
def expectedParamsLowerCase = expectedParams.collect{ it.replace("-", "").toLowerCase() }
|
||||
def specifiedParamLowerCase = specifiedParam.replace("-", "").toLowerCase()
|
||||
def isCamelCaseBug = (specifiedParam.contains("-") && !expectedParams.contains(specifiedParam) && expectedParamsLowerCase.contains(specifiedParamLowerCase))
|
||||
if (!expectedParams.contains(specifiedParam) && !params_ignore.contains(specifiedParam) && !isCamelCaseBug) {
|
||||
// Temporarily remove camelCase/camel-case params #1035
|
||||
def unexpectedParamsLowerCase = unexpectedParams.collect{ it.replace("-", "").toLowerCase()}
|
||||
if (!unexpectedParamsLowerCase.contains(specifiedParamLowerCase)){
|
||||
unexpectedParams.push(specifiedParam)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//=====================================================================//
|
||||
// Validate parameters against the schema
|
||||
InputStream input_stream = new File(getSchemaPath(workflow, schema_filename=schema_filename)).newInputStream()
|
||||
JSONObject raw_schema = new JSONObject(new JSONTokener(input_stream))
|
||||
|
||||
// Remove anything that's in params.schema_ignore_params
|
||||
raw_schema = removeIgnoredParams(raw_schema, params)
|
||||
|
||||
Schema schema = SchemaLoader.load(raw_schema)
|
||||
|
||||
// Clean the parameters
|
||||
def cleanedParams = cleanParameters(params)
|
||||
|
||||
// Convert to JSONObject
|
||||
def jsonParams = new JsonBuilder(cleanedParams)
|
||||
JSONObject params_json = new JSONObject(jsonParams.toString())
|
||||
|
||||
// Validate
|
||||
try {
|
||||
schema.validate(params_json)
|
||||
} catch (ValidationException e) {
|
||||
println ''
|
||||
log.error 'ERROR: Validation of pipeline parameters failed!'
|
||||
JSONObject exceptionJSON = e.toJSON()
|
||||
printExceptions(exceptionJSON, params_json, log)
|
||||
println ''
|
||||
has_error = true
|
||||
}
|
||||
|
||||
// Check for unexpected parameters
|
||||
if (unexpectedParams.size() > 0) {
|
||||
Map colors = NfcoreTemplate.logColours(params.monochrome_logs)
|
||||
println ''
|
||||
def warn_msg = 'Found unexpected parameters:'
|
||||
for (unexpectedParam in unexpectedParams) {
|
||||
warn_msg = warn_msg + "\n* --${unexpectedParam}: ${params[unexpectedParam].toString()}"
|
||||
}
|
||||
log.warn warn_msg
|
||||
log.info "- ${colors.dim}Ignore this warning: params.schema_ignore_params = \"${unexpectedParams.join(',')}\" ${colors.reset}"
|
||||
println ''
|
||||
}
|
||||
|
||||
if (has_error) {
|
||||
System.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Beautify parameters for --help
|
||||
//
|
||||
public static String paramsHelp(workflow, params, command, schema_filename='nextflow_schema.json') {
|
||||
Map colors = NfcoreTemplate.logColours(params.monochrome_logs)
|
||||
Integer num_hidden = 0
|
||||
String output = ''
|
||||
output += 'Typical pipeline command:\n\n'
|
||||
output += " ${colors.cyan}${command}${colors.reset}\n\n"
|
||||
Map params_map = paramsLoad(getSchemaPath(workflow, schema_filename=schema_filename))
|
||||
Integer max_chars = paramsMaxChars(params_map) + 1
|
||||
Integer desc_indent = max_chars + 14
|
||||
Integer dec_linewidth = 160 - desc_indent
|
||||
for (group in params_map.keySet()) {
|
||||
Integer num_params = 0
|
||||
String group_output = colors.underlined + colors.bold + group + colors.reset + '\n'
|
||||
def group_params = params_map.get(group) // This gets the parameters of that particular group
|
||||
for (param in group_params.keySet()) {
|
||||
if (group_params.get(param).hidden && !params.show_hidden_params) {
|
||||
num_hidden += 1
|
||||
continue;
|
||||
}
|
||||
def type = '[' + group_params.get(param).type + ']'
|
||||
def description = group_params.get(param).description
|
||||
def defaultValue = group_params.get(param).default ? " [default: " + group_params.get(param).default.toString() + "]" : ''
|
||||
def description_default = description + colors.dim + defaultValue + colors.reset
|
||||
// Wrap long description texts
|
||||
// Loosely based on https://dzone.com/articles/groovy-plain-text-word-wrap
|
||||
if (description_default.length() > dec_linewidth){
|
||||
List olines = []
|
||||
String oline = "" // " " * indent
|
||||
description_default.split(" ").each() { wrd ->
|
||||
if ((oline.size() + wrd.size()) <= dec_linewidth) {
|
||||
oline += wrd + " "
|
||||
} else {
|
||||
olines += oline
|
||||
oline = wrd + " "
|
||||
}
|
||||
}
|
||||
olines += oline
|
||||
description_default = olines.join("\n" + " " * desc_indent)
|
||||
}
|
||||
group_output += " --" + param.padRight(max_chars) + colors.dim + type.padRight(10) + colors.reset + description_default + '\n'
|
||||
num_params += 1
|
||||
}
|
||||
group_output += '\n'
|
||||
if (num_params > 0){
|
||||
output += group_output
|
||||
}
|
||||
}
|
||||
if (num_hidden > 0){
|
||||
output += colors.dim + "!! Hiding $num_hidden params, use --show_hidden_params to show them !!\n" + colors.reset
|
||||
}
|
||||
output += NfcoreTemplate.dashedLine(params.monochrome_logs)
|
||||
return output
|
||||
}
|
||||
|
||||
//
|
||||
// Groovy Map summarising parameters/workflow options used by the pipeline
|
||||
//
|
||||
public static LinkedHashMap paramsSummaryMap(workflow, params, schema_filename='nextflow_schema.json') {
|
||||
// Get a selection of core Nextflow workflow options
|
||||
def Map workflow_summary = [:]
|
||||
if (workflow.revision) {
|
||||
workflow_summary['revision'] = workflow.revision
|
||||
}
|
||||
workflow_summary['runName'] = workflow.runName
|
||||
if (workflow.containerEngine) {
|
||||
workflow_summary['containerEngine'] = workflow.containerEngine
|
||||
}
|
||||
if (workflow.container) {
|
||||
workflow_summary['container'] = workflow.container
|
||||
}
|
||||
workflow_summary['launchDir'] = workflow.launchDir
|
||||
workflow_summary['workDir'] = workflow.workDir
|
||||
workflow_summary['projectDir'] = workflow.projectDir
|
||||
workflow_summary['userName'] = workflow.userName
|
||||
workflow_summary['profile'] = workflow.profile
|
||||
workflow_summary['configFiles'] = workflow.configFiles.join(', ')
|
||||
|
||||
// Get pipeline parameters defined in JSON Schema
|
||||
def Map params_summary = [:]
|
||||
def blacklist = ['hostnames']
|
||||
def params_map = paramsLoad(getSchemaPath(workflow, schema_filename=schema_filename))
|
||||
for (group in params_map.keySet()) {
|
||||
def sub_params = new LinkedHashMap()
|
||||
def group_params = params_map.get(group) // This gets the parameters of that particular group
|
||||
for (param in group_params.keySet()) {
|
||||
if (params.containsKey(param) && !blacklist.contains(param)) {
|
||||
def params_value = params.get(param)
|
||||
def schema_value = group_params.get(param).default
|
||||
def param_type = group_params.get(param).type
|
||||
if (schema_value != null) {
|
||||
if (param_type == 'string') {
|
||||
if (schema_value.contains('$projectDir') || schema_value.contains('${projectDir}')) {
|
||||
def sub_string = schema_value.replace('\$projectDir', '')
|
||||
sub_string = sub_string.replace('\${projectDir}', '')
|
||||
if (params_value.contains(sub_string)) {
|
||||
schema_value = params_value
|
||||
}
|
||||
}
|
||||
if (schema_value.contains('$params.outdir') || schema_value.contains('${params.outdir}')) {
|
||||
def sub_string = schema_value.replace('\$params.outdir', '')
|
||||
sub_string = sub_string.replace('\${params.outdir}', '')
|
||||
if ("${params.outdir}${sub_string}" == params_value) {
|
||||
schema_value = params_value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We have a default in the schema, and this isn't it
|
||||
if (schema_value != null && params_value != schema_value) {
|
||||
sub_params.put(param, params_value)
|
||||
}
|
||||
// No default in the schema, and this isn't empty
|
||||
else if (schema_value == null && params_value != "" && params_value != null && params_value != false) {
|
||||
sub_params.put(param, params_value)
|
||||
}
|
||||
}
|
||||
}
|
||||
params_summary.put(group, sub_params)
|
||||
}
|
||||
return [ 'Core Nextflow options' : workflow_summary ] << params_summary
|
||||
}
|
||||
|
||||
//
|
||||
// Beautify parameters for summary and return as string
|
||||
//
|
||||
public static String paramsSummaryLog(workflow, params) {
|
||||
Map colors = NfcoreTemplate.logColours(params.monochrome_logs)
|
||||
String output = ''
|
||||
def params_map = paramsSummaryMap(workflow, params)
|
||||
def max_chars = paramsMaxChars(params_map)
|
||||
for (group in params_map.keySet()) {
|
||||
def group_params = params_map.get(group) // This gets the parameters of that particular group
|
||||
if (group_params) {
|
||||
output += colors.bold + group + colors.reset + '\n'
|
||||
for (param in group_params.keySet()) {
|
||||
output += " " + colors.blue + param.padRight(max_chars) + ": " + colors.green + group_params.get(param) + colors.reset + '\n'
|
||||
}
|
||||
output += '\n'
|
||||
}
|
||||
}
|
||||
output += "!! Only displaying parameters that differ from the pipeline defaults !!\n"
|
||||
output += NfcoreTemplate.dashedLine(params.monochrome_logs)
|
||||
return output
|
||||
}
|
||||
|
||||
//
|
||||
// Loop over nested exceptions and print the causingException
|
||||
//
|
||||
private static void printExceptions(ex_json, params_json, log) {
|
||||
def causingExceptions = ex_json['causingExceptions']
|
||||
if (causingExceptions.length() == 0) {
|
||||
def m = ex_json['message'] =~ /required key \[([^\]]+)\] not found/
|
||||
// Missing required param
|
||||
if (m.matches()) {
|
||||
log.error "* Missing required parameter: --${m[0][1]}"
|
||||
}
|
||||
// Other base-level error
|
||||
else if (ex_json['pointerToViolation'] == '#') {
|
||||
log.error "* ${ex_json['message']}"
|
||||
}
|
||||
// Error with specific param
|
||||
else {
|
||||
def param = ex_json['pointerToViolation'] - ~/^#\//
|
||||
def param_val = params_json[param].toString()
|
||||
log.error "* --${param}: ${ex_json['message']} (${param_val})"
|
||||
}
|
||||
}
|
||||
for (ex in causingExceptions) {
|
||||
printExceptions(ex, params_json, log)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Remove an element from a JSONArray
|
||||
//
|
||||
private static JSONArray removeElement(json_array, element) {
|
||||
def list = []
|
||||
int len = json_array.length()
|
||||
for (int i=0;i<len;i++){
|
||||
list.add(json_array.get(i).toString())
|
||||
}
|
||||
list.remove(element)
|
||||
JSONArray jsArray = new JSONArray(list)
|
||||
return jsArray
|
||||
}
|
||||
|
||||
//
|
||||
// Remove ignored parameters
|
||||
//
|
||||
private static JSONObject removeIgnoredParams(raw_schema, params) {
|
||||
// Remove anything that's in params.schema_ignore_params
|
||||
params.schema_ignore_params.split(',').each{ ignore_param ->
|
||||
if(raw_schema.keySet().contains('definitions')){
|
||||
raw_schema.definitions.each { definition ->
|
||||
for (key in definition.keySet()){
|
||||
if (definition[key].get("properties").keySet().contains(ignore_param)){
|
||||
// Remove the param to ignore
|
||||
definition[key].get("properties").remove(ignore_param)
|
||||
// If the param was required, change this
|
||||
if (definition[key].has("required")) {
|
||||
def cleaned_required = removeElement(definition[key].required, ignore_param)
|
||||
definition[key].put("required", cleaned_required)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(raw_schema.keySet().contains('properties') && raw_schema.get('properties').keySet().contains(ignore_param)) {
|
||||
raw_schema.get("properties").remove(ignore_param)
|
||||
}
|
||||
if(raw_schema.keySet().contains('required') && raw_schema.required.contains(ignore_param)) {
|
||||
def cleaned_required = removeElement(raw_schema.required, ignore_param)
|
||||
raw_schema.put("required", cleaned_required)
|
||||
}
|
||||
}
|
||||
return raw_schema
|
||||
}
|
||||
|
||||
//
|
||||
// Clean and check parameters relative to Nextflow native classes
|
||||
//
|
||||
private static Map cleanParameters(params) {
|
||||
def new_params = params.getClass().newInstance(params)
|
||||
for (p in params) {
|
||||
// remove anything evaluating to false
|
||||
if (!p['value']) {
|
||||
new_params.remove(p.key)
|
||||
}
|
||||
// Cast MemoryUnit to String
|
||||
if (p['value'].getClass() == nextflow.util.MemoryUnit) {
|
||||
new_params.replace(p.key, p['value'].toString())
|
||||
}
|
||||
// Cast Duration to String
|
||||
if (p['value'].getClass() == nextflow.util.Duration) {
|
||||
new_params.replace(p.key, p['value'].toString().replaceFirst(/d(?!\S)/, "day"))
|
||||
}
|
||||
// Cast LinkedHashMap to String
|
||||
if (p['value'].getClass() == LinkedHashMap) {
|
||||
new_params.replace(p.key, p['value'].toString())
|
||||
}
|
||||
}
|
||||
return new_params
|
||||
}
|
||||
|
||||
//
|
||||
// This function tries to read a JSON params file
|
||||
//
|
||||
private static LinkedHashMap paramsLoad(String json_schema) {
|
||||
def params_map = new LinkedHashMap()
|
||||
try {
|
||||
params_map = paramsRead(json_schema)
|
||||
} catch (Exception e) {
|
||||
println "Could not read parameters settings from JSON. $e"
|
||||
params_map = new LinkedHashMap()
|
||||
}
|
||||
return params_map
|
||||
}
|
||||
|
||||
//
|
||||
// Method to actually read in JSON file using Groovy.
|
||||
// Group (as Key), values are all parameters
|
||||
// - Parameter1 as Key, Description as Value
|
||||
// - Parameter2 as Key, Description as Value
|
||||
// ....
|
||||
// Group
|
||||
// -
|
||||
private static LinkedHashMap paramsRead(String json_schema) throws Exception {
|
||||
def json = new File(json_schema).text
|
||||
def Map schema_definitions = (Map) new JsonSlurper().parseText(json).get('definitions')
|
||||
def Map schema_properties = (Map) new JsonSlurper().parseText(json).get('properties')
|
||||
/* Tree looks like this in nf-core schema
|
||||
* definitions <- this is what the first get('definitions') gets us
|
||||
group 1
|
||||
title
|
||||
description
|
||||
properties
|
||||
parameter 1
|
||||
type
|
||||
description
|
||||
parameter 2
|
||||
type
|
||||
description
|
||||
group 2
|
||||
title
|
||||
description
|
||||
properties
|
||||
parameter 1
|
||||
type
|
||||
description
|
||||
* properties <- parameters can also be ungrouped, outside of definitions
|
||||
parameter 1
|
||||
type
|
||||
description
|
||||
*/
|
||||
|
||||
// Grouped params
|
||||
def params_map = new LinkedHashMap()
|
||||
schema_definitions.each { key, val ->
|
||||
def Map group = schema_definitions."$key".properties // Gets the property object of the group
|
||||
def title = schema_definitions."$key".title
|
||||
def sub_params = new LinkedHashMap()
|
||||
group.each { innerkey, value ->
|
||||
sub_params.put(innerkey, value)
|
||||
}
|
||||
params_map.put(title, sub_params)
|
||||
}
|
||||
|
||||
// Ungrouped params
|
||||
def ungrouped_params = new LinkedHashMap()
|
||||
schema_properties.each { innerkey, value ->
|
||||
ungrouped_params.put(innerkey, value)
|
||||
}
|
||||
params_map.put("Other parameters", ungrouped_params)
|
||||
|
||||
return params_map
|
||||
}
|
||||
|
||||
//
|
||||
// Get maximum number of characters across all parameter names
|
||||
//
|
||||
private static Integer paramsMaxChars(params_map) {
|
||||
Integer max_chars = 0
|
||||
for (group in params_map.keySet()) {
|
||||
def group_params = params_map.get(group) // This gets the parameters of that particular group
|
||||
for (param in group_params.keySet()) {
|
||||
if (param.size() > max_chars) {
|
||||
max_chars = param.size()
|
||||
}
|
||||
}
|
||||
}
|
||||
return max_chars
|
||||
}
|
||||
}
|
||||
321
lib/NfcoreTemplate.groovy
Normal file
321
lib/NfcoreTemplate.groovy
Normal file
@ -0,0 +1,321 @@
|
||||
//
|
||||
// This file holds several functions used within the nf-core pipeline template.
|
||||
//
|
||||
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2018 nf-core
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
|
||||
import org.yaml.snakeyaml.Yaml
|
||||
|
||||
class NfcoreTemplate {
|
||||
|
||||
//
|
||||
// Check AWS Batch related parameters have been specified correctly
|
||||
//
|
||||
public static void awsBatch(workflow, params) {
|
||||
if (workflow.profile.contains('awsbatch')) {
|
||||
// Check params.awsqueue and params.awsregion have been set if running on AWSBatch
|
||||
assert (params.awsqueue && params.awsregion) : "Specify correct --awsqueue and --awsregion parameters on AWSBatch!"
|
||||
// Check outdir paths to be S3 buckets if running on AWSBatch
|
||||
assert params.outdir.startsWith('s3:') : "Outdir not on S3 - specify S3 Bucket to run on AWSBatch!"
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Check params.hostnames
|
||||
//
|
||||
public static void hostName(workflow, params, log) {
|
||||
Map colors = logColours(params.monochrome_logs)
|
||||
if (params.hostnames) {
|
||||
try {
|
||||
def hostname = "hostname".execute().text.trim()
|
||||
params.hostnames.each { prof, hnames ->
|
||||
hnames.each { hname ->
|
||||
if (hostname.contains(hname) && !workflow.profile.contains(prof)) {
|
||||
log.info "=${colors.yellow}====================================================${colors.reset}=\n" +
|
||||
"${colors.yellow}WARN: You are running with `-profile $workflow.profile`\n" +
|
||||
" but your machine hostname is ${colors.white}'$hostname'${colors.reset}.\n" +
|
||||
" ${colors.yellow_bold}Please use `-profile $prof${colors.reset}`\n" +
|
||||
"=${colors.yellow}====================================================${colors.reset}="
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn "[$workflow.manifest.name] Could not determine 'hostname' - skipping check. Reason: ${e.message}."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Construct and send completion email
|
||||
//
|
||||
public static void email(workflow, params, summary_params, projectDir, log, multiqc_report=[], fail_mapped_reads=[:]) {
|
||||
|
||||
// Set up the e-mail variables
|
||||
def subject = "[$workflow.manifest.name] Successful: $workflow.runName"
|
||||
if (fail_mapped_reads.size() > 0) {
|
||||
subject = "[$workflow.manifest.name] Partially successful (${fail_mapped_reads.size()} skipped): $workflow.runName"
|
||||
}
|
||||
if (!workflow.success) {
|
||||
subject = "[$workflow.manifest.name] FAILED: $workflow.runName"
|
||||
}
|
||||
|
||||
def summary = [:]
|
||||
for (group in summary_params.keySet()) {
|
||||
summary << summary_params[group]
|
||||
}
|
||||
|
||||
def misc_fields = [:]
|
||||
misc_fields['Date Started'] = workflow.start
|
||||
misc_fields['Date Completed'] = workflow.complete
|
||||
misc_fields['Pipeline script file path'] = workflow.scriptFile
|
||||
misc_fields['Pipeline script hash ID'] = workflow.scriptId
|
||||
if (workflow.repository) misc_fields['Pipeline repository Git URL'] = workflow.repository
|
||||
if (workflow.commitId) misc_fields['Pipeline repository Git Commit'] = workflow.commitId
|
||||
if (workflow.revision) misc_fields['Pipeline Git branch/tag'] = workflow.revision
|
||||
misc_fields['Nextflow Version'] = workflow.nextflow.version
|
||||
misc_fields['Nextflow Build'] = workflow.nextflow.build
|
||||
misc_fields['Nextflow Compile Timestamp'] = workflow.nextflow.timestamp
|
||||
|
||||
def email_fields = [:]
|
||||
email_fields['version'] = workflow.manifest.version
|
||||
email_fields['runName'] = workflow.runName
|
||||
email_fields['success'] = workflow.success
|
||||
email_fields['dateComplete'] = workflow.complete
|
||||
email_fields['duration'] = workflow.duration
|
||||
email_fields['exitStatus'] = workflow.exitStatus
|
||||
email_fields['errorMessage'] = (workflow.errorMessage ?: 'None')
|
||||
email_fields['errorReport'] = (workflow.errorReport ?: 'None')
|
||||
email_fields['commandLine'] = workflow.commandLine
|
||||
email_fields['projectDir'] = workflow.projectDir
|
||||
email_fields['summary'] = summary << misc_fields
|
||||
email_fields['fail_mapped_reads'] = fail_mapped_reads.keySet()
|
||||
email_fields['min_mapped_reads'] = params.min_mapped_reads
|
||||
|
||||
// On success try attach the multiqc report
|
||||
def mqc_report = null
|
||||
try {
|
||||
if (workflow.success && !params.skip_multiqc) {
|
||||
mqc_report = multiqc_report.getVal()
|
||||
if (mqc_report.getClass() == ArrayList && mqc_report.size() >= 1) {
|
||||
if (mqc_report.size() > 1) {
|
||||
log.warn "[$workflow.manifest.name] Found multiple reports from process 'MULTIQC', will use only one"
|
||||
}
|
||||
mqc_report = mqc_report[0]
|
||||
}
|
||||
}
|
||||
} catch (all) {
|
||||
if (multiqc_report) {
|
||||
log.warn "[$workflow.manifest.name] Could not attach MultiQC report to summary email"
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we are only sending emails on failure
|
||||
def email_address = params.email
|
||||
if (!params.email && params.email_on_fail && !workflow.success) {
|
||||
email_address = params.email_on_fail
|
||||
}
|
||||
|
||||
// Render the TXT template
|
||||
def engine = new groovy.text.GStringTemplateEngine()
|
||||
def tf = new File("$projectDir/assets/email_template.txt")
|
||||
def txt_template = engine.createTemplate(tf).make(email_fields)
|
||||
def email_txt = txt_template.toString()
|
||||
|
||||
// Render the HTML template
|
||||
def hf = new File("$projectDir/assets/email_template.html")
|
||||
def html_template = engine.createTemplate(hf).make(email_fields)
|
||||
def email_html = html_template.toString()
|
||||
|
||||
// Render the sendmail template
|
||||
def max_multiqc_email_size = params.max_multiqc_email_size as nextflow.util.MemoryUnit
|
||||
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() ]
|
||||
def sf = new File("$projectDir/assets/sendmail_template.txt")
|
||||
def sendmail_template = engine.createTemplate(sf).make(smail_fields)
|
||||
def sendmail_html = sendmail_template.toString()
|
||||
|
||||
// Send the HTML e-mail
|
||||
Map colors = logColours(params.monochrome_logs)
|
||||
if (email_address) {
|
||||
try {
|
||||
if (params.plaintext_email) { throw GroovyException('Send plaintext e-mail, not HTML') }
|
||||
// Try to send HTML e-mail using sendmail
|
||||
[ 'sendmail', '-t' ].execute() << sendmail_html
|
||||
log.info "-${colors.purple}[$workflow.manifest.name]${colors.green} Sent summary e-mail to $email_address (sendmail)-"
|
||||
} catch (all) {
|
||||
// Catch failures and try with plaintext
|
||||
def mail_cmd = [ 'mail', '-s', subject, '--content-type=text/html', email_address ]
|
||||
if ( mqc_report.size() <= max_multiqc_email_size.toBytes() ) {
|
||||
mail_cmd += [ '-A', mqc_report ]
|
||||
}
|
||||
mail_cmd.execute() << email_html
|
||||
log.info "-${colors.purple}[$workflow.manifest.name]${colors.green} Sent summary e-mail to $email_address (mail)-"
|
||||
}
|
||||
}
|
||||
|
||||
// Write summary e-mail HTML to a file
|
||||
def output_d = new File("${params.outdir}/pipeline_info/")
|
||||
if (!output_d.exists()) {
|
||||
output_d.mkdirs()
|
||||
}
|
||||
def output_hf = new File(output_d, "pipeline_report.html")
|
||||
output_hf.withWriter { w -> w << email_html }
|
||||
def output_tf = new File(output_d, "pipeline_report.txt")
|
||||
output_tf.withWriter { w -> w << email_txt }
|
||||
}
|
||||
|
||||
//
|
||||
// Print pipeline summary on completion
|
||||
//
|
||||
public static void summary(workflow, params, log, fail_mapped_reads=[:], pass_mapped_reads=[:]) {
|
||||
Map colors = logColours(params.monochrome_logs)
|
||||
|
||||
if (pass_mapped_reads.size() > 0) {
|
||||
def idx = 0
|
||||
def samp_aln = ''
|
||||
def total_aln_count = pass_mapped_reads.size() + fail_mapped_reads.size()
|
||||
for (samp in pass_mapped_reads) {
|
||||
samp_aln += " ${samp.value}: ${samp.key}\n"
|
||||
idx += 1
|
||||
if (idx > 5) {
|
||||
samp_aln += " ..see pipeline reports for full list\n"
|
||||
break;
|
||||
}
|
||||
}
|
||||
log.info "-${colors.purple}[$workflow.manifest.name]${colors.green} ${pass_mapped_reads.size()}/$total_aln_count samples passed Bowtie2 ${params.min_mapped_reads} mapped read threshold:\n${samp_aln}${colors.reset}-"
|
||||
}
|
||||
if (fail_mapped_reads.size() > 0) {
|
||||
def samp_aln = ''
|
||||
for (samp in fail_mapped_reads) {
|
||||
samp_aln += " ${samp.value}: ${samp.key}\n"
|
||||
}
|
||||
log.info "-${colors.purple}[$workflow.manifest.name]${colors.red} ${fail_mapped_reads.size()} samples skipped since they failed Bowtie2 ${params.min_mapped_reads} mapped read threshold:\n${samp_aln}${colors.reset}-"
|
||||
}
|
||||
|
||||
if (workflow.success) {
|
||||
if (workflow.stats.ignoredCount == 0) {
|
||||
log.info "-${colors.purple}[$workflow.manifest.name]${colors.green} Pipeline completed successfully${colors.reset}-"
|
||||
} else {
|
||||
log.info "-${colors.purple}[$workflow.manifest.name]${colors.red} Pipeline completed successfully, but with errored process(es) ${colors.reset}-"
|
||||
}
|
||||
} else {
|
||||
hostName(workflow, params, log)
|
||||
log.info "-${colors.purple}[$workflow.manifest.name]${colors.red} Pipeline completed with errors${colors.reset}-"
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// ANSII Colours used for terminal logging
|
||||
//
|
||||
public static Map logColours(Boolean monochrome_logs) {
|
||||
Map colorcodes = [:]
|
||||
|
||||
// Reset / Meta
|
||||
colorcodes['reset'] = monochrome_logs ? '' : "\033[0m"
|
||||
colorcodes['bold'] = monochrome_logs ? '' : "\033[1m"
|
||||
colorcodes['dim'] = monochrome_logs ? '' : "\033[2m"
|
||||
colorcodes['underlined'] = monochrome_logs ? '' : "\033[4m"
|
||||
colorcodes['blink'] = monochrome_logs ? '' : "\033[5m"
|
||||
colorcodes['reverse'] = monochrome_logs ? '' : "\033[7m"
|
||||
colorcodes['hidden'] = monochrome_logs ? '' : "\033[8m"
|
||||
|
||||
// Regular Colors
|
||||
colorcodes['black'] = monochrome_logs ? '' : "\033[0;30m"
|
||||
colorcodes['red'] = monochrome_logs ? '' : "\033[0;31m"
|
||||
colorcodes['green'] = monochrome_logs ? '' : "\033[0;32m"
|
||||
colorcodes['yellow'] = monochrome_logs ? '' : "\033[0;33m"
|
||||
colorcodes['blue'] = monochrome_logs ? '' : "\033[0;34m"
|
||||
colorcodes['purple'] = monochrome_logs ? '' : "\033[0;35m"
|
||||
colorcodes['cyan'] = monochrome_logs ? '' : "\033[0;36m"
|
||||
colorcodes['white'] = monochrome_logs ? '' : "\033[0;37m"
|
||||
|
||||
// Bold
|
||||
colorcodes['bblack'] = monochrome_logs ? '' : "\033[1;30m"
|
||||
colorcodes['bred'] = monochrome_logs ? '' : "\033[1;31m"
|
||||
colorcodes['bgreen'] = monochrome_logs ? '' : "\033[1;32m"
|
||||
colorcodes['byellow'] = monochrome_logs ? '' : "\033[1;33m"
|
||||
colorcodes['bblue'] = monochrome_logs ? '' : "\033[1;34m"
|
||||
colorcodes['bpurple'] = monochrome_logs ? '' : "\033[1;35m"
|
||||
colorcodes['bcyan'] = monochrome_logs ? '' : "\033[1;36m"
|
||||
colorcodes['bwhite'] = monochrome_logs ? '' : "\033[1;37m"
|
||||
|
||||
// Underline
|
||||
colorcodes['ublack'] = monochrome_logs ? '' : "\033[4;30m"
|
||||
colorcodes['ured'] = monochrome_logs ? '' : "\033[4;31m"
|
||||
colorcodes['ugreen'] = monochrome_logs ? '' : "\033[4;32m"
|
||||
colorcodes['uyellow'] = monochrome_logs ? '' : "\033[4;33m"
|
||||
colorcodes['ublue'] = monochrome_logs ? '' : "\033[4;34m"
|
||||
colorcodes['upurple'] = monochrome_logs ? '' : "\033[4;35m"
|
||||
colorcodes['ucyan'] = monochrome_logs ? '' : "\033[4;36m"
|
||||
colorcodes['uwhite'] = monochrome_logs ? '' : "\033[4;37m"
|
||||
|
||||
// High Intensity
|
||||
colorcodes['iblack'] = monochrome_logs ? '' : "\033[0;90m"
|
||||
colorcodes['ired'] = monochrome_logs ? '' : "\033[0;91m"
|
||||
colorcodes['igreen'] = monochrome_logs ? '' : "\033[0;92m"
|
||||
colorcodes['iyellow'] = monochrome_logs ? '' : "\033[0;93m"
|
||||
colorcodes['iblue'] = monochrome_logs ? '' : "\033[0;94m"
|
||||
colorcodes['ipurple'] = monochrome_logs ? '' : "\033[0;95m"
|
||||
colorcodes['icyan'] = monochrome_logs ? '' : "\033[0;96m"
|
||||
colorcodes['iwhite'] = monochrome_logs ? '' : "\033[0;97m"
|
||||
|
||||
// Bold High Intensity
|
||||
colorcodes['biblack'] = monochrome_logs ? '' : "\033[1;90m"
|
||||
colorcodes['bired'] = monochrome_logs ? '' : "\033[1;91m"
|
||||
colorcodes['bigreen'] = monochrome_logs ? '' : "\033[1;92m"
|
||||
colorcodes['biyellow'] = monochrome_logs ? '' : "\033[1;93m"
|
||||
colorcodes['biblue'] = monochrome_logs ? '' : "\033[1;94m"
|
||||
colorcodes['bipurple'] = monochrome_logs ? '' : "\033[1;95m"
|
||||
colorcodes['bicyan'] = monochrome_logs ? '' : "\033[1;96m"
|
||||
colorcodes['biwhite'] = monochrome_logs ? '' : "\033[1;97m"
|
||||
|
||||
return colorcodes
|
||||
}
|
||||
|
||||
//
|
||||
// Does what is says on the tin
|
||||
//
|
||||
public static String dashedLine(monochrome_logs) {
|
||||
Map colors = logColours(monochrome_logs)
|
||||
return "-${colors.dim}----------------------------------------------------${colors.reset}-"
|
||||
}
|
||||
|
||||
//
|
||||
// nf-core logo
|
||||
//
|
||||
public static String logo(workflow, monochrome_logs) {
|
||||
Map colors = logColours(monochrome_logs)
|
||||
String.format(
|
||||
"""\n
|
||||
${dashedLine(monochrome_logs)}
|
||||
${colors.green},--.${colors.black}/${colors.green},-.${colors.reset}
|
||||
${colors.blue} ___ __ __ __ ___ ${colors.green}/,-._.--~\'${colors.reset}
|
||||
${colors.blue} |\\ | |__ __ / ` / \\ |__) |__ ${colors.yellow}} {${colors.reset}
|
||||
${colors.blue} | \\| | \\__, \\__/ | \\ |___ ${colors.green}\\`-._,-`-,${colors.reset}
|
||||
${colors.green}`._,._,\'${colors.reset}
|
||||
${colors.purple} ${workflow.manifest.name} v${workflow.manifest.version}${colors.reset}
|
||||
${dashedLine(monochrome_logs)}
|
||||
""".stripIndent()
|
||||
)
|
||||
}
|
||||
}
|
||||
44
lib/WorkflowMain.groovy
Normal file
44
lib/WorkflowMain.groovy
Normal file
@ -0,0 +1,44 @@
|
||||
|
||||
class WorkflowMain {
|
||||
|
||||
// Citation string for pipeline
|
||||
public static String citation(workflow) {
|
||||
return "If you use wf-template for your analysis please cite:\n\n" +
|
||||
"* The nf-core framework\n" +
|
||||
" https://doi.org/10.1038/s41587-020-0439-x\n\n"
|
||||
}
|
||||
|
||||
// Print help to screen
|
||||
public static String help(workflow, params, log) {
|
||||
def command = "nextflow run epi2me-labs/wf-template --fastq <input folder> -profile docker"
|
||||
def help_string = ''
|
||||
help_string += NfcoreSchema.paramsHelp(workflow, params, command)
|
||||
help_string += '\n' + citation(workflow) + '\n'
|
||||
return help_string
|
||||
}
|
||||
|
||||
// Print parameter summary log to screen
|
||||
public static String paramsSummaryLog(workflow, params, log) {
|
||||
def summary_log = ''
|
||||
summary_log += NfcoreSchema.paramsSummaryLog(workflow, params)
|
||||
summary_log += '\n' + citation(workflow) + '\n'
|
||||
return summary_log
|
||||
}
|
||||
|
||||
// Validate parameters and print summary to screen
|
||||
public static void initialise(workflow, params, log) {
|
||||
// Print help to screen if required
|
||||
if (params.help) {
|
||||
log.info help(workflow, params, log)
|
||||
System.exit(0)
|
||||
}
|
||||
|
||||
// Validate workflow parameters via the JSON schema
|
||||
if (params.validate_params) {
|
||||
NfcoreSchema.validateParameters(workflow, params, log)
|
||||
}
|
||||
|
||||
// Print parameter summary log to screen
|
||||
log.info paramsSummaryLog(workflow, params, log)
|
||||
}
|
||||
}
|
||||
BIN
lib/nfcore_external_java_deps.jar
Normal file
BIN
lib/nfcore_external_java_deps.jar
Normal file
Binary file not shown.
29
main.nf
29
main.nf
@ -15,22 +15,6 @@ nextflow.enable.dsl = 2
|
||||
|
||||
include { fastq_ingress } from './lib/fastqingress'
|
||||
|
||||
def helpMessage(){
|
||||
log.info """
|
||||
Workflow template'
|
||||
|
||||
Usage:
|
||||
nextflow run epi2melabs/wf-template [options]
|
||||
|
||||
Script Options:
|
||||
--fastq DIR Path to FASTQ directory (required)
|
||||
--samples FILE CSV file with columns named `barcode` and `sample_name`
|
||||
(or simply a sample name for non-multiplexed data).
|
||||
--out_dir DIR Path for output (default: $params.out_dir)
|
||||
--report_name STR Optional report suffix (default: $params.report_name)
|
||||
"""
|
||||
}
|
||||
|
||||
|
||||
process summariseReads {
|
||||
// concatenate fastq and fastq.gz in a dir
|
||||
@ -121,20 +105,9 @@ workflow pipeline {
|
||||
}
|
||||
|
||||
// entrypoint workflow
|
||||
WorkflowMain.initialise(workflow, params, log)
|
||||
workflow {
|
||||
|
||||
if (params.help) {
|
||||
helpMessage()
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (!params.fastq) {
|
||||
helpMessage()
|
||||
println("")
|
||||
println("`--fastq` is required")
|
||||
exit 1
|
||||
}
|
||||
|
||||
samples = fastq_ingress(
|
||||
params.fastq, params.out_dir, params.samples, params.sanitize_fastq)
|
||||
|
||||
|
||||
@ -20,8 +20,22 @@ params {
|
||||
aws_image_prefix = null
|
||||
aws_queue = null
|
||||
report_name = "report"
|
||||
|
||||
monochrome_logs = false
|
||||
validate_params = true
|
||||
show_hidden_params = false
|
||||
schema_ignore_params = 'show_hidden_params,validate_params,monochrome_logs,aws_queue,aws_image_prefix,wfversion'
|
||||
}
|
||||
|
||||
manifest {
|
||||
name = 'epi2me-labs/wf-template'
|
||||
author = 'Oxford Nanopore Technologies'
|
||||
homePage = 'https://github.com/epi2me-labs/wf-template'
|
||||
description = 'Template workflow'
|
||||
mainScript = 'main.nf'
|
||||
nextflowVersion = '>=20.10.0'
|
||||
//version = 'v0.0.7' // TODO: do switch to this?
|
||||
}
|
||||
|
||||
executor {
|
||||
$local {
|
||||
|
||||
102
nextflow_schema.json
Normal file
102
nextflow_schema.json
Normal file
@ -0,0 +1,102 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://raw.githubusercontent.com/./master/nextflow_schema.json",
|
||||
"title": ". pipeline parameters",
|
||||
"description": "",
|
||||
"type": "object",
|
||||
"definitions": {
|
||||
"basic_input_output_options": {
|
||||
"title": "Basic Input/Output Options",
|
||||
"type": "object",
|
||||
"fa_icon": "fas fa-terminal",
|
||||
"description": "Define where the pipeline should find input data and save output data.",
|
||||
"properties": {
|
||||
"out_dir": {
|
||||
"type": "string",
|
||||
"default": "output",
|
||||
"description": "Directory for output of all user-facing files."
|
||||
},
|
||||
"fastq": {
|
||||
"type": "string",
|
||||
"description": "Directory containing fastq input files. May contain fastq files directly or directories name barcodeXX relating to independent samples."
|
||||
},
|
||||
"samples": {
|
||||
"type": "string",
|
||||
"description": "CSV file with columns named `barcode` and `sample_name`. (or simply a sample name for non-multiplexed data)"
|
||||
},
|
||||
"sanitize_fastq": {
|
||||
"type": "boolean",
|
||||
"description": "Use additional heuristics to identify barcodes from file paths.",
|
||||
"help_text": "Enabling this option will group together files into samples by the presence of strings of the form `barcodeXXX` present in filenames, rather than simply files grouped into directories (as output by MinKNOW and the Guppy basecaller)."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"fastq"
|
||||
]
|
||||
},
|
||||
"meta_data": {
|
||||
"title": "Meta Data",
|
||||
"type": "object",
|
||||
"description": "",
|
||||
"default": "",
|
||||
"properties": {
|
||||
"report_name": {
|
||||
"type": "string",
|
||||
"default": "report",
|
||||
"description": "Output report filename suffix."
|
||||
}
|
||||
}
|
||||
},
|
||||
"generic_options": {
|
||||
"title": "Generic options",
|
||||
"type": "object",
|
||||
"fa_icon": "far fa-question-circle",
|
||||
"description": "Less common options for the pipeline, typically set in a config file.",
|
||||
"help_text": "These options are common to all nf-core pipelines and allow you to customise some of the core preferences for how the pipeline runs.\n\nTypically these options would be set in a Nextflow config file loaded for all pipeline runs, such as `~/.nextflow/config`.",
|
||||
"properties": {
|
||||
"help": {
|
||||
"type": "boolean",
|
||||
"description": "Display help text.",
|
||||
"fa_icon": "fas fa-question-circle",
|
||||
"hidden": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/basic_input_output_options"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/meta_data"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/generic_options"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"aws_image_prefix": {
|
||||
"type": "string",
|
||||
"hidden": true
|
||||
},
|
||||
"aws_queue": {
|
||||
"type": "string",
|
||||
"hidden": true
|
||||
},
|
||||
"wfversion": {
|
||||
"type": "string",
|
||||
"default": "v0.3.3",
|
||||
"hidden": true
|
||||
},
|
||||
"monochrome_logs": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"validate_params": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"show_hidden_params": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user