diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c915169..f0fa9e40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,29 @@ # Version 2.1.0 +## Breaking changes + +1. The minimum supported Nextflow version is now `23.10.0` instead of `22.10.0` + ## New features 1. The plugin now fully supports nested parameters! +2. Added a config option `validation.parametersSchema` which can be used to set the parameters JSON schema in a config file. The default is `nextflow_schema.json` + +## Help message changes + +1. The use of the `paramsHelp()` function has now been deprecated in favor of a new built-in help message functionality. `paramsHelp()` has been updated to use the reworked help message creator. If you still want to use `paramsHelp()` for some reason in your pipeline, please add the `hideWarning:true` option to it to make sure the deprecation warning will not be shown. +2. Added new configuration values to support the new help message functionality: + - `validation.help.enabled`: Enables the checker for the help message parameters. The plugin will automatically show the help message when one of these parameters have been given and exit the pipeline. Default = `false` + - `validation.help.shortParameter`: The parameter to use for the compact help message. This help message will only contain top level parameters. Default = `help` + - `validation.help.fullParameter`: The parameter to use for the expanded help message. This help message will show all parameters no matter how deeply nested they are. Default = `helpFull` + - `validation.help.showHiddenParameter`: The parameter to use to also show all parameters with the `hidden: true` keyword in the schema. Default = `showHidden` + - `validation.help.showHidden`: Set this to `true` to show hidden parameters by default. This configuration option is overwritten by the value supplied to the parameter in `validation.help.showHiddenParameter`. Default = `false` + - `validation.help.beforeText`: Some custom text to add before the help message. + - `validation.help.afterText`: Some custom text to add after the help message. + - `validation.help.command`: An example command to add to the top of the help message +3. Added support for nested parameters to the help message. A detailed help message using `--help ` will now also contain all nested parameters. The parameter supplied to `--help` can be a nested parameter too (e.g. `--help top_parameter.nested_parameter.deeper_parameter`) +4. The help message now won't show empty parameter groups. ## JSON schema fixes diff --git a/buildSrc/src/main/groovy/io.nextflow.groovy-common-conventions.gradle b/buildSrc/src/main/groovy/io.nextflow.groovy-common-conventions.gradle index eb9ac877..a5ddfc26 100644 --- a/buildSrc/src/main/groovy/io.nextflow.groovy-common-conventions.gradle +++ b/buildSrc/src/main/groovy/io.nextflow.groovy-common-conventions.gradle @@ -14,7 +14,7 @@ repositories { java { toolchain { - languageVersion = JavaLanguageVersion.of(17) + languageVersion = JavaLanguageVersion.of(19) } } diff --git a/gradle.properties b/gradle.properties index 04003651..2f605cb4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ -nextflowVersion = 22.10.0 +nextflowVersion = 23.10.0 groovyVersion = 3.0.14 diff --git a/plugins/nf-schema/build.gradle b/plugins/nf-schema/build.gradle index b65a7a23..3d72ac62 100644 --- a/plugins/nf-schema/build.gradle +++ b/plugins/nf-schema/build.gradle @@ -79,5 +79,26 @@ dependencies { // use JUnit 5 platform test { - useJUnitPlatform() + useJUnitPlatform() +} + +tasks.withType(Test) { + jvmArgs ([ + '--add-opens=java.base/java.lang=ALL-UNNAMED', + '--add-opens=java.base/java.io=ALL-UNNAMED', + '--add-opens=java.base/java.nio=ALL-UNNAMED', + '--add-opens=java.base/java.nio.file.spi=ALL-UNNAMED', + '--add-opens=java.base/java.net=ALL-UNNAMED', + '--add-opens=java.base/java.util=ALL-UNNAMED', + '--add-opens=java.base/java.util.concurrent.locks=ALL-UNNAMED', + '--add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED', + '--add-opens=java.base/sun.nio.ch=ALL-UNNAMED', + '--add-opens=java.base/sun.nio.fs=ALL-UNNAMED', + '--add-opens=java.base/sun.net.www.protocol.http=ALL-UNNAMED', + '--add-opens=java.base/sun.net.www.protocol.https=ALL-UNNAMED', + '--add-opens=java.base/sun.net.www.protocol.ftp=ALL-UNNAMED', + '--add-opens=java.base/sun.net.www.protocol.file=ALL-UNNAMED', + '--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED', + '--add-opens=java.base/jdk.internal.vm=ALL-UNNAMED', + ]) } diff --git a/plugins/nf-schema/src/main/nextflow/validation/HelpMessage.groovy b/plugins/nf-schema/src/main/nextflow/validation/HelpMessage.groovy new file mode 100644 index 00000000..8542fe26 --- /dev/null +++ b/plugins/nf-schema/src/main/nextflow/validation/HelpMessage.groovy @@ -0,0 +1,255 @@ +package nextflow.validation + +import groovy.util.logging.Slf4j + +import java.nio.file.Path + +import nextflow.Session + +/** + * This class contains methods to write a help message + * + * @author : nvnieuwk + * + */ + +@Slf4j +class HelpMessage { + + private final ValidationConfig config + private final Map colors + private Integer hiddenParametersCount = 0 + private Map paramsMap + + // The length of the terminal + private Integer terminalLength = System.getenv("COLUMNS")?.toInteger() ?: 100 + + HelpMessage(ValidationConfig inputConfig, Session session) { + config = inputConfig + colors = Utils.logColours(config.monochromeLogs) + paramsMap = Utils.paramsLoad( Path.of(Utils.getSchemaPath(session.baseDir.toString(), config.parametersSchema)) ) + addHelpParameters() + } + + public String getShortHelpMessage(String param) { + def String helpMessage = "" + if (param) { + def List paramNames = param.tokenize(".") as List + def Map paramOptions = [:] + paramsMap.each { String group, Map groupParams -> + if (groupParams.containsKey(paramNames[0])) { + paramOptions = groupParams.get(paramNames[0]) as Map + } + } + if (paramNames.size() > 1) { + paramNames.remove(0) + paramNames.each { + paramOptions = (Map) paramOptions?.properties?[it] ?: [:] + } + } + if (!paramOptions) { + throw new Exception("Unable to create help message: Specified param '${param}' does not exist in JSON schema.") + } + if(paramOptions.containsKey("properties")) { + paramOptions.properties = removeHidden(paramOptions.properties) + } + helpMessage = getDetailedHelpString(param, paramOptions) + } else { + helpMessage = getGroupHelpString() + } + return helpMessage + } + + public String getFullHelpMessage() { + return getGroupHelpString(true) + } + + public String getBeforeText() { + def String beforeText = config.help.beforeText + if (config.help.command) { + beforeText += "Typical pipeline command:\n\n" + beforeText += " ${colors.cyan}${config.help.command}${colors.reset}\n\n" + } + return beforeText + } + + public String getAfterText() { + def String afterText = "" + if (hiddenParametersCount > 0) { + afterText += " ${colors.dim}!! Hiding ${hiddenParametersCount} param(s), use the `--${config.help.showHiddenParameter}` parameter to show them !!${colors.reset}\n" + } + afterText += "-${colors.dim}----------------------------------------------------${colors.reset}-\n" + afterText += config.help.afterText + return afterText + } + + // + // Get a detailed help string from one parameter + // + private String getDetailedHelpString(String paramName, Map paramOptions) { + def String helpMessage = "${colors.underlined}${colors.bold}--${paramName}${colors.reset}\n" + def Integer optionMaxChars = Utils.longestStringLength(paramOptions.keySet().collect { it == "properties" ? "options" : it } as List) + for (option in paramOptions) { + def String key = option.key + if (key == "fa_icon" || (key == "type" && option.value == "object")) { + continue + } + if (key == "properties") { + def Map subParamsOptions = [:] + flattenNestedSchemaMap(option.value as Map).each { String subParam, Map value -> + subParamsOptions.put("${paramName}.${subParam}" as String, value) + } + def Integer maxChars = Utils.longestStringLength(subParamsOptions.keySet() as List) + 1 + def String subParamsHelpString = getHelpListParams(subParamsOptions, maxChars, paramName) + .collect { + " --" + it[4..it.length()-1] + } + .join("\n") + helpMessage += " " + colors.dim + "options".padRight(optionMaxChars) + ": " + colors.reset + "\n" + subParamsHelpString + "\n" + continue + } + def String value = option.value + if (value.length() > terminalLength) { + value = wrapText(value) + } + helpMessage += " " + colors.dim + key.padRight(optionMaxChars) + ": " + colors.reset + value + '\n' + } + return helpMessage + } + + // + // Get the full help message for a grouped params structure in list format + // + private String getGroupHelpString(Boolean showNested = false) { + def String helpMessage = "" + def Map visibleParamsMap = paramsMap.collectEntries { key, Map value -> [key, removeHidden(value)]} + def Map parsedParams = showNested ? visibleParamsMap.collectEntries { key, Map value -> [key, flattenNestedSchemaMap(value)] } : visibleParamsMap + def Integer maxChars = Utils.paramsMaxChars(parsedParams) + 1 + if (parsedParams.containsKey(null)) { + def Map ungroupedParams = parsedParams[null] + parsedParams.remove(null) + helpMessage += getHelpListParams(ungroupedParams, maxChars + 2).collect { + it[2..it.length()-1] + }.join("\n") + "\n\n" + } + parsedParams.each { String group, Map groupParams -> + def List helpList = getHelpListParams(groupParams, maxChars) + if (helpList.size() > 0) { + helpMessage += "${colors.underlined}${colors.bold}${group}${colors.reset}\n" as String + helpMessage += helpList.join("\n") + "\n\n" + } + } + return helpMessage + } + + private Map removeHidden(Map map) { + if (config.help.showHidden) { + return map + } + def Map returnMap = [:] + map.each { String key, Map value -> + if(!value.hidden) { + returnMap[key] = value + } else if(value.containsKey("properties")) { + value.properties = removeHidden(value.properties) + returnMap[key] = value + } else { + hiddenParametersCount++ + } + } + return returnMap + } + + // + // Get help for params in list format + // + private List getHelpListParams(Map params, Integer maxChars, String parentParameter = "") { + def List helpMessage = [] + def Integer typeMaxChars = Utils.longestStringLength(params.collect { key, value -> value.type instanceof String ? "[${value.type}]" : value.type as String}) + for (String paramName in params.keySet()) { + def Map paramOptions = params.get(paramName) as Map + def String type = paramOptions.type instanceof String ? '[' + paramOptions.type + ']' : paramOptions.type as String + def String enumsString = "" + if (paramOptions.enum != null) { + def List enums = (List) paramOptions.enum + def String chopEnums = enums.join(", ") + if(chopEnums.length() > terminalLength){ + chopEnums = chopEnums.substring(0, terminalLength-5) + chopEnums = chopEnums.substring(0, chopEnums.lastIndexOf(",")) + ", ..." + } + enumsString = " (accepted: " + chopEnums + ") " + } + def String description = paramOptions.description ? paramOptions.description as String + " " : "" + def defaultValue = paramOptions.default != null ? "[default: " + paramOptions.default.toString() + "] " : '' + def String nestedParamName = parentParameter ? parentParameter + "." + paramName : paramName + def String nestedString = paramOptions.properties ? "(This parameter has sub-parameters. Use '--help ${nestedParamName}' to see all sub-parameters) " : "" + def descriptionDefault = description + colors.dim + enumsString + defaultValue + colors.reset + nestedString + // Wrap long description texts + // Loosely based on https://dzone.com/articles/groovy-plain-text-word-wrap + if (descriptionDefault.length() > terminalLength){ + descriptionDefault = wrapText(descriptionDefault) + } + helpMessage.add(" --" + paramName.padRight(maxChars) + colors.dim + type.padRight(typeMaxChars + 1) + colors.reset + descriptionDefault) + } + return helpMessage + } + + // + // Flattens the schema params map so all nested parameters are shown as their full name + // + private Map flattenNestedSchemaMap(Map params) { + def Map returnMap = [:] + params.each { String key, Map value -> + if (value.containsKey("properties")) { + def flattenedMap = flattenNestedSchemaMap(value.properties) + flattenedMap.each { String k, Map v -> + returnMap.put(key + "." + k, v) + } + } else { + returnMap.put(key, value) + } + } + return returnMap + } + + // + // This function adds the help parameters to the main parameters map as ungrouped parameters + // + private void addHelpParameters() { + if (!paramsMap.containsKey(null)) { + paramsMap[null] = [:] + } + paramsMap[null][config.help.shortParameter] = [ + "type": ["boolean", "string"], + "description": "Show the help message for all top level parameters. When a parameter is given to `--${config.help.shortParameter}`, the full help message of that parameter will be printed." + ] + paramsMap[null][config.help.fullParameter] = [ + "type": "boolean", + "description": "Show the help message for all non-hidden parameters." + ] + paramsMap[null][config.help.showHiddenParameter] = [ + "type": "boolean", + "description": "Show all hidden parameters in the help message. This needs to be used in combination with `--${config.help.shortParameter}` or `--${config.help.fullParameter}`." + ] + } + + // + // Wrap too long text + // + private String wrapText(String text) { + def List olines = [] + def String oline = "" + text.split(" ").each() { wrd -> + if ((oline.size() + wrd.size()) <= terminalLength) { + oline += wrd + " " + } else { + olines += oline + oline = wrd + " " + } + } + olines += oline + return olines.join("\n") + } + + +} \ No newline at end of file diff --git a/plugins/nf-schema/src/main/nextflow/validation/JsonSchemaValidator.groovy b/plugins/nf-schema/src/main/nextflow/validation/JsonSchemaValidator.groovy index 8037b2d7..dbcd85ab 100644 --- a/plugins/nf-schema/src/main/nextflow/validation/JsonSchemaValidator.groovy +++ b/plugins/nf-schema/src/main/nextflow/validation/JsonSchemaValidator.groovy @@ -77,7 +77,7 @@ public class JsonSchemaValidator { } } - def String[] locationList = instanceLocation.split("/").findAll { it != "" } + def List locationList = instanceLocation.split("/").findAll { it != "" } as List if (locationList.size() > 0 && Utils.isInteger(locationList[0]) && validationType == "field") { def Integer entryInteger = locationList[0] as Integer diff --git a/plugins/nf-schema/src/main/nextflow/validation/SamplesheetConverter.groovy b/plugins/nf-schema/src/main/nextflow/validation/SamplesheetConverter.groovy index 98e092b8..7d432b18 100644 --- a/plugins/nf-schema/src/main/nextflow/validation/SamplesheetConverter.groovy +++ b/plugins/nf-schema/src/main/nextflow/validation/SamplesheetConverter.groovy @@ -125,7 +125,7 @@ class SamplesheetConverter { if (input instanceof Map) { def List result = [] - def LinkedHashMap properties = schema["properties"] + def Map properties = schema["properties"] as Map def Set unusedKeys = input.keySet() - properties.keySet() // Check for properties in the samplesheet that have not been defined in the schema @@ -234,7 +234,7 @@ class SamplesheetConverter { if (input instanceof Map) { def Map result = [:] - def LinkedHashMap properties = schema["properties"] + def Map properties = schema["properties"] as Map def Set unusedKeys = input.keySet() - properties.keySet() // Check for properties in the samplesheet that have not been defined in the schema diff --git a/plugins/nf-schema/src/main/nextflow/validation/SchemaValidator.groovy b/plugins/nf-schema/src/main/nextflow/validation/SchemaValidator.groovy index 99fb9ec4..ced34990 100644 --- a/plugins/nf-schema/src/main/nextflow/validation/SchemaValidator.groovy +++ b/plugins/nf-schema/src/main/nextflow/validation/SchemaValidator.groovy @@ -119,6 +119,12 @@ class SchemaValidator extends PluginExtensionPoint { // The length of the terminal private Integer terminalLength = System.getenv("COLUMNS")?.toInteger() ?: 100 + // The configuration class + private ValidationConfig config + + // The session + private Session session + @Override protected void init(Session session) { def plugins = session?.config?.navigate("plugins") as ArrayList @@ -140,14 +146,31 @@ class SchemaValidator extends PluginExtensionPoint { !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! """) } - } - Session getSession(){ - Global.getSession() as Session - } + this.session = session + + // Help message logic + def Map params = (Map)session.params ?: [:] + config = new ValidationConfig(session?.config?.navigate('validation') as Map, params) + def Boolean containsFullParameter = params.containsKey(config.help.fullParameter) && params[config.help.fullParameter] + def Boolean containsShortParameter = params.containsKey(config.help.shortParameter) && params[config.help.shortParameter] + if (config.help.enabled && (containsFullParameter || containsShortParameter)) { + def String help = "" + def HelpMessage helpMessage = new HelpMessage(config, session) + help += helpMessage.getBeforeText() + if (containsFullParameter) { + log.debug("Printing out the full help message") + help += helpMessage.getFullHelpMessage() + } else if (containsShortParameter) { + log.debug("Printing out the short help message") + def paramValue = params.get(config.help.shortParameter) + help += helpMessage.getShortHelpMessage(paramValue instanceof String ? paramValue : "") + } + help += helpMessage.getAfterText() + log.info(help) + System.exit(0) + } - ValidationConfig getConfig() { - new ValidationConfig(session.config.navigate('validation') as Map) } boolean hasErrors() { errors.size()>0 } @@ -238,7 +261,7 @@ class SchemaValidator extends PluginExtensionPoint { def Map params = initialiseExpectedParams(session.params) def String baseDir = session.baseDir.toString() - def String schemaFilename = options?.containsKey('parameters_schema') ? options.parameters_schema as String : 'nextflow_schema.json' + def String schemaFilename = options?.containsKey('parameters_schema') ? options.parameters_schema as String : config.parametersSchema log.debug "Starting parameters validation" // Clean the parameters @@ -333,154 +356,41 @@ class SchemaValidator extends PluginExtensionPoint { return new Tuple (expectedParams, enums) } - - // - // Wrap too long text - // - String wrapText(String text) { - List olines = [] - String oline = "" - text.split(" ").each() { wrd -> - if ((oline.size() + wrd.size()) <= terminalLength) { - oline += wrd + " " - } else { - olines += oline - oline = wrd + " " - } - } - olines += oline - return olines.join("\n") - } - // // Beautify parameters for --help // @Function - String paramsHelp( - Map options = null, + public String paramsHelp( + Map options = [:], String command ) { - def Map params = initialiseExpectedParams(session.params) - - def String schemaFilename = options?.containsKey('parameters_schema') ? options.parameters_schema as String : 'nextflow_schema.json' - - def colors = Utils.logColours(config.monochromeLogs) - Integer num_hidden = 0 - String output = '' - output += 'Typical pipeline command:\n\n' - output += " ${colors.cyan}${command}${colors.reset}\n\n" - Map paramsMap = paramsLoad( Path.of(Utils.getSchemaPath(session.baseDir.toString(), schemaFilename)) ) - Integer maxChars = paramsMaxChars(paramsMap) + 1 - - // Make sure the hidden parameters count is 0 - hiddenParametersCount = 0 - - // If a value is passed to help - if (params.help instanceof String) { - def String paramName = params.help - def List paramNames = params.help.tokenize(".") as List - def Map paramOptions = [:] - for (group in paramsMap.keySet()) { - def Map group_params = paramsMap.get(group) as Map // This gets the parameters of that particular group - if (group_params.containsKey(paramNames[0])) { - paramOptions = group_params.get(paramNames[0]) as Map - } - } - if (paramNames.size() > 1) { - paramNames.remove(0) - paramNames.each { - paramOptions = (Map) paramOptions?.properties?[it] ?: [:] - } - } - if (!paramOptions) { - throw new Exception("Specified param '${paramName}' does not exist in JSON schema.") - } - output += getDetailedHelpString(paramName, paramOptions, colors) - output += "-${colors.dim}----------------------------------------------------${colors.reset}-" - return output - } - - for (group in paramsMap.keySet()) { - Integer num_params = 0 - String group_output = "$colors.underlined$colors.bold$group$colors.reset\n" - def Map group_params = paramsMap.get(group) as Map // This gets the parameters of that particular group - group_output += getHelpList(group_params, colors, maxChars).join("\n") + "\n" - if (group_output != "\n"){ - output += group_output - } - } - if (num_hidden > 0){ - output += "$colors.dim !! Hiding $num_hidden params, use the 'validation.showHiddenParams' config value to show them !!\n$colors.reset" - } - output += "-${colors.dim}----------------------------------------------------${colors.reset}-" - return output - } + // TODO add link to help message migration guide once created + if (!options.containsKey("hideWarning") || options.hideWarning == false) { + log.warn(""" +Using `paramsHelp()` is not recommended. Check out the help message migration guide: +If you intended to use this function, please add the following option to the input of the function: + `hideWarning: true` - // - // Get help text in string format - // - private List getHelpList(Map params, Map colors, Integer maxChars, String parentParameter = "") { - def List helpMessage = [] - for (String paramName in params.keySet()) { - def Map paramOptions = params.get(paramName) as Map - if (paramOptions.hidden && !config.showHiddenParams) { - hiddenParametersCount += 1 - continue - } - def String type = '[' + paramOptions.type + ']' - def String enumsString = "" - if (paramOptions.enum != null) { - def List enums = (List) paramOptions.enum - def String chopEnums = enums.join(", ") - if(chopEnums.length() > terminalLength){ - chopEnums = chopEnums.substring(0, terminalLength-5) - chopEnums = chopEnums.substring(0, chopEnums.lastIndexOf(",")) + ", ..." - } - enumsString = " (accepted: " + chopEnums + ") " - } - def String description = paramOptions.description ? paramOptions.description as String + " " : "" - def defaultValue = paramOptions.default != null ? "[default: " + paramOptions.default.toString() + "] " : '' - def String nestedParamName = parentParameter ? parentParameter + "." + paramName : paramName - def String nestedString = paramOptions.properties ? "(This parameter has sub-parameters. Use '--help ${nestedParamName}' to see all sub-parameters) " : "" - def descriptionDefault = description + colors.dim + enumsString + defaultValue + colors.reset + nestedString - // Wrap long description texts - // Loosely based on https://dzone.com/articles/groovy-plain-text-word-wrap - if (descriptionDefault.length() > terminalLength){ - descriptionDefault = wrapText(descriptionDefault) - } - helpMessage.add(" --" + paramName.padRight(maxChars) + colors.dim + type.padRight(10) + colors.reset + descriptionDefault) +Please contact the pipeline maintainer(s) if you see this warning as a user. + """) } - return helpMessage - } - // - // Get a detailed help string from one parameter - // - private String getDetailedHelpString(String paramName, Map paramOptions, Map colors) { - def String helpMessage = "--" + paramName + '\n' - for (option in paramOptions) { - def String key = option.key - if (key == "fa_icon" || (key == "type" && option.value == "object")) { - continue - } - if (key == "properties") { - def Map subParamsOptions = option.value as Map - def Integer maxChars = paramsMaxChars(subParamsOptions) + 2 - def String subParamsHelpString = getHelpList(subParamsOptions, colors, maxChars, paramName) - .collect { - " ." + it[4..it.length()-1] - } - .join("\n") - helpMessage += " " + colors.dim + "options".padRight(11) + ": " + colors.reset + "\n" + subParamsHelpString + "\n" - continue - } - def String value = option.value - if (value.length() > terminalLength) { - value = wrapText(value) - } - helpMessage += " " + colors.dim + key.padRight(11) + ": " + colors.reset + value + '\n' - } - return helpMessage + def Map params = session.params + def Map validationConfig = (Map)session.config.navigate("validation") + validationConfig.parametersSchema = options.containsKey('parameters_schema') ? options.parameters_schema as String : config.parametersSchema + validationConfig.help = (Map)validationConfig.help + [command: command, beforeText: "", afterText: ""] + def ValidationConfig copyConfig = new ValidationConfig(validationConfig, params) + def HelpMessage helpMessage = new HelpMessage(copyConfig, session) + def String help = helpMessage.getBeforeText() + def List helpBodyLines = helpMessage.getShortHelpMessage(params.help && params.help instanceof String ? params.help : "").readLines() + help += helpBodyLines.findAll { + // Remove added ungrouped help parameters + !it.startsWith("--${copyConfig.help.shortParameter}") && + !it.startsWith("--${copyConfig.help.fullParameter}") && + !it.startsWith("--${copyConfig.help.showHiddenParameter}") + }.join("\n") + help += helpMessage.getAfterText() + return help } // @@ -492,7 +402,7 @@ class SchemaValidator extends PluginExtensionPoint { WorkflowMetadata workflow ) { - def String schemaFilename = options?.containsKey('parameters_schema') ? options.parameters_schema as String : 'nextflow_schema.json' + def String schemaFilename = options?.containsKey('parameters_schema') ? options.parameters_schema as String : config.parametersSchema def Map params = session.params // Get a selection of core Nextflow workflow options @@ -517,12 +427,13 @@ class SchemaValidator extends PluginExtensionPoint { // Get pipeline parameters defined in JSON Schema def Map paramsSummary = [:] - def Map paramsMap = paramsLoad( Path.of(Utils.getSchemaPath(session.baseDir.toString(), schemaFilename)) ) + def Map paramsMap = Utils.paramsLoad( Path.of(Utils.getSchemaPath(session.baseDir.toString(), schemaFilename)) ) for (group in paramsMap.keySet()) { def Map groupSummary = getSummaryMapFromParams(params, paramsMap.get(group) as Map) paramsSummary.put(group, groupSummary) } - return [ 'Core Nextflow options' : workflowSummary ] << paramsSummary as LinkedHashMap + paramsSummary.put('Core Nextflow options', workflowSummary) + return paramsSummary } @@ -585,7 +496,7 @@ class SchemaValidator extends PluginExtensionPoint { def Map params = session.params - def String schemaFilename = options?.containsKey('parameters_schema') ? options.parameters_schema as String : 'nextflow_schema.json' + def String schemaFilename = options?.containsKey('parameters_schema') ? options.parameters_schema as String : config.parametersSchema def colors = Utils.logColours(config.monochromeLogs) String output = '' @@ -593,7 +504,7 @@ class SchemaValidator extends PluginExtensionPoint { paramsMap.each { key, value -> paramsMap[key] = flattenNestedParamsMap(value as Map) } - def maxChars = paramsMaxChars(paramsMap) + def maxChars = Utils.paramsMaxChars(paramsMap) for (group in paramsMap.keySet()) { def Map group_params = paramsMap.get(group) as Map // This gets the parameters of that particular group if (group_params) { @@ -654,99 +565,4 @@ class SchemaValidator extends PluginExtensionPoint { } return new_params } - - // - // This function tries to read a JSON params file - // - private static LinkedHashMap paramsLoad(Path json_schema) { - def paramsMap = new LinkedHashMap() - try { - paramsMap = paramsRead(json_schema) - } catch (Exception e) { - println "Could not read parameters settings from JSON. $e" - paramsMap = new LinkedHashMap() - } - return paramsMap - } - - // - // 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(Path json_schema) throws Exception { - def slurper = new JsonSlurper() - def Map schema = (Map) slurper.parse( json_schema ) - // $defs is the adviced keyword for definitions. Keeping defs in for backwards compatibility - def Map schema_defs = (Map) (schema.get('$defs') ?: schema.get("defs")) - def Map schema_properties = (Map) schema.get('properties') - /* Tree looks like this in nf-core schema - * $defs <- this is what the first get('$defs') 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 $defs - parameter 1 - type - description - */ - - def paramsMap = new LinkedHashMap() - // Grouped params - if (schema_defs) { - for (group in schema_defs) { - def Map group_property = (Map) group.value['properties'] // Gets the property object of the group - def String title = (String) group.value['title'] - def sub_params = new LinkedHashMap() - group_property.each { innerkey, value -> - sub_params.put(innerkey, value) - } - paramsMap.put(title, sub_params) - } - } - - // Ungrouped params - if (schema_properties) { - def ungrouped_params = new LinkedHashMap() - schema_properties.each { innerkey, value -> - ungrouped_params.put(innerkey, value) - } - paramsMap.put("Other parameters", ungrouped_params) - } - - return paramsMap - } - - // - // Get maximum number of characters across all parameter names - // - private static Integer paramsMaxChars( Map paramsMap) { - Integer maxChars = 0 - for (group in paramsMap.keySet()) { - def Map group_params = (Map) paramsMap.get(group) // This gets the parameters of that particular group - for (String param in group_params.keySet()) { - if (param.size() > maxChars) { - maxChars = param.size() - } - } - } - return maxChars - } } diff --git a/plugins/nf-schema/src/main/nextflow/validation/Utils.groovy b/plugins/nf-schema/src/main/nextflow/validation/Utils.groovy index 79f2c4e6..736082db 100644 --- a/plugins/nf-schema/src/main/nextflow/validation/Utils.groovy +++ b/plugins/nf-schema/src/main/nextflow/validation/Utils.groovy @@ -123,7 +123,7 @@ public class Utils { } // Resolve Schema path relative to main workflow directory - public static String getSchemaPath(String baseDir, String schemaFilename='nextflow_schema.json') { + public static String getSchemaPath(String baseDir, String schemaFilename) { if (Path.of(schemaFilename).exists()) { return schemaFilename } else { @@ -269,4 +269,99 @@ public class Utils { return colorcodes } + + // + // This function tries to read a JSON params file + // + public static Map paramsLoad(Path json_schema) { + def paramsMap = [:] + try { + paramsMap = paramsRead(json_schema) + } catch (Exception e) { + println "Could not read parameters settings from JSON. $e" + } + return paramsMap + } + + // + // 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 Map paramsRead(Path json_schema) throws Exception { + def slurper = new JsonSlurper() + def Map schema = (Map) slurper.parse( json_schema ) + // $defs is the adviced keyword for definitions. Keeping defs in for backwards compatibility + def Map schema_defs = (Map) (schema.get('$defs') ?: schema.get("defs")) + def Map schema_properties = (Map) schema.get('properties') + /* Tree looks like this in nf-core schema + * $defs <- this is what the first get('$defs') 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 $defs + parameter 1 + type + description + */ + + def paramsMap = [:] + // Grouped params + if (schema_defs) { + for (group in schema_defs) { + def Map group_property = (Map) group.value['properties'] // Gets the property object of the group + def String title = (String) group.value['title'] + def sub_params = [:] + group_property.each { innerkey, value -> + sub_params.put(innerkey, value) + } + paramsMap.put(title, sub_params) + } + } + + // Ungrouped params + if (schema_properties) { + def ungrouped_params = [:] + schema_properties.each { innerkey, value -> + ungrouped_params.put(innerkey, value) + } + paramsMap.put("Other parameters", ungrouped_params) + } + + return paramsMap + } + + // + // Get maximum number of characters across all parameter names + // + public static Integer paramsMaxChars( Map paramsMap ) { + return Collections.max(paramsMap.collect { _, val -> + def Map groupParams = val as Map + longestStringLength(groupParams.keySet() as List ) + }) + } + + // + // Get the size of the longest string value in a list of strings + // + public static Integer longestStringLength( List strings ) { + return strings ? Collections.max(strings.collect { it.size() }) : 0 + } } \ No newline at end of file diff --git a/plugins/nf-schema/src/main/nextflow/validation/config/HelpConfig.groovy b/plugins/nf-schema/src/main/nextflow/validation/config/HelpConfig.groovy new file mode 100644 index 00000000..1a9d7d2d --- /dev/null +++ b/plugins/nf-schema/src/main/nextflow/validation/config/HelpConfig.groovy @@ -0,0 +1,40 @@ +package nextflow.validation + +import groovy.util.logging.Slf4j +import groovy.transform.PackageScope + + +/** + * This class allows to model a specific configuration, extracting values from a map and converting + * + * We anotate this class as @PackageScope to restrict the access of their methods only to class in the + * same package + * + * @author : nvnieuwk + * + */ + +@Slf4j +@PackageScope +class HelpConfig { + final public Boolean enabled + final public String shortParameter + final public String fullParameter + final public String showHiddenParameter + final public String beforeText + final public String afterText + final public String command + final public Boolean showHidden + + HelpConfig(Map map, Map params) { + def config = map ?: Collections.emptyMap() + enabled = config.enabled ?: false + shortParameter = config.shortParameter ?: "help" + fullParameter = config.fullParameter ?: "helpFull" + showHiddenParameter = config.showHiddenParameter ?: "showHidden" + beforeText = config.beforeText ?: "" + afterText = config.afterText ?: "" + command = config.command ?: "" + showHidden = params.get(showHiddenParameter) ?: config.showHidden ?: false + } +} \ No newline at end of file diff --git a/plugins/nf-schema/src/main/nextflow/validation/ValidationConfig.groovy b/plugins/nf-schema/src/main/nextflow/validation/config/ValidationConfig.groovy similarity index 64% rename from plugins/nf-schema/src/main/nextflow/validation/ValidationConfig.groovy rename to plugins/nf-schema/src/main/nextflow/validation/config/ValidationConfig.groovy index 5dd6374f..bb347e51 100644 --- a/plugins/nf-schema/src/main/nextflow/validation/ValidationConfig.groovy +++ b/plugins/nf-schema/src/main/nextflow/validation/config/ValidationConfig.groovy @@ -18,19 +18,25 @@ import groovy.transform.PackageScope @PackageScope class ValidationConfig { - final private Boolean lenientMode - final private Boolean monochromeLogs - final private Boolean failUnrecognisedParams - final private Boolean showHiddenParams + final public Boolean lenientMode + final public Boolean monochromeLogs + final public Boolean failUnrecognisedParams + final public String parametersSchema + final public Boolean showHiddenParams = false + final public HelpConfig help - final private List ignoreParams + final public List ignoreParams - ValidationConfig(Map map){ + ValidationConfig(Map map, Map params){ def config = map ?: Collections.emptyMap() lenientMode = config.lenientMode ?: false monochromeLogs = config.monochromeLogs ?: false failUnrecognisedParams = config.failUnrecognisedParams ?: false - showHiddenParams = config.showHiddenParams ?: false + if(config.showHiddenParams) { + log.warn("configuration option `validation.showHiddenParams` is deprecated, please use `validation.help.showHidden` or the `--showHidden` parameter instead") + } + parametersSchema = config.parametersSchema ?: "nextflow_schema.json" + help = new HelpConfig(config.help as Map ?: [:], params) if(config.ignoreParams && !(config.ignoreParams instanceof List)) { throw new SchemaValidationException("Config value 'validation.ignoreParams' should be a list of String values") diff --git a/plugins/nf-schema/src/resources/META-INF/MANIFEST.MF b/plugins/nf-schema/src/resources/META-INF/MANIFEST.MF index 8bf31df6..028c9086 100644 --- a/plugins/nf-schema/src/resources/META-INF/MANIFEST.MF +++ b/plugins/nf-schema/src/resources/META-INF/MANIFEST.MF @@ -3,4 +3,4 @@ Plugin-Id: nf-schema Plugin-Version: 2.1.0 Plugin-Class: nextflow.validation.ValidationPlugin Plugin-Provider: nextflow -Plugin-Requires: >=22.10.0 +Plugin-Requires: >=23.10.0 diff --git a/plugins/nf-schema/src/test/nextflow/validation/HelpMessageTest.groovy b/plugins/nf-schema/src/test/nextflow/validation/HelpMessageTest.groovy new file mode 100644 index 00000000..6ce2da7f --- /dev/null +++ b/plugins/nf-schema/src/test/nextflow/validation/HelpMessageTest.groovy @@ -0,0 +1,492 @@ +package nextflow.validation + +import java.nio.file.Path + +import nextflow.plugin.Plugins +import nextflow.plugin.TestPluginDescriptorFinder +import nextflow.plugin.TestPluginManager +import nextflow.plugin.extension.PluginExtensionProvider +import org.pf4j.PluginDescriptorFinder +import nextflow.Session +import spock.lang.Specification +import spock.lang.Shared +import org.slf4j.Logger +import org.junit.Rule +import test.Dsl2Spec +import test.OutputCapture + +/** + * @author : nvnieuwk + */ +class HelpMessageTest extends Specification{ + + @Rule + OutputCapture capture = new OutputCapture() + + @Shared String pluginsMode + + Path root = Path.of('.').toAbsolutePath().normalize() + Path getRoot() { this.root } + String getRootString() { this.root.toString() } + + private Session session + + def setup() { + session = Mock(Session) + session.getBaseDir() >> getRoot() + } + + def 'should get a short help message' () { + given: + def validationConfig = [ + monochromeLogs: true, + parametersSchema: 'src/testResources/nextflow_schema.json', + help: [ + enabled: true + ] + ] + def params = [:] + def config = new ValidationConfig(validationConfig, params) + def helpMessage = new HelpMessage(config, session) + + when: + def help = helpMessage.getShortHelpMessage("") + + then: + noExceptionThrown() + def expectedHelp = """--help [boolean, string] Show the help message for all top level parameters. When a parameter is given to `--help`, the full +help message of that parameter will be printed. +--helpFull [boolean] Show the help message for all non-hidden parameters. +--showHidden [boolean] Show all hidden parameters in the help message. This needs to be used in combination with `--help` +or `--helpFull`. + +Input/output options + --input [string] Path to comma-separated file containing information about the samples in the experiment. + --outdir [string] The output directory where the results will be saved. You have to use absolute paths to storage on +Cloud infrastructure. + --email [string] Email address for completion summary. + --multiqc_title [string] MultiQC report title. Printed as page header, used for filename if not otherwise specified. + +Reference genome options + --genome [string] Name of iGenomes reference. + --fasta [string] Path to FASTA genome file. + +""" + def resultHelp = help.readLines() + expectedHelp.readLines().each { + assert help.contains(it) + resultHelp.removeElement(it) + } + assert resultHelp.size() == 0, "Found extra unexpected lines: ${resultHelp}" + } + + def 'should get a short help message with hidden params - config' () { + given: + def validationConfig = [ + monochromeLogs: true, + parametersSchema: 'src/testResources/nextflow_schema.json', + help: [ + enabled: true, + showHidden: true + ] + ] + def params = [:] + def config = new ValidationConfig(validationConfig, params) + def helpMessage = new HelpMessage(config, session) + + when: + def help = helpMessage.getShortHelpMessage("") + + then: + noExceptionThrown() + def expectedHelp = """--help [boolean, string] Show the help message for all top level parameters. When a parameter is given to `--help`, the full +help message of that parameter will be printed. +--helpFull [boolean] Show the help message for all non-hidden parameters. +--showHidden [boolean] Show all hidden parameters in the help message. This needs to be used in combination with `--help` +or `--helpFull`. + +Input/output options + --input [string] Path to comma-separated file containing information about the samples in the experiment. + --outdir [string] The output directory where the results will be saved. You have to use absolute paths to storage on +Cloud infrastructure. + --email [string] Email address for completion summary. + --multiqc_title [string] MultiQC report title. Printed as page header, used for filename if not otherwise specified. + +Reference genome options + --genome [string] Name of iGenomes reference. + --fasta [string] Path to FASTA genome file. + --igenomes_base [string] Directory / URL base for iGenomes references. [default: s3://ngi-igenomes/igenomes] + --igenomes_ignore [boolean] Do not load the iGenomes reference config. + +Institutional config options + --custom_config_version [string] Git commit id for Institutional configs. [default: master] + --custom_config_base [string] Base directory for Institutional configs. [default: +https://raw.githubusercontent.com/nf-core/configs/master] + --config_profile_name [string] Institutional config name. + --config_profile_description [string] Institutional config description. + --config_profile_contact [string] Institutional config contact information. + --config_profile_url [string] Institutional config URL link. + +Max job request options + --max_cpus [integer] Maximum number of CPUs that can be requested for any single job. [default: 16] + --max_memory [string] Maximum amount of memory that can be requested for any single job. [default: 128.GB] + --max_time [string] Maximum amount of time that can be requested for any single job. [default: 240.h] + +Generic options + --help [string, boolean] Display help text. + --publish_dir_mode [string] Method used to save pipeline results to output directory. (accepted: symlink, rellink, link, copy, +copyNoFollow, move) [default: copy] + --email_on_fail [string] Email address for completion summary, only when pipeline fails. + --plaintext_email [boolean] Send plain-text email instead of HTML. + --max_multiqc_email_size [string] File size limit when attaching MultiQC reports to summary emails. [default: 25.MB] + --monochrome_logs [boolean] Do not use coloured log outputs. + --multiqc_config [string] Custom config file to supply to MultiQC. + --tracedir [string] Directory to keep pipeline Nextflow logs and reports. [default: \${params.outdir}/pipeline_info] + --validate_params [boolean] Boolean whether to validate parameters against the schema at runtime [default: true] + --validationShowHiddenParams [boolean] Show all params when using `--help` + --enable_conda [boolean] Run this workflow with Conda. You can also use '-profile conda' instead of providing this parameter. + +""" + def resultHelp = help.readLines() + expectedHelp.readLines().each { + assert help.contains(it) + resultHelp.removeElement(it) + } + assert resultHelp.size() == 0, "Found extra unexpected lines: ${resultHelp}" + } + + def 'should get a short help message with hidden params - param' () { + given: + def validationConfig = [ + monochromeLogs: true, + parametersSchema: 'src/testResources/nextflow_schema.json', + help: [ + enabled: true, + showHiddenParameter: "showMeThoseHiddenParams" + ] + ] + def params = [ + showMeThoseHiddenParams:true + ] + def config = new ValidationConfig(validationConfig, params) + def helpMessage = new HelpMessage(config, session) + + when: + def help = helpMessage.getShortHelpMessage("") + + then: + noExceptionThrown() + def expectedHelp = """--help [boolean, string] Show the help message for all top level parameters. When a parameter is given to `--help`, the full +help message of that parameter will be printed. +--helpFull [boolean] Show the help message for all non-hidden parameters. +--showMeThoseHiddenParams [boolean] Show all hidden parameters in the help message. This needs to be used in combination with `--help` +or `--helpFull`. + +Input/output options + --input [string] Path to comma-separated file containing information about the samples in the experiment. + --outdir [string] The output directory where the results will be saved. You have to use absolute paths to storage on +Cloud infrastructure. + --email [string] Email address for completion summary. + --multiqc_title [string] MultiQC report title. Printed as page header, used for filename if not otherwise specified. + +Reference genome options + --genome [string] Name of iGenomes reference. + --fasta [string] Path to FASTA genome file. + --igenomes_base [string] Directory / URL base for iGenomes references. [default: s3://ngi-igenomes/igenomes] + --igenomes_ignore [boolean] Do not load the iGenomes reference config. + +Institutional config options + --custom_config_version [string] Git commit id for Institutional configs. [default: master] + --custom_config_base [string] Base directory for Institutional configs. [default: +https://raw.githubusercontent.com/nf-core/configs/master] + --config_profile_name [string] Institutional config name. + --config_profile_description [string] Institutional config description. + --config_profile_contact [string] Institutional config contact information. + --config_profile_url [string] Institutional config URL link. + +Max job request options + --max_cpus [integer] Maximum number of CPUs that can be requested for any single job. [default: 16] + --max_memory [string] Maximum amount of memory that can be requested for any single job. [default: 128.GB] + --max_time [string] Maximum amount of time that can be requested for any single job. [default: 240.h] + +Generic options + --help [string, boolean] Display help text. + --publish_dir_mode [string] Method used to save pipeline results to output directory. (accepted: symlink, rellink, link, copy, +copyNoFollow, move) [default: copy] + --email_on_fail [string] Email address for completion summary, only when pipeline fails. + --plaintext_email [boolean] Send plain-text email instead of HTML. + --max_multiqc_email_size [string] File size limit when attaching MultiQC reports to summary emails. [default: 25.MB] + --monochrome_logs [boolean] Do not use coloured log outputs. + --multiqc_config [string] Custom config file to supply to MultiQC. + --tracedir [string] Directory to keep pipeline Nextflow logs and reports. [default: \${params.outdir}/pipeline_info] + --validate_params [boolean] Boolean whether to validate parameters against the schema at runtime [default: true] + --validationShowHiddenParams [boolean] Show all params when using `--help` + --enable_conda [boolean] Run this workflow with Conda. You can also use '-profile conda' instead of providing this parameter. + +""" + def resultHelp = help.readLines() + expectedHelp.readLines().each { + assert help.contains(it) + resultHelp.removeElement(it) + } + assert resultHelp.size() == 0, "Found extra unexpected lines: ${resultHelp}" + } + + def 'should get a full help message' () { + given: + def validationConfig = [ + monochromeLogs: true, + parametersSchema: 'src/testResources/nextflow_schema_nested_parameters.json', + help: [ + enabled: true + ] + ] + def params = [:] + def config = new ValidationConfig(validationConfig, params) + def helpMessage = new HelpMessage(config, session) + + when: + def help = helpMessage.getFullHelpMessage() + + then: + noExceptionThrown() + def expectedHelp = """--help [boolean, string] Show the help message for all top level parameters. When a parameter is given to `--help`, the full +help message of that parameter will be printed. +--helpFull [boolean] Show the help message for all non-hidden parameters. +--showHidden [boolean] Show all hidden parameters in the help message. This needs to be used in combination with `--help` +or `--helpFull`. + +Nested Parameters + --this.is.so.deep [boolean] so deep [default: true] + +""" + def resultHelp = help.readLines() + expectedHelp.readLines().each { + assert help.contains(it) + resultHelp.removeElement(it) + } + assert resultHelp.size() == 0, "Found extra unexpected lines: ${resultHelp}" + } + + def 'should get a detailed help message - nested' () { + given: + def validationConfig = [ + monochromeLogs: true, + parametersSchema: 'src/testResources/nextflow_schema_nested_parameters.json', + help: [ + enabled: true + ] + ] + def params = [:] + def config = new ValidationConfig(validationConfig, params) + def helpMessage = new HelpMessage(config, session) + + when: + def help = helpMessage.getShortHelpMessage("this") + + then: + noExceptionThrown() + def expectedHelp = """--this + description: this is this + options : + --this.is.so.deep [boolean] so deep [default: true] + +""" + def resultHelp = help.readLines() + expectedHelp.readLines().each { + assert help.contains(it) + resultHelp.removeElement(it) + } + assert resultHelp.size() == 0, "Found extra unexpected lines: ${resultHelp}" + } + + def 'should get a detailed help message - not nested' () { + given: + def validationConfig = [ + monochromeLogs: true, + parametersSchema: 'src/testResources/nextflow_schema.json', + help: [ + enabled: true + ] + ] + def params = [:] + def config = new ValidationConfig(validationConfig, params) + def helpMessage = new HelpMessage(config, session) + + when: + def help = helpMessage.getShortHelpMessage("input") + + then: + noExceptionThrown() + def expectedHelp = """--input + type : string + format : file-path + mimetype : text/csv + pattern : ^\\S+\\.(csv|tsv|yaml|json)\$ + description: Path to comma-separated file containing information about the samples in the experiment. + help_text : You will need to create a design file with information about the samples in your experiment before +running the pipeline. Use this parameter to specify its location. It has to be a comma-separated +file with 3 columns, and a header row. See [usage +docs](https://nf-co.re/testpipeline/usage#samplesheet-input). + +""" + def resultHelp = help.readLines() + expectedHelp.readLines().each { + assert help.contains(it) + resultHelp.removeElement(it) + } + assert resultHelp.size() == 0, "Found extra unexpected lines: ${resultHelp}" + } + + def 'should get a before text - with command' () { + given: + def validationConfig = [ + monochromeLogs: true, + parametersSchema: 'src/testResources/nextflow_schema.json', + help: [ + enabled: true, + command: "nextflow run test/test --profile test,docker --outdir results", + beforeText: """Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas condimentum ligula ac metus sollicitudin rutrum. Vestibulum a lectus ipsum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Cras consequat placerat aliquet. Maecenas et vulputate nibh. Donec luctus, purus ut scelerisque ornare, sem nisl mollis libero, non faucibus nibh nunc ac nulla. Donec et pharetra neque. Etiam id nibh vel turpis ornare efficitur. Cras eu eros mi. +Etiam at nulla ac dui ullamcorper viverra. Donec posuere imperdiet eros nec consequat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nullam nec aliquam magna. Quisque nec dapibus velit, id convallis justo. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Suspendisse bibendum ipsum quis nulla fringilla laoreet. Integer dictum, purus et pretium ultrices, nunc nisl vestibulum erat, et tempus ex massa eget nunc. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer varius aliquam vestibulum. Proin sit amet lobortis ipsum. Vestibulum fermentum lorem ac erat pharetra, eu eleifend sapien hendrerit. Quisque id varius ex. Morbi et dui et libero varius tempus. Ut eu sagittis lorem, sed congue libero. +""" + ] + ] + def params = [:] + def config = new ValidationConfig(validationConfig, params) + def helpMessage = new HelpMessage(config, session) + + when: + def help = helpMessage.getBeforeText() + + then: + noExceptionThrown() + def expectedHelp = """Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas condimentum ligula ac metus sollicitudin rutrum. Vestibulum a lectus ipsum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Cras consequat placerat aliquet. Maecenas et vulputate nibh. Donec luctus, purus ut scelerisque ornare, sem nisl mollis libero, non faucibus nibh nunc ac nulla. Donec et pharetra neque. Etiam id nibh vel turpis ornare efficitur. Cras eu eros mi. +Etiam at nulla ac dui ullamcorper viverra. Donec posuere imperdiet eros nec consequat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nullam nec aliquam magna. Quisque nec dapibus velit, id convallis justo. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Suspendisse bibendum ipsum quis nulla fringilla laoreet. Integer dictum, purus et pretium ultrices, nunc nisl vestibulum erat, et tempus ex massa eget nunc. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer varius aliquam vestibulum. Proin sit amet lobortis ipsum. Vestibulum fermentum lorem ac erat pharetra, eu eleifend sapien hendrerit. Quisque id varius ex. Morbi et dui et libero varius tempus. Ut eu sagittis lorem, sed congue libero. +Typical pipeline command: + + nextflow run test/test --profile test,docker --outdir results + +""" + def resultHelp = help.readLines() + expectedHelp.readLines().each { + assert help.contains(it) + resultHelp.removeElement(it) + } + assert resultHelp.size() == 0, "Found extra unexpected lines: ${resultHelp}" + } + + def 'should get a before text - without command' () { + given: + def validationConfig = [ + monochromeLogs: true, + parametersSchema: 'src/testResources/nextflow_schema.json', + help: [ + enabled: true, + beforeText: """Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas condimentum ligula ac metus sollicitudin rutrum. Vestibulum a lectus ipsum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Cras consequat placerat aliquet. Maecenas et vulputate nibh. Donec luctus, purus ut scelerisque ornare, sem nisl mollis libero, non faucibus nibh nunc ac nulla. Donec et pharetra neque. Etiam id nibh vel turpis ornare efficitur. Cras eu eros mi. +Etiam at nulla ac dui ullamcorper viverra. Donec posuere imperdiet eros nec consequat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nullam nec aliquam magna. Quisque nec dapibus velit, id convallis justo. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Suspendisse bibendum ipsum quis nulla fringilla laoreet. Integer dictum, purus et pretium ultrices, nunc nisl vestibulum erat, et tempus ex massa eget nunc. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer varius aliquam vestibulum. Proin sit amet lobortis ipsum. Vestibulum fermentum lorem ac erat pharetra, eu eleifend sapien hendrerit. Quisque id varius ex. Morbi et dui et libero varius tempus. Ut eu sagittis lorem, sed congue libero. +""" + ] + ] + def params = [:] + def config = new ValidationConfig(validationConfig, params) + def helpMessage = new HelpMessage(config, session) + + when: + def help = helpMessage.getBeforeText() + + then: + noExceptionThrown() + def expectedHelp = """Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas condimentum ligula ac metus sollicitudin rutrum. Vestibulum a lectus ipsum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Cras consequat placerat aliquet. Maecenas et vulputate nibh. Donec luctus, purus ut scelerisque ornare, sem nisl mollis libero, non faucibus nibh nunc ac nulla. Donec et pharetra neque. Etiam id nibh vel turpis ornare efficitur. Cras eu eros mi. +Etiam at nulla ac dui ullamcorper viverra. Donec posuere imperdiet eros nec consequat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nullam nec aliquam magna. Quisque nec dapibus velit, id convallis justo. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Suspendisse bibendum ipsum quis nulla fringilla laoreet. Integer dictum, purus et pretium ultrices, nunc nisl vestibulum erat, et tempus ex massa eget nunc. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer varius aliquam vestibulum. Proin sit amet lobortis ipsum. Vestibulum fermentum lorem ac erat pharetra, eu eleifend sapien hendrerit. Quisque id varius ex. Morbi et dui et libero varius tempus. Ut eu sagittis lorem, sed congue libero. + +""" + def resultHelp = help.readLines() + expectedHelp.readLines().each { + assert help.contains(it) + resultHelp.removeElement(it) + } + assert resultHelp.size() == 0, "Found extra unexpected lines: ${resultHelp}" + } + + def 'should get an after text' () { + given: + def validationConfig = [ + monochromeLogs: true, + parametersSchema: 'src/testResources/nextflow_schema.json', + help: [ + enabled: true, + afterText: """Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas condimentum ligula ac metus sollicitudin rutrum. Vestibulum a lectus ipsum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Cras consequat placerat aliquet. Maecenas et vulputate nibh. Donec luctus, purus ut scelerisque ornare, sem nisl mollis libero, non faucibus nibh nunc ac nulla. Donec et pharetra neque. Etiam id nibh vel turpis ornare efficitur. Cras eu eros mi. +Etiam at nulla ac dui ullamcorper viverra. Donec posuere imperdiet eros nec consequat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nullam nec aliquam magna. Quisque nec dapibus velit, id convallis justo. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Suspendisse bibendum ipsum quis nulla fringilla laoreet. Integer dictum, purus et pretium ultrices, nunc nisl vestibulum erat, et tempus ex massa eget nunc. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer varius aliquam vestibulum. Proin sit amet lobortis ipsum. Vestibulum fermentum lorem ac erat pharetra, eu eleifend sapien hendrerit. Quisque id varius ex. Morbi et dui et libero varius tempus. Ut eu sagittis lorem, sed congue libero. +""" + ] + ] + def params = [:] + def config = new ValidationConfig(validationConfig, params) + def helpMessage = new HelpMessage(config, session) + + when: + def help = helpMessage.getAfterText() + + then: + noExceptionThrown() + def expectedHelp = """------------------------------------------------------ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas condimentum ligula ac metus sollicitudin rutrum. Vestibulum a lectus ipsum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Cras consequat placerat aliquet. Maecenas et vulputate nibh. Donec luctus, purus ut scelerisque ornare, sem nisl mollis libero, non faucibus nibh nunc ac nulla. Donec et pharetra neque. Etiam id nibh vel turpis ornare efficitur. Cras eu eros mi. +Etiam at nulla ac dui ullamcorper viverra. Donec posuere imperdiet eros nec consequat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nullam nec aliquam magna. Quisque nec dapibus velit, id convallis justo. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Suspendisse bibendum ipsum quis nulla fringilla laoreet. Integer dictum, purus et pretium ultrices, nunc nisl vestibulum erat, et tempus ex massa eget nunc. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer varius aliquam vestibulum. Proin sit amet lobortis ipsum. Vestibulum fermentum lorem ac erat pharetra, eu eleifend sapien hendrerit. Quisque id varius ex. Morbi et dui et libero varius tempus. Ut eu sagittis lorem, sed congue libero. + +""" + def resultHelp = help.readLines() + expectedHelp.readLines().each { + assert help.contains(it) + resultHelp.removeElement(it) + } + assert resultHelp.size() == 0, "Found extra unexpected lines: ${resultHelp}" + } + + def 'should get a short help message with after text' () { + given: + def validationConfig = [ + monochromeLogs: true, + parametersSchema: 'src/testResources/nextflow_schema.json', + help: [ + enabled: true + ] + ] + def params = [:] + def config = new ValidationConfig(validationConfig, params) + def helpMessage = new HelpMessage(config, session) + + when: + def help = helpMessage.getShortHelpMessage("") + helpMessage.getAfterText() + + then: + noExceptionThrown() + def expectedHelp = """--help [boolean, string] Show the help message for all top level parameters. When a parameter is given to `--help`, the full +help message of that parameter will be printed. +--helpFull [boolean] Show the help message for all non-hidden parameters. +--showHidden [boolean] Show all hidden parameters in the help message. This needs to be used in combination with `--help` +or `--helpFull`. + +Input/output options + --input [string] Path to comma-separated file containing information about the samples in the experiment. + --outdir [string] The output directory where the results will be saved. You have to use absolute paths to storage on +Cloud infrastructure. + --email [string] Email address for completion summary. + --multiqc_title [string] MultiQC report title. Printed as page header, used for filename if not otherwise specified. + +Reference genome options + --genome [string] Name of iGenomes reference. + --fasta [string] Path to FASTA genome file. + + !! Hiding 22 param(s), use the `--showHidden` parameter to show them !! +------------------------------------------------------ + +""" + def resultHelp = help.readLines() + expectedHelp.readLines().each { + assert help.contains(it) + resultHelp.removeElement(it) + } + assert resultHelp.size() == 0, "Found extra unexpected lines: ${resultHelp}" + } + +} \ No newline at end of file diff --git a/plugins/nf-schema/src/test/nextflow/validation/ParamsHelpTest.groovy b/plugins/nf-schema/src/test/nextflow/validation/ParamsHelpTest.groovy index 83812bfd..52c1b483 100644 --- a/plugins/nf-schema/src/test/nextflow/validation/ParamsHelpTest.groovy +++ b/plugins/nf-schema/src/test/nextflow/validation/ParamsHelpTest.groovy @@ -105,7 +105,9 @@ class ParamsHelpTest extends Dsl2Spec{ when: def config = ["validation": [ - "showHiddenParams": true + "help": [ + "showHidden": true + ] ]] def result = new MockScriptRunner(config).setScript(SCRIPT).execute() def stdout = capture diff --git a/plugins/nf-schema/src/test/nextflow/validation/SamplesheetConverterTest.groovy b/plugins/nf-schema/src/test/nextflow/validation/SamplesheetConverterTest.groovy index 2be61836..c2ed023f 100644 --- a/plugins/nf-schema/src/test/nextflow/validation/SamplesheetConverterTest.groovy +++ b/plugins/nf-schema/src/test/nextflow/validation/SamplesheetConverterTest.groovy @@ -355,7 +355,7 @@ class SamplesheetConverterTest extends Dsl2Spec{ .toString() .readLines() .collect { - it.split("nextflow.validation.SamplesheetConverter - ")[-1] + it.split("nextflow.validation.SamplesheetConverter -- ")[-1] } then: