670 lines
25 KiB
Kotlin
670 lines
25 KiB
Kotlin
package net.keinekekse.args.v1
|
|
|
|
import net.keinekekse.args.v1.AbstractCommandLineParser.Companion.NAME_PATTERN
|
|
import net.keinekekse.args.v1.AbstractCommandLineParser.Companion.SHORT_NAME_PATTERN
|
|
import net.keinekekse.util.ValueDelegate
|
|
import kotlin.system.exitProcess
|
|
|
|
class ArgumentParserException(message: String) : RuntimeException(message) {
|
|
companion object {
|
|
const val ERROR_MSG = "\n*** ERROR parsing arguments: %s\n"
|
|
fun optionExists(name: String) = ArgumentParserException("Option '$name' already exists.")
|
|
fun commandExists(name: String) =
|
|
ArgumentParserException("Command or alias '$name' already exists.")
|
|
|
|
fun unknownOption(name: String) = ArgumentParserException("Unknown option '$name'.")
|
|
fun unknownCommand(name: String) = ArgumentParserException("Unknown command '$name'.")
|
|
fun optionRequiresValue(name: String) =
|
|
ArgumentParserException("Option '$name' requires a value.")
|
|
|
|
fun optionNoValue(name: String) =
|
|
ArgumentParserException("Option '$name' does not accept a value.")
|
|
|
|
fun optionRequired(name: String) = ArgumentParserException("Option '$name' is required.")
|
|
fun commandRequired() = ArgumentParserException("At least on command is required.")
|
|
fun invalidLongValue(value: String) =
|
|
ArgumentParserException("Value '$value' is not a long integer.")
|
|
|
|
fun invalidShortName(name: String) =
|
|
ArgumentParserException("Option short name '$name' is invalid.")
|
|
|
|
fun invalidName(name: String) = ArgumentParserException("Option name '$name' is invalid.")
|
|
fun invalidCommand(name: String) =
|
|
ArgumentParserException("Command name '$name' is invalid.")
|
|
|
|
fun noMoreOptions() = ArgumentParserException("Options can't be added after parsing.")
|
|
fun noMoreParsing() = ArgumentParserException("Arguments have already been parsed.")
|
|
}
|
|
}
|
|
|
|
class ParameterValue : ValueDelegate<List<String>> {
|
|
override var value: List<String>? = null
|
|
}
|
|
|
|
class OptionValue<T> : ValueDelegate<T> {
|
|
override var value: T? = null
|
|
fun isEmpty() = value == null
|
|
}
|
|
|
|
data class OptionConfig(
|
|
val name: String,
|
|
val shortName: Char? = null,
|
|
val description: String,
|
|
val required: Boolean = false,
|
|
)
|
|
|
|
interface OptionConverter<T> {
|
|
fun convert(value: String): T
|
|
}
|
|
|
|
sealed class OptionType<T>(
|
|
val converter: OptionConverter<T>,
|
|
val hasParameter: Boolean = true,
|
|
) {
|
|
companion object {
|
|
private class StringConverter : OptionConverter<String> {
|
|
override fun convert(value: String) = value
|
|
}
|
|
|
|
private class LongConverter : OptionConverter<Long> {
|
|
override fun convert(value: String) = value.toLongOrNull()
|
|
?: throw ArgumentParserException.invalidLongValue(value)
|
|
}
|
|
|
|
private class BooleanConverter : OptionConverter<Boolean> {
|
|
override fun convert(value: String) = value == "true"
|
|
}
|
|
}
|
|
|
|
open fun createOption(config: OptionConfig): Option<T> = Option(config, this)
|
|
fun convertAndSet(value: String, opt: Option<T>) {
|
|
opt.setValue(converter.convert(value))
|
|
}
|
|
|
|
object STRING : OptionType<String>(StringConverter())
|
|
object LONG : OptionType<Long>(LongConverter())
|
|
object BOOLEAN : OptionType<Boolean>(BooleanConverter(), hasParameter = false) {
|
|
override fun createOption(config: OptionConfig): Option<Boolean> {
|
|
return super.createOption(config).apply { setValue(false) }
|
|
}
|
|
}
|
|
}
|
|
|
|
data class Option<T>(
|
|
val config: OptionConfig,
|
|
val type: OptionType<T>,
|
|
) {
|
|
val optionValue = OptionValue<T>()
|
|
|
|
fun setValue(value: T) {
|
|
optionValue.value = value
|
|
}
|
|
|
|
fun isEmpty() = optionValue.isEmpty()
|
|
}
|
|
|
|
interface AddOptions {
|
|
val options: List<Option<Any>>
|
|
fun stringOption(config: OptionConfig): OptionValue<String>
|
|
fun longOption(config: OptionConfig): OptionValue<Long>
|
|
fun boolOption(config: OptionConfig): OptionValue<Boolean>
|
|
fun <T> createOption(config: OptionConfig, type: OptionType<T>): OptionValue<T>
|
|
}
|
|
|
|
/**
|
|
* Represents a command in a command-line interface that can have options and aliases.
|
|
*
|
|
* A `Command` is identified by its name and can optionally have a description and alternative names (aliases).
|
|
* It also supports adding and managing options that customize its behavior.
|
|
*
|
|
* @property name The unique name of the command.
|
|
* @property description A brief description explaining the purpose of the command.
|
|
* @property aliases A list of alternative names (aliases) that can also invoke the command.
|
|
*
|
|
* @constructor Creates a new `Command` instance with the specified name, description, and optional aliases.
|
|
*/
|
|
class Command(
|
|
val name: String,
|
|
val description: String,
|
|
val aliases: List<String> = emptyList<String>(),
|
|
) : AddOptions {
|
|
override val options = mutableListOf<Option<Any>>()
|
|
var isSet = false
|
|
internal set
|
|
|
|
/**
|
|
* Adds a string-based option to the command configuration.
|
|
*
|
|
* This method creates a command-line option that accepts a string value, based on the provided
|
|
* configuration. The configuration includes details such as the option's name, description,
|
|
* and whether it is required.
|
|
*
|
|
* @param config the configuration object containing details about the string option to be created
|
|
* @return an `OptionValue` instance representing the value of the string option
|
|
*/
|
|
override fun stringOption(config: OptionConfig): OptionValue<String> =
|
|
createOption(config, OptionType.STRING)
|
|
|
|
/**
|
|
* Creates a command-line option that accepts a `Long` value based on the provided configuration.
|
|
*
|
|
* This method utilizes the specified configuration to define details such as the option's name,
|
|
* description, and whether it is required. It then generates an option that expects a `Long` value.
|
|
*
|
|
* @param config the configuration object containing details about the long option to be created
|
|
* @return an `OptionValue<Long>` instance representing the value of the long option
|
|
*/
|
|
override fun longOption(config: OptionConfig): OptionValue<Long> =
|
|
createOption(config, OptionType.LONG)
|
|
|
|
/**
|
|
* Creates a command-line option that accepts a Boolean value based on the provided configuration.
|
|
*
|
|
* This method uses the specified configuration to define details such as the option's name,
|
|
* description, and whether it is required. The resulting option accepts a Boolean value, with
|
|
* `false` being the default if the option is not explicitly provided in the command-line arguments.
|
|
*
|
|
* @param config the configuration object containing details about the Boolean option to be created
|
|
* @return an `OptionValue<Boolean>` instance representing the value of the Boolean option
|
|
*/
|
|
override fun boolOption(config: OptionConfig): OptionValue<Boolean> =
|
|
createOption(
|
|
config.copy(required = false),
|
|
OptionType.BOOLEAN
|
|
)
|
|
|
|
override fun <T> createOption(
|
|
config: OptionConfig,
|
|
type: OptionType<T>,
|
|
): OptionValue<T> {
|
|
DefaultOptionValidator.validateOption(config, options)
|
|
@Suppress("UNCHECKED_CAST")
|
|
return type.createOption(config).also { options.add(it as Option<Any>) }.optionValue
|
|
}
|
|
|
|
override operator fun equals(other: Any?): Boolean {
|
|
if (other is String) {
|
|
return name == other || aliases.contains(other)
|
|
}
|
|
return super.equals(other)
|
|
}
|
|
|
|
override fun hashCode(): Int {
|
|
var result = isSet.hashCode()
|
|
result = 31 * result + name.hashCode()
|
|
result = 31 * result + description.hashCode()
|
|
result = 31 * result + aliases.hashCode()
|
|
result = 31 * result + options.hashCode()
|
|
return result
|
|
}
|
|
|
|
}
|
|
|
|
class CommandList(commands: MutableList<Command> = mutableListOf<Command>()) :
|
|
MutableList<Command> by commands {
|
|
operator fun contains(name: String) = this.any { it.equals(name) }
|
|
operator fun get(name: String) =
|
|
this.find { it.equals(name) } ?: throw ArgumentParserException.unknownCommand(name)
|
|
}
|
|
|
|
/**
|
|
* Parses and handles command-line arguments for commands and their associated options.
|
|
*
|
|
* The `CommandCommandLineParser` is a specialized parser designed to handle top-level commands,
|
|
* each potentially with its own set of options and arguments. It extends `AbstractCommandLineParser`
|
|
* to incorporate functionality for parsing commands alongside their arguments and options.
|
|
*
|
|
* Example:
|
|
* ```
|
|
* val arguments = CommandCommandLineParser("some info text")
|
|
*
|
|
* val cmdImport = arguments.addCommand("import", "description", listOf("imp", "in"))
|
|
* val auto by cmdImport.boolOption(OptionConfig("auto",'a',"description",false))
|
|
*
|
|
* // more option & commands ...
|
|
*
|
|
* val files by arguments.parametersList()
|
|
* arguments.parse(args)
|
|
*
|
|
* when {
|
|
* cmdImport.isSet -> {
|
|
*
|
|
* ```
|
|
*
|
|
* @constructor accepts a help string that is used to display usage instructions, adding context
|
|
* for the users of the command-line tool.
|
|
* @param helpString A message string to display as guidance for the tool usage.
|
|
*/
|
|
class CommandCommandLineParser(private val helpString: String) :
|
|
AbstractCommandLineParser(helpString) {
|
|
private val commands = CommandList()
|
|
|
|
/**
|
|
* Holds the currently active command after the command-line arguments are parsed.
|
|
*
|
|
* This variable is updated when a valid command is identified and processed by the parser.
|
|
* If no valid command is found during parsing, it remains null.
|
|
* This property is read-only externally and can only be set internally by the parser.
|
|
*/
|
|
var command: Command? = null
|
|
private set
|
|
|
|
override val helpFormatter = CommandHelpFormatter(commands, helpString)
|
|
|
|
init {
|
|
addCommand("help", "Display this help message.")
|
|
}
|
|
|
|
/**
|
|
* Adds a new command with the specified name, description, and optional aliases.
|
|
*
|
|
* This method registers a command to the parser, allowing it to be identified by its name or aliases.
|
|
* Throws an exception if commands cannot be added or if the name or aliases conflict with existing commands.
|
|
*
|
|
* @param name The unique name of the command to be added.
|
|
* @param description A brief description of the command.
|
|
* @param aliases An optional list of alternative names (aliases) for the command. Defaults to an empty list.
|
|
* @return A `Command` instance that can be used to add option for this command.
|
|
*/
|
|
fun addCommand(name: String, description: String, aliases: List<String> = emptyList()) =
|
|
try {
|
|
if (isParsed) throw ArgumentParserException.noMoreOptions()
|
|
if (commands.contains(name) || aliases.any { commands.contains(it) })
|
|
throw ArgumentParserException.commandExists(name)
|
|
Command(name, description, aliases).also { commands.add(it) }
|
|
} catch (e: ArgumentParserException) {
|
|
handleParsingError(e)
|
|
}
|
|
|
|
/**
|
|
* Parses an array of command-line arguments and validates the command and its options.
|
|
*
|
|
* The method processes the first argument as the command and checks whether it is valid.
|
|
* If the command is recognized and exists within the predefined set of allowed commands,
|
|
* its associated options are then parsed. If an error occurs during parsing, appropriate
|
|
* error handling is invoked.
|
|
*
|
|
* @param args The array of command-line arguments to be parsed. The first element is treated
|
|
* as the primary command, and the rest are interpreted as its options.
|
|
*/
|
|
override fun parse(args: Array<String>) {
|
|
if (args.isEmpty()) {
|
|
handleParsingError(ArgumentParserException.commandRequired())
|
|
}
|
|
val cmd = args[0]
|
|
if (!argumentParser.parseCommand(cmd)) {
|
|
handleParsingError(ArgumentParserException.invalidCommand(cmd))
|
|
}
|
|
if (cmd !in commands) {
|
|
handleParsingError(ArgumentParserException.unknownCommand(cmd))
|
|
} else {
|
|
parseArguments(args.drop(1).toTypedArray(), commands[cmd].options)
|
|
commands[cmd].isSet = true
|
|
command = commands[cmd]
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* A command-line parser specifically designed to parse and manage options provided
|
|
* via command-line arguments. It extends the `AbstractCommandLineParser` and implements
|
|
* the `AddOptions` interface.
|
|
*
|
|
* This parser supports different types of options including string, long, and boolean,
|
|
* and ensures validation during the parsing process. It also auto-registers a help option
|
|
* to display the usage of the parser.
|
|
*
|
|
* Example:
|
|
* ```
|
|
* val arguments = OptionCommandLineParser("some info text")
|
|
* val author by arguments.stringOption(OptionConfig("author", 'a', "some author", true))
|
|
*
|
|
* // more options ...
|
|
*
|
|
* val files by arguments.parametersList()
|
|
* arguments.parse(args)
|
|
*
|
|
* if (author != null) {
|
|
*```
|
|
*
|
|
* @constructor Creates an `OptionCommandLineParser` with a custom help string.
|
|
* @param helpString Descriptive text that is displayed when the help option is invoked.
|
|
*/
|
|
class OptionCommandLineParser(private val helpString: String) :
|
|
AbstractCommandLineParser(helpString), AddOptions {
|
|
override val options = mutableListOf<Option<Any>>()
|
|
|
|
override val helpFormatter = OptionHelpFormatter(options, helpString)
|
|
|
|
init {
|
|
boolOption(OptionConfig("help", 'h', "Display this help message"))
|
|
}
|
|
|
|
/**
|
|
* Creates and registers a string-based command-line option based on the provided configuration.
|
|
*
|
|
* @param config The configuration object containing the name, short name, description,
|
|
* and requirements for the string option.
|
|
* @return An instance of OptionValue that delegates access to the parsed value of the option.
|
|
*/
|
|
override fun stringOption(config: OptionConfig): OptionValue<String> =
|
|
createOption(config, OptionType.STRING)
|
|
|
|
/**
|
|
* Creates and registers a command-line option that parses and returns a `Long` value.
|
|
*
|
|
* @param config The configuration object containing the name, short name, description,
|
|
* and requirements for the long option.
|
|
* @return An instance of `OptionValue<Long>` that provides access to the parsed value
|
|
* of the long option.
|
|
*/
|
|
override fun longOption(config: OptionConfig): OptionValue<Long> =
|
|
createOption(config, OptionType.LONG)
|
|
|
|
/**
|
|
* Creates and registers a command-line option that parses and returns a `Boolean` value.
|
|
*
|
|
* @param config The configuration object containing the name, short name, description,
|
|
* and requirements for the boolean option.
|
|
* @return An instance of `OptionValue<Boolean>` that provides access to the parsed value
|
|
* of the boolean option.
|
|
*/
|
|
override fun boolOption(config: OptionConfig): OptionValue<Boolean> =
|
|
createOption(
|
|
config.copy(required = false),
|
|
OptionType.BOOLEAN
|
|
)
|
|
|
|
override fun <T> createOption(
|
|
config: OptionConfig,
|
|
type: OptionType<T>,
|
|
): OptionValue<T> {
|
|
try {
|
|
if (isParsed) throw ArgumentParserException.noMoreOptions()
|
|
DefaultOptionValidator.validateOption(config, options)
|
|
@Suppress("UNCHECKED_CAST")
|
|
return type.createOption(config).also { options.add(it as Option<Any>) }.optionValue
|
|
} catch (e: ArgumentParserException) {
|
|
handleParsingError(e)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parses the provided array of command-line arguments.
|
|
*
|
|
* This method processes the input arguments, identifies the options and parameters
|
|
* based on the argument parser's configuration, and validates the required options.
|
|
* If the arguments have already been parsed, it raises an exception.
|
|
* In case of errors during parsing or validation, an error message is displayed
|
|
* and the help information is shown.
|
|
*
|
|
* @param args The array of command-line arguments to be parsed.
|
|
*/
|
|
override fun parse(args: Array<String>) {
|
|
parseArguments(args, options)
|
|
}
|
|
|
|
}
|
|
|
|
abstract class AbstractCommandLineParser(private val helpString: String) {
|
|
|
|
private val parameters = ParameterValue()
|
|
protected var isParsed = false
|
|
protected val argumentParser: ArgumentParser = DefaultArgumentParser()
|
|
protected abstract val helpFormatter: HelpFormatter
|
|
|
|
companion object {
|
|
const val NAME = """[a-zA-Z][\w-]+"""
|
|
const val SHORT_NAME = """[a-zA-Z]"""
|
|
val ARGUMENT_PATTERN = """^(?:-(${SHORT_NAME})|--(${NAME}))(?:=(.+)|$)$""".toRegex()
|
|
val NAME_PATTERN = """^${NAME}$""".toRegex()
|
|
val SHORT_NAME_PATTERN = """^${SHORT_NAME}$""".toRegex()
|
|
}
|
|
|
|
/**
|
|
* Returns the list of parameters provided to the argument parser.
|
|
*
|
|
* The parameters are parsed from the command-line input following the provided argument options.
|
|
* This method allows accessing any additional parameters that are not directly associated with
|
|
* the defined options.
|
|
*
|
|
* @return A list of strings representing the positional parameters passed after all options.
|
|
*/
|
|
fun parametersList() = parameters
|
|
|
|
abstract fun parse(args: Array<String>)
|
|
|
|
protected fun parseArguments(args: Array<String>, options: MutableList<Option<Any>>) {
|
|
if (isParsed) throw ArgumentParserException.noMoreParsing()
|
|
try {
|
|
argumentParser.parseArguments(args, options, parameters)
|
|
isParsed = true
|
|
DefaultOptionValidator.validateRequiredOptions(options)
|
|
handleHelpOption(options)
|
|
} catch (e: ArgumentParserException) {
|
|
handleParsingError(e)
|
|
}
|
|
}
|
|
|
|
fun showHelp(errorMessage: String = ""): Nothing {
|
|
helpFormatter.show()
|
|
if (errorMessage.isNotEmpty()) {
|
|
println()
|
|
println(errorMessage)
|
|
println()
|
|
}
|
|
exitProcess(0)
|
|
}
|
|
|
|
private fun handleHelpOption(options: MutableList<Option<Any>>) {
|
|
if (findOption("help", options)?.optionValue?.value == true) {
|
|
showHelp()
|
|
}
|
|
}
|
|
|
|
private fun findOption(name: String, options: MutableList<Option<Any>>): Option<Any>? =
|
|
if (name.length > 1) options.find { it.config.name == name }
|
|
else options.find { it.config.shortName == name[0] }
|
|
|
|
protected fun handleParsingError(e: ArgumentParserException): Nothing {
|
|
showHelp(ArgumentParserException.ERROR_MSG.format(e.message))
|
|
}
|
|
|
|
// argument parsing
|
|
|
|
interface ArgumentParser {
|
|
fun parseArguments(
|
|
args: Array<String>,
|
|
options: List<Option<Any>>,
|
|
parameters: ParameterValue,
|
|
)
|
|
|
|
fun parseCommand(cmd: String): Boolean
|
|
}
|
|
|
|
private class DefaultArgumentParser : ArgumentParser {
|
|
override fun parseArguments(
|
|
args: Array<String>,
|
|
options: List<Option<Any>>,
|
|
parameters: ParameterValue,
|
|
) {
|
|
var index = 0
|
|
parameters.value = emptyList()
|
|
while (index < args.size) {
|
|
val parseResult = parseArgument(args[index])
|
|
if (parseResult != null) {
|
|
val (key, value) = parseResult
|
|
index += handleOption(key, value, args, index, options)
|
|
index++
|
|
} else {
|
|
parameters.value = args.drop(index)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun parseCommand(cmd: String) =
|
|
NAME_PATTERN.matches(cmd)
|
|
|
|
private fun parseArgument(arg: String): Pair<String, String>? =
|
|
ARGUMENT_PATTERN.matchEntire(arg)?.groupValues?.let { Pair(it[1] + it[2], it[3]) }
|
|
|
|
private fun handleOption(
|
|
key: String,
|
|
value: String,
|
|
args: Array<String>,
|
|
index: Int,
|
|
options: List<Option<Any>>,
|
|
): Int {
|
|
val option = options.find {
|
|
it.config.name == key || it.config.shortName?.toString() == key
|
|
} ?: throw ArgumentParserException.unknownOption(key)
|
|
|
|
return when {
|
|
option.type.hasParameter -> handleParameterOption(option, value, args, index)
|
|
else -> handleFlagOption(option, value)
|
|
}
|
|
}
|
|
|
|
private fun handleParameterOption(
|
|
option: Option<Any>,
|
|
value: String,
|
|
args: Array<String>,
|
|
index: Int,
|
|
): Int = when {
|
|
value.isNotBlank() -> {
|
|
option.type.convertAndSet(value, option)
|
|
0
|
|
}
|
|
|
|
index < args.size - 1 && !ARGUMENT_PATTERN.matches(args[index + 1]) -> {
|
|
option.type.convertAndSet(args[index + 1], option)
|
|
1
|
|
}
|
|
|
|
else -> throw ArgumentParserException.optionRequiresValue(option.config.name)
|
|
}
|
|
|
|
private fun handleFlagOption(option: Option<Any>, value: String): Int {
|
|
if (value.isNotBlank()) {
|
|
throw ArgumentParserException.optionNoValue(option.config.name)
|
|
}
|
|
option.type.convertAndSet("true", option)
|
|
return 0
|
|
}
|
|
|
|
}
|
|
|
|
// help output
|
|
|
|
protected interface HelpFormatter {
|
|
fun show(indent: String = "")
|
|
}
|
|
|
|
protected class CommandHelpFormatter(
|
|
protected val commands: CommandList,
|
|
private val infoMessage: String,
|
|
) : HelpFormatter {
|
|
override fun show(indent: String) {
|
|
println()
|
|
println(infoMessage)
|
|
for (cmd in commands) {
|
|
val aliases = buildString {
|
|
if (cmd.aliases.isNotEmpty()) append(
|
|
"|'${
|
|
cmd.aliases.joinToString("'|'")
|
|
}'"
|
|
)
|
|
}
|
|
OptionHelpFormatter(
|
|
cmd.options,
|
|
"Command '${cmd.name}'${aliases}: ${cmd.description}"
|
|
).show(" ")
|
|
println()
|
|
}
|
|
}
|
|
}
|
|
|
|
protected class OptionHelpFormatter(
|
|
private val options: List<Option<Any>>,
|
|
private val infoMessage: String,
|
|
) : HelpFormatter {
|
|
override fun show(indent: String) {
|
|
println(infoMessage)
|
|
if (options.isNotEmpty()) {
|
|
if (indent.isEmpty()) println("Options:")
|
|
printOptions(indent)
|
|
}
|
|
}
|
|
|
|
private fun printOptions(indent: String) {
|
|
val maxNameLength = calculateMaxNameLength() + 4 + indent.length
|
|
options.forEach { printOption(it, maxNameLength, indent) }
|
|
}
|
|
|
|
private fun calculateMaxNameLength(): Int =
|
|
options.maxOfOrNull { opt ->
|
|
opt.config.name.length + 2 + (opt.config.shortName?.let { 4 } ?: 0)
|
|
} ?: 0
|
|
|
|
private fun printOption(option: Option<Any>, maxNameLength: Int, indent: String) {
|
|
val shortName = option.config.shortName?.let { "-$it, " } ?: ""
|
|
val optionText = StringBuilder()
|
|
.append(indent)
|
|
.append(shortName)
|
|
.append("--")
|
|
.append(option.config.name)
|
|
.padEnd(maxNameLength)
|
|
println("$optionText ${option.config.description}")
|
|
}
|
|
|
|
}
|
|
}
|
|
// option validation
|
|
|
|
private interface OptionValidator {
|
|
fun validateOption(config: OptionConfig, existingOptions: List<Option<Any>>)
|
|
fun validateRequiredOptions(options: List<Option<Any>>)
|
|
}
|
|
|
|
private object DefaultOptionValidator : OptionValidator {
|
|
override fun validateOption(config: OptionConfig, existingOptions: List<Option<Any>>) {
|
|
validateOptionName(config.name)
|
|
validateShortName(config.shortName)
|
|
validateOptionUniqueness(config, existingOptions)
|
|
}
|
|
|
|
override fun validateRequiredOptions(options: List<Option<Any>>) {
|
|
val emptyNames = options.filter { it.config.required && it.isEmpty() }
|
|
.joinToString(", ") { it.config.name }
|
|
if (emptyNames.isNotEmpty()) throw ArgumentParserException.optionRequired(emptyNames)
|
|
}
|
|
|
|
private fun validateOptionName(name: String) {
|
|
if (!name.matches(NAME_PATTERN)) {
|
|
throw ArgumentParserException.invalidName(name)
|
|
}
|
|
}
|
|
|
|
private fun validateShortName(shortName: Char?) {
|
|
if (shortName != null && !shortName.toString().matches(SHORT_NAME_PATTERN)) {
|
|
throw ArgumentParserException.invalidShortName(shortName.toString())
|
|
}
|
|
}
|
|
|
|
private fun validateOptionUniqueness(
|
|
config: OptionConfig,
|
|
existingOptions: List<Option<Any>>,
|
|
) {
|
|
val nameExists = existingOptions.any { it.config.name == config.name }
|
|
val shortNameExists = config.shortName?.let { shortName ->
|
|
existingOptions.any { it.config.shortName == shortName }
|
|
} ?: false
|
|
|
|
if (nameExists || shortNameExists) {
|
|
throw ArgumentParserException.optionExists(config.name)
|
|
}
|
|
}
|
|
}
|