mirror of
https://github.com/corda/corda.git
synced 2025-06-16 22:28:15 +00:00
CORDA-1833: Create a picocli base class (#3826)
* Add shell extensions to CLI utils class and move into its own module * Fix issue with completion script generation and slight refactor * Fix autocompletion for logging level * Delete uneeded comment * More tidying up * Make run function final * Fixed an issue with the program being run twice. * Address review comments
This commit is contained in:
@ -1,119 +0,0 @@
|
||||
package net.corda.node.utilities
|
||||
|
||||
import com.jcabi.manifests.Manifests
|
||||
import net.corda.core.internal.exists
|
||||
import net.corda.core.internal.isReadable
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import org.apache.logging.log4j.Level
|
||||
import picocli.CommandLine
|
||||
import picocli.CommandLine.*
|
||||
import java.nio.file.Path
|
||||
import kotlin.system.exitProcess
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Something heavily used in network services, I am not sure it's of much use in corda, but who knows. Definitely it was the key to making DevOps happy.
|
||||
* Add it as
|
||||
* `@CommandLine.Mixin
|
||||
* lateinit var configParser: ConfigFilePathArgsParser`
|
||||
*
|
||||
* in your command class and then validate()
|
||||
*/
|
||||
@Command(description = ["Parse configuration file. Checks if given configuration file exists"])
|
||||
class ConfigFilePathArgsParser : Validated {
|
||||
@Option(names = ["--config-file", "-f"], required = true, paramLabel = "FILE", description = ["The path to the config file"])
|
||||
lateinit var configFile: Path
|
||||
|
||||
override fun validator(): List<String> {
|
||||
val res = mutableListOf<String>()
|
||||
if(!configFile.exists()) res += "Config file ${configFile.toAbsolutePath().normalize()} does not exist!"
|
||||
if(!configFile.isReadable) res += "Config file ${configFile.toAbsolutePath().normalize()} is not readable"
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple version printing when command is called with --version or -V flag. Assuming that we reuse Corda-Release-Version and Corda-Revision
|
||||
* in the manifest file.
|
||||
*/
|
||||
class CordaVersionProvider : IVersionProvider {
|
||||
override fun getVersion(): Array<String> {
|
||||
return if (Manifests.exists("Corda-Release-Version") && Manifests.exists("Corda-Revision")) {
|
||||
arrayOf("Version: ${Manifests.read("Corda-Release-Version")}", "Revision: ${Manifests.read("Corda-Revision")}")
|
||||
} else {
|
||||
arrayOf("No version data is available in the MANIFEST file.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Usually when we have errors in some command line flags that are not handled by picocli (e.g. non existing file). Error is thrown
|
||||
* and no CommandLine help afterwards. This can be called from run() method.
|
||||
*/
|
||||
interface Validated {
|
||||
companion object {
|
||||
val logger = contextLogger()
|
||||
const val RED = "\u001B[31m"
|
||||
const val RESET = "\u001B[0m"
|
||||
}
|
||||
/**
|
||||
* Check that provided command line parameters are valid, e.g. check file existence. Return list of error strings.
|
||||
*/
|
||||
fun validator(): List<String>
|
||||
|
||||
/**
|
||||
* Function that provides nice error handing of command line validation.
|
||||
*/
|
||||
fun validate() {
|
||||
val errors = validator()
|
||||
if (errors.isNotEmpty()) {
|
||||
logger.error(RED + "Exceptions when parsing command line arguments:")
|
||||
logger.error(errors.joinToString("\n") + RESET)
|
||||
CommandLine(this).usage(System.err)
|
||||
exitProcess(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple base class for handling help, version, verbose and logging-level commands.
|
||||
* As versionProvider information from the MANIFEST file is used. It can be overwritten by custom version providers (see: Node)
|
||||
* Picocli will prioritise versionProvider from the `@Command` annotation on the subclass, see: https://picocli.info/#_reuse_combinations
|
||||
*/
|
||||
@Command(mixinStandardHelpOptions = true,
|
||||
versionProvider = CordaVersionProvider::class,
|
||||
sortOptions = false,
|
||||
synopsisHeading = "%n@|bold,underline Usage|@:%n%n",
|
||||
descriptionHeading = "%n@|bold,underline Description|@:%n%n",
|
||||
parameterListHeading = "%n@|bold,underline Parameters|@:%n%n",
|
||||
optionListHeading = "%n@|bold,underline Options|@:%n%n",
|
||||
commandListHeading = "%n@|bold,underline Commands|@:%n%n")
|
||||
abstract class ArgsParser {
|
||||
@Option(names = [ "-v", "--verbose" ], description = ["If set, prints logging to the console as well as to a file."])
|
||||
var verbose: Boolean = false
|
||||
|
||||
@Option(names = ["--logging-level"],
|
||||
// TODO For some reason I couldn't make picocli COMPLETION-CANDIDATES work
|
||||
description = ["Enable logging at this level and higher. Defaults to INFO. Possible values: OFF, INFO, WARN, TRACE, DEBUG, ERROR, FATAL, ALL"],
|
||||
converter = [LoggingLevelConverter::class])
|
||||
var loggingLevel: Level = Level.INFO
|
||||
|
||||
// This needs to be called before loggers (See: NodeStartup.kt:51 logger called by lazy, initLogging happens before).
|
||||
// Node's logging is more rich. In corda configurations two properties, defaultLoggingLevel and consoleLogLevel, are usually used.
|
||||
protected open fun initLogging() {
|
||||
val loggingLevel = loggingLevel.name().toLowerCase(Locale.ENGLISH)
|
||||
System.setProperty("defaultLogLevel", loggingLevel) // These properties are referenced from the XML config file.
|
||||
if (verbose) {
|
||||
System.setProperty("consoleLogLevel", loggingLevel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converter from String to log4j logging Level.
|
||||
*/
|
||||
class LoggingLevelConverter : ITypeConverter<Level> {
|
||||
override fun convert(value: String?): Level {
|
||||
return value?.let { Level.getLevel(it) } ?: throw TypeConversionException("Unknown option for --logging-level: $value")
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user