Updated kotlinx.cli to release version (#3762)
This commit is contained in:
@@ -41,11 +41,27 @@ internal class ArgumentsQueue(argumentsDescriptors: List<ArgDescriptor<*, *>>) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface of argument value.
|
||||
* A property delegate that provides access to the argument/option value.
|
||||
*/
|
||||
interface ArgumentValueDelegate<T> {
|
||||
/**
|
||||
* The value of an option or argument parsed from command line.
|
||||
*
|
||||
* Accessing this value before [ArgParser.parse] method is called will result in an exception.
|
||||
*
|
||||
* @see CLIEntity.value
|
||||
*/
|
||||
var value: T
|
||||
|
||||
/** Provides the value for the delegated property getter. Returns the [value] property.
|
||||
* @throws IllegalStateException in case of accessing the value before [ArgParser.parse] method is called.
|
||||
*/
|
||||
operator fun getValue(thisRef: Any?, property: KProperty<*>): T = value
|
||||
|
||||
/** Sets the [value] to the [ArgumentValueDelegate.value] property from the delegated property setter.
|
||||
* This operation is possible only after command line arguments were parsed with [ArgParser.parse]
|
||||
* @throws IllegalStateException in case of resetting value before command line arguments are parsed.
|
||||
*/
|
||||
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
||||
this.value = value
|
||||
}
|
||||
@@ -54,7 +70,6 @@ interface ArgumentValueDelegate<T> {
|
||||
/**
|
||||
* Abstract base class for subcommands.
|
||||
*/
|
||||
@SinceKotlin("1.3")
|
||||
@ExperimentalCli
|
||||
abstract class Subcommand(val name: String): ArgParser(name) {
|
||||
/**
|
||||
@@ -74,14 +89,18 @@ class ArgParserResult(val commandName: String)
|
||||
/**
|
||||
* Arguments parser.
|
||||
*
|
||||
* @property programName name of current program.
|
||||
* @property useDefaultHelpShortName add or not -h flag for help message.
|
||||
* @property prefixStyle style of expected options prefix.
|
||||
* @property skipExtraArguments just skip extra arguments in command line string without producing error message.
|
||||
* @property programName the name of the current program.
|
||||
* @property useDefaultHelpShortName specifies whether to register "-h" option for printing the usage information.
|
||||
* @property prefixStyle the style of option prefixing.
|
||||
* @property skipExtraArguments specifies whether the extra unmatched arguments in a command line string
|
||||
* can be skipped without producing an error message.
|
||||
*/
|
||||
open class ArgParser(val programName: String, var useDefaultHelpShortName: Boolean = true,
|
||||
var prefixStyle: OPTION_PREFIX_STYLE = OPTION_PREFIX_STYLE.LINUX,
|
||||
var skipExtraArguments: Boolean = false) {
|
||||
open class ArgParser(
|
||||
val programName: String,
|
||||
var useDefaultHelpShortName: Boolean = true,
|
||||
var prefixStyle: OptionPrefixStyle = OptionPrefixStyle.LINUX,
|
||||
var skipExtraArguments: Boolean = false
|
||||
) {
|
||||
|
||||
/**
|
||||
* Map of options: key - full name of option, value - pair of descriptor and parsed values.
|
||||
@@ -102,6 +121,11 @@ open class ArgParser(val programName: String, var useDefaultHelpShortName: Boole
|
||||
*/
|
||||
private val declaredArguments = mutableListOf<CLIEntityWrapper>()
|
||||
|
||||
/**
|
||||
* State of parser. Stores last parsing result or null.
|
||||
*/
|
||||
private var parsingState: ArgParserResult? = null
|
||||
|
||||
/**
|
||||
* Map of subcommands.
|
||||
*/
|
||||
@@ -116,7 +140,7 @@ open class ArgParser(val programName: String, var useDefaultHelpShortName: Boole
|
||||
/**
|
||||
* Used prefix form for full option form.
|
||||
*/
|
||||
private val optionFullFormPrefix = if (prefixStyle == OPTION_PREFIX_STYLE.LINUX) "--" else "-"
|
||||
private val optionFullFormPrefix = if (prefixStyle == OptionPrefixStyle.LINUX) "--" else "-"
|
||||
|
||||
/**
|
||||
* Used prefix form for short option form.
|
||||
@@ -129,39 +153,66 @@ open class ArgParser(val programName: String, var useDefaultHelpShortName: Boole
|
||||
protected val fullCommandName = mutableListOf(programName)
|
||||
|
||||
/**
|
||||
* Origin of option/argument value.
|
||||
*
|
||||
* Possible values:
|
||||
* SET_BY_USER - value of option was provided in command line string;
|
||||
* SET_DEFAULT_VALUE - value of option wasn't provided in command line, but set using default value;
|
||||
* UNSET - value of option is unset
|
||||
* REDEFINED - value of option was redefined in source code after parsing.
|
||||
* The way an option/argument has got its value.
|
||||
*/
|
||||
enum class ValueOrigin { SET_BY_USER, SET_DEFAULT_VALUE, UNSET, REDEFINED }
|
||||
enum class ValueOrigin {
|
||||
/* The value was parsed from command line arguments. */
|
||||
SET_BY_USER,
|
||||
/* The value was missing in command line, therefore the default value was used. */
|
||||
SET_DEFAULT_VALUE,
|
||||
/* The value is not initialized by command line values or by default values. */
|
||||
UNSET,
|
||||
/* The value was redefined after parsing manually (usually with the property setter). */
|
||||
REDEFINED,
|
||||
/* The value is undefined, because parsing wasn't called. */
|
||||
UNDEFINED
|
||||
}
|
||||
|
||||
/**
|
||||
* Options prefix style.
|
||||
*
|
||||
* Possible values:
|
||||
* LINUX - Linux style, for full forms of options "--", for short form - "-"
|
||||
* JVM - JVM style, both for full and short forms of options "-"
|
||||
* The style of option prefixing.
|
||||
*/
|
||||
enum class OPTION_PREFIX_STYLE { LINUX, JVM }
|
||||
enum class OptionPrefixStyle {
|
||||
/* Linux style: the full name of an option is prefixed with two hyphens "--" and the short name — with one "-". */
|
||||
LINUX,
|
||||
/* JVM style: both full and short names are prefixed with one hyphen "-". */
|
||||
JVM,
|
||||
}
|
||||
|
||||
@Deprecated("OPTION_PREFIX_STYLE is deprecated. Please, use OptionPrefixStyle.",
|
||||
ReplaceWith("OptionPrefixStyle", "kotlinx.cli.OptionPrefixStyle"))
|
||||
@Suppress("TOPLEVEL_TYPEALIASES_ONLY")
|
||||
typealias OPTION_PREFIX_STYLE = OptionPrefixStyle
|
||||
|
||||
/**
|
||||
* Add option with single possible value and get delegator to its value.
|
||||
* Declares a named option and returns an object which can be used to access the option value
|
||||
* after all arguments are parsed or to delegate a property for accessing the option value to.
|
||||
*
|
||||
* @param type argument type, one of [ArgType].
|
||||
* @param fullName argument full name.
|
||||
* @param shortName option short name.
|
||||
* @param description text description of Argument.
|
||||
* @param deprecatedWarning text message with information in case if option is deprecated.
|
||||
* By default, the option supports only a single value, is optional, and has no default value,
|
||||
* therefore its value's type is `T?`.
|
||||
*
|
||||
* You can alter the option properties by chaining extensions for the option type on the returned object:
|
||||
* - [AbstractSingleOption.default] to provide a default value that is used when the option is not specified;
|
||||
* - [SingleNullableOption.required] to make the option non-optional;
|
||||
* - [AbstractSingleOption.delimiter] to allow specifying multiple values in one command line argument with a delimiter;
|
||||
* - [AbstractSingleOption.multiple] to allow specifying the option several times.
|
||||
*
|
||||
* @param type The type describing how to parse an option value from a string,
|
||||
* an instance of [ArgType], e.g. [ArgType.String] or [ArgType.Choice].
|
||||
* @param fullName the full name of the option, can be omitted if the option name is inferred
|
||||
* from the name of a property delegated to this option.
|
||||
* @param shortName the short name of the option, `null` if the option cannot be specified in a short form.
|
||||
* @param description the description of the option used when rendering the usage information.
|
||||
* @param deprecatedWarning the deprecation message for the option.
|
||||
* Specifying anything except `null` makes this option deprecated. The message is rendered in a help message and
|
||||
* issued as a warning when the option is encountered when parsing command line arguments.
|
||||
*/
|
||||
fun <T : Any>option(type: ArgType<T>,
|
||||
fullName: String? = null,
|
||||
shortName: String ? = null,
|
||||
description: String? = null,
|
||||
deprecatedWarning: String? = null): SingleNullableOption<T> {
|
||||
fun <T : Any> option(
|
||||
type: ArgType<T>,
|
||||
fullName: String? = null,
|
||||
shortName: String ? = null,
|
||||
description: String? = null,
|
||||
deprecatedWarning: String? = null
|
||||
): SingleNullableOption<T> {
|
||||
val option = SingleNullableOption(OptionDescriptor(optionFullFormPrefix, optionShortFromPrefix, type,
|
||||
fullName, shortName, description, deprecatedWarning = deprecatedWarning), CLIEntityWrapper())
|
||||
option.owner.entity = option
|
||||
@@ -176,38 +227,55 @@ open class ArgParser(val programName: String, var useDefaultHelpShortName: Boole
|
||||
private fun inspectRequiredAndDefaultUsage() {
|
||||
var previousArgument: ParsingValue<*, *>? = null
|
||||
arguments.forEach { (_, currentArgument) ->
|
||||
previousArgument?.let {
|
||||
previousArgument?.let { previous ->
|
||||
// Previous argument has default value.
|
||||
it.descriptor.defaultValue?.let {
|
||||
if (currentArgument.descriptor.defaultValue == null && currentArgument.descriptor.required) {
|
||||
printError("Default value of argument ${previousArgument.descriptor.fullName} will be unused, " +
|
||||
if (previous.descriptor.defaultValueSet) {
|
||||
if (!currentArgument.descriptor.defaultValueSet && currentArgument.descriptor.required) {
|
||||
error("Default value of argument ${previous.descriptor.fullName} will be unused, " +
|
||||
"because next argument ${currentArgument.descriptor.fullName} is always required and has no default value.")
|
||||
}
|
||||
}
|
||||
// Previous argument is optional.
|
||||
if (!it.descriptor.required) {
|
||||
if (currentArgument.descriptor.defaultValue == null && currentArgument.descriptor.required) {
|
||||
printError("Argument ${previousArgument.descriptor.fullName} will be always required, " +
|
||||
if (!previous.descriptor.required) {
|
||||
if (!currentArgument.descriptor.defaultValueSet && currentArgument.descriptor.required) {
|
||||
error("Argument ${previous.descriptor.fullName} will be always required, " +
|
||||
"because next argument ${currentArgument.descriptor.fullName} is always required.")
|
||||
}
|
||||
}
|
||||
}
|
||||
previousArgument = currentArgument
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add argument with single nullable value and get delegator to its value.
|
||||
* Declares an argument and returns an object which can be used to access the argument value
|
||||
* after all arguments are parsed or to delegate a property for accessing the argument value to.
|
||||
*
|
||||
* @param type argument type, one of [ArgType].
|
||||
* @param fullName argument full name.
|
||||
* @param description text description of argument.
|
||||
* @param deprecatedWarning text message with information in case if argument is deprecated.
|
||||
* By default, the argument supports only a single value, is required, and has no default value,
|
||||
* therefore its value's type is `T`.
|
||||
*
|
||||
* You can alter the argument properties by chaining extensions for the argument type on the returned object:
|
||||
* - [AbstractSingleArgument.default] to provide a default value that is used when the argument is not specified;
|
||||
* - [SingleArgument.optional] to allow omitting the argument;
|
||||
* - [AbstractSingleArgument.multiple] to require the argument to have exactly the number of values specified;
|
||||
* - [AbstractSingleArgument.vararg] to allow specifying an unlimited number of values for the _last_ argument.
|
||||
*
|
||||
* @param type The type describing how to parse an option value from a string,
|
||||
* an instance of [ArgType], e.g. [ArgType.String] or [ArgType.Choice].
|
||||
* @param fullName the full name of the argument, can be omitted if the argument name is inferred
|
||||
* from the name of a property delegated to this argument.
|
||||
* @param description the description of the argument used when rendering the usage information.
|
||||
* @param deprecatedWarning the deprecation message for the argument.
|
||||
* Specifying anything except `null` makes this argument deprecated. The message is rendered in a help message and
|
||||
* issued as a warning when the argument is encountered when parsing command line arguments.
|
||||
*/
|
||||
fun <T : Any>argument(type: ArgType<T>,
|
||||
fullName: String? = null,
|
||||
description: String? = null,
|
||||
deprecatedWarning: String? = null) : SingleArgument<T> {
|
||||
val argument = SingleArgument(ArgDescriptor(type, fullName, 1,
|
||||
fun <T : Any> argument(
|
||||
type: ArgType<T>,
|
||||
fullName: String? = null,
|
||||
description: String? = null,
|
||||
deprecatedWarning: String? = null
|
||||
) : SingleArgument<T, DefaultRequiredType.Required> {
|
||||
val argument = SingleArgument<T, DefaultRequiredType.Required>(ArgDescriptor(type, fullName, 1,
|
||||
description, deprecatedWarning = deprecatedWarning), CLIEntityWrapper())
|
||||
argument.owner.entity = argument
|
||||
declaredArguments.add(argument.owner)
|
||||
@@ -215,16 +283,15 @@ open class ArgParser(val programName: String, var useDefaultHelpShortName: Boole
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subcommands.
|
||||
* Registers one or more subcommands.
|
||||
*
|
||||
* @param subcommandsList subcommands to add.
|
||||
*/
|
||||
@SinceKotlin("1.3")
|
||||
@ExperimentalCli
|
||||
fun subcommands(vararg subcommandsList: Subcommand) {
|
||||
subcommandsList.forEach {
|
||||
if (it.name in subcommands) {
|
||||
printError("Subcommand with name ${it.name} was already defined.")
|
||||
error("Subcommand with name ${it.name} was already defined.")
|
||||
}
|
||||
|
||||
// Set same settings as main parser.
|
||||
@@ -238,11 +305,11 @@ open class ArgParser(val programName: String, var useDefaultHelpShortName: Boole
|
||||
}
|
||||
|
||||
/**
|
||||
* Output error. Also adds help usage information for easy understanding of problem.
|
||||
* Outputs an error message adding the usage information after it.
|
||||
*
|
||||
* @param message error message.
|
||||
*/
|
||||
fun printError(message: String): Nothing {
|
||||
fun printError(message: String): Nothing {
|
||||
error("$message\n${makeUsage()}")
|
||||
}
|
||||
|
||||
@@ -292,21 +359,30 @@ open class ArgParser(val programName: String, var useDefaultHelpShortName: Boole
|
||||
else null
|
||||
|
||||
/**
|
||||
* Parse arguments.
|
||||
* Parses the provided array of command line arguments.
|
||||
* After a successful parsing, the options and arguments declared in this parser get their values and can be accessed
|
||||
* with the properties delegated to them.
|
||||
*
|
||||
* @param args array with command line arguments.
|
||||
* @param args the array with command line arguments.
|
||||
*
|
||||
* @return true if all arguments were parsed successfully, otherwise return false and print help message.
|
||||
* @return an [ArgParserResult] if all arguments were parsed successfully.
|
||||
* Otherwise, prints the usage information and terminates the program execution.
|
||||
* @throws IllegalStateException in case of attempt of calling parsing several times.
|
||||
*/
|
||||
fun parse(args: Array<String>) = parse(args.asList())
|
||||
fun parse(args: Array<String>): ArgParserResult = parse(args.asList())
|
||||
|
||||
protected fun parse(args: List<String>): ArgParserResult {
|
||||
check(parsingState == null) { "Parsing of command line options can be called only once." }
|
||||
// Add help option.
|
||||
val helpDescriptor = if (useDefaultHelpShortName) OptionDescriptor<Boolean, Boolean>(optionFullFormPrefix,
|
||||
optionShortFromPrefix, ArgType.Boolean,
|
||||
"help", "h", "Usage info")
|
||||
else OptionDescriptor(optionFullFormPrefix, optionShortFromPrefix,
|
||||
ArgType.Boolean, "help", description = "Usage info")
|
||||
val helpDescriptor = if (useDefaultHelpShortName) OptionDescriptor<Boolean, Boolean>(
|
||||
optionFullFormPrefix,
|
||||
optionShortFromPrefix, ArgType.Boolean,
|
||||
"help", "h", "Usage info"
|
||||
)
|
||||
else OptionDescriptor(
|
||||
optionFullFormPrefix, optionShortFromPrefix,
|
||||
ArgType.Boolean, "help", description = "Usage info"
|
||||
)
|
||||
val helpOption = SingleNullableOption(helpDescriptor, CLIEntityWrapper())
|
||||
helpOption.owner.entity = helpOption
|
||||
declaredOptions.add(helpOption.owner)
|
||||
@@ -316,6 +392,10 @@ open class ArgParser(val programName: String, var useDefaultHelpShortName: Boole
|
||||
argument(ArgType.String, "").vararg()
|
||||
}
|
||||
|
||||
// Clean options and arguments maps.
|
||||
options.clear()
|
||||
arguments.clear()
|
||||
|
||||
// Map declared options and arguments to maps.
|
||||
declaredOptions.forEachIndexed { index, option ->
|
||||
val value = option.entity?.delegate as ParsingValue<*, *>
|
||||
@@ -350,6 +430,12 @@ open class ArgParser(val programName: String, var useDefaultHelpShortName: Boole
|
||||
// Make inspections for arguments.
|
||||
inspectRequiredAndDefaultUsage()
|
||||
|
||||
listOf(arguments, options).forEach {
|
||||
it.forEach { (_, value) ->
|
||||
value.valueOrigin = ValueOrigin.UNSET
|
||||
}
|
||||
}
|
||||
|
||||
val argumentsQueue = ArgumentsQueue(arguments.map { it.value.descriptor as ArgDescriptor<*, *> })
|
||||
|
||||
var index = 0
|
||||
@@ -363,8 +449,9 @@ open class ArgParser(val programName: String, var useDefaultHelpShortName: Boole
|
||||
// Use parser for this subcommand.
|
||||
subcommand.parse(args.slice(index + 1..args.size - 1))
|
||||
subcommand.execute()
|
||||
parsingState = ArgParserResult(name)
|
||||
|
||||
return ArgParserResult(name)
|
||||
return parsingState!!
|
||||
}
|
||||
}
|
||||
// Parse arguments from command line.
|
||||
@@ -409,16 +496,19 @@ open class ArgParser(val programName: String, var useDefaultHelpShortName: Boole
|
||||
if (value.isEmpty()) {
|
||||
value.addDefaultValue()
|
||||
}
|
||||
if (value.valueOrigin != ValueOrigin.SET_BY_USER && value.descriptor.required) {
|
||||
printError("Value for ${value.descriptor.textDescription} should be always provided in command line.")
|
||||
}
|
||||
}
|
||||
} catch (exception: ParsingException) {
|
||||
printError(exception.message!!)
|
||||
}
|
||||
|
||||
return ArgParserResult(programName)
|
||||
parsingState = ArgParserResult(programName)
|
||||
return parsingState!!
|
||||
}
|
||||
|
||||
/**
|
||||
* Create message with usage description.
|
||||
* Creates a message with the usage information.
|
||||
*/
|
||||
internal fun makeUsage(): String {
|
||||
val result = StringBuilder()
|
||||
|
||||
@@ -23,7 +23,7 @@ abstract class ArgType<T : Any>(val hasParameter: kotlin.Boolean) {
|
||||
*
|
||||
* @param value value
|
||||
*/
|
||||
abstract val conversion: (value: kotlin.String, name: kotlin.String)->T
|
||||
abstract fun convert(value: kotlin.String, name: kotlin.String): T
|
||||
|
||||
/**
|
||||
* Argument type for flags that can be only set/unset.
|
||||
@@ -32,8 +32,8 @@ abstract class ArgType<T : Any>(val hasParameter: kotlin.Boolean) {
|
||||
override val description: kotlin.String
|
||||
get() = ""
|
||||
|
||||
override val conversion: (value: kotlin.String, name: kotlin.String) -> kotlin.Boolean
|
||||
get() = { value, _ -> if (value == "false") false else true }
|
||||
override fun convert(value: kotlin.String, name: kotlin.String): kotlin.Boolean =
|
||||
value != "false"
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,8 +43,7 @@ abstract class ArgType<T : Any>(val hasParameter: kotlin.Boolean) {
|
||||
override val description: kotlin.String
|
||||
get() = "{ String }"
|
||||
|
||||
override val conversion: (value: kotlin.String, name: kotlin.String) -> kotlin.String
|
||||
get() = { value, _ -> value }
|
||||
override fun convert(value: kotlin.String, name: kotlin.String): kotlin.String = value
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,9 +53,9 @@ abstract class ArgType<T : Any>(val hasParameter: kotlin.Boolean) {
|
||||
override val description: kotlin.String
|
||||
get() = "{ Int }"
|
||||
|
||||
override val conversion: (value: kotlin.String, name: kotlin.String) -> kotlin.Int
|
||||
get() = { value, name -> value.toIntOrNull()
|
||||
?: throw ParsingException("Option $name is expected to be integer number. $value is provided.") }
|
||||
override fun convert(value: kotlin.String, name: kotlin.String): kotlin.Int =
|
||||
value.toIntOrNull()
|
||||
?: throw ParsingException("Option $name is expected to be integer number. $value is provided.")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,9 +65,9 @@ abstract class ArgType<T : Any>(val hasParameter: kotlin.Boolean) {
|
||||
override val description: kotlin.String
|
||||
get() = "{ Double }"
|
||||
|
||||
override val conversion: (value: kotlin.String, name: kotlin.String) -> kotlin.Double
|
||||
get() = { value, name -> value.toDoubleOrNull()
|
||||
?: throw ParsingException("Option $name is expected to be double number. $value is provided.") }
|
||||
override fun convert(value: kotlin.String, name: kotlin.String): kotlin.Double =
|
||||
value.toDoubleOrNull()
|
||||
?: throw ParsingException("Option $name is expected to be double number. $value is provided.")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,9 +77,9 @@ abstract class ArgType<T : Any>(val hasParameter: kotlin.Boolean) {
|
||||
override val description: kotlin.String
|
||||
get() = "{ Value should be one of $values }"
|
||||
|
||||
override val conversion: (value: kotlin.String, name: kotlin.String) -> kotlin.String
|
||||
get() = { value, name -> if (value in values) value
|
||||
else throw ParsingException("Option $name is expected to be one of $values. $value is provided.") }
|
||||
override fun convert(value: kotlin.String, name: kotlin.String): kotlin.String =
|
||||
if (value in values) value
|
||||
else throw ParsingException("Option $name is expected to be one of $values. $value is provided.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
*/
|
||||
package kotlinx.cli
|
||||
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
/**
|
||||
* Parsing value of option/argument.
|
||||
*/
|
||||
@@ -18,8 +16,8 @@ internal abstract class ParsingValue<T: Any, TResult: Any>(val descriptor: Descr
|
||||
/**
|
||||
* Value origin.
|
||||
*/
|
||||
var valueOrigin = ArgParser.ValueOrigin.UNSET
|
||||
protected set
|
||||
var valueOrigin = ArgParser.ValueOrigin.UNDEFINED
|
||||
internal set
|
||||
|
||||
/**
|
||||
* Check if values of argument are empty.
|
||||
@@ -76,9 +74,6 @@ internal abstract class ParsingValue<T: Any, TResult: Any>(val descriptor: Descr
|
||||
* Set default value to option.
|
||||
*/
|
||||
fun addDefaultValue() {
|
||||
if (!descriptor.defaultValueSet && descriptor.required) {
|
||||
throw ParsingException("Please, provide value for ${descriptor.textDescription}. It should be always set.")
|
||||
}
|
||||
if (descriptor.defaultValueSet) {
|
||||
parsedValue = descriptor.defaultValue!!
|
||||
valueOrigin = ArgParser.ValueOrigin.SET_DEFAULT_VALUE
|
||||
@@ -105,7 +100,7 @@ internal abstract class AbstractArgumentSingleValue<T: Any>(descriptor: Descript
|
||||
|
||||
override fun saveValue(stringValue: String) {
|
||||
if (!valueIsInitialized()) {
|
||||
parsedValue = descriptor.type.conversion(stringValue, descriptor.fullName!!)
|
||||
parsedValue = descriptor.type.convert(stringValue, descriptor.fullName!!)
|
||||
valueOrigin = ArgParser.ValueOrigin.SET_BY_USER
|
||||
} else {
|
||||
throw ParsingException("Try to provide more than one value $parsedValue and $stringValue for ${descriptor.fullName}.")
|
||||
@@ -165,7 +160,7 @@ internal class ArgumentMultipleValues<T : Any>(descriptor: Descriptor<T, List<T>
|
||||
set(value) = setDelegatedValue(value)
|
||||
|
||||
override fun saveValue(stringValue: String) {
|
||||
addedValue.add(descriptor.type.conversion(stringValue, descriptor.fullName!!))
|
||||
addedValue.add(descriptor.type.convert(stringValue, descriptor.fullName!!))
|
||||
valueOrigin = ArgParser.ValueOrigin.SET_BY_USER
|
||||
}
|
||||
|
||||
|
||||
@@ -9,46 +9,98 @@ import kotlin.reflect.KProperty
|
||||
internal data class CLIEntityWrapper(var entity: CLIEntity<*>? = null)
|
||||
|
||||
/**
|
||||
* Command line entity.
|
||||
* Base interface for all possible types of entities with default and required values.
|
||||
* Provides limitations for API that is accessible for different options/arguments types.
|
||||
* Allows to save the reason why option/argument can(or can't) be omitted in command line.
|
||||
*
|
||||
* @property owner parser which owns current entity.
|
||||
* @see [SingleOption], [MultipleOption], [SingleArgument], [MultipleArgument].
|
||||
*/
|
||||
abstract class CLIEntity<TResult> internal constructor(internal val owner: CLIEntityWrapper) {
|
||||
interface DefaultRequiredType {
|
||||
/**
|
||||
* Wrapper for element - read only property.
|
||||
* Needed to close set of variable [cliElement].
|
||||
* Type of an entity with default value.
|
||||
*/
|
||||
lateinit var delegate: ArgumentValueDelegate<TResult>
|
||||
internal set
|
||||
class Default : DefaultRequiredType
|
||||
|
||||
/**
|
||||
* Value of entity.
|
||||
* Type of an entity which value should always be provided in command line.
|
||||
*/
|
||||
class Required : DefaultRequiredType
|
||||
|
||||
/**
|
||||
* Type of entity which is optional and has no default value.
|
||||
*/
|
||||
class None : DefaultRequiredType
|
||||
}
|
||||
|
||||
/**
|
||||
* The base class for a command line argument or an option.
|
||||
*/
|
||||
abstract class CLIEntity<TResult> internal constructor(val delegate: ArgumentValueDelegate<TResult>,
|
||||
internal val owner: CLIEntityWrapper) {
|
||||
/**
|
||||
* The value of the option or argument parsed from command line.
|
||||
*
|
||||
* Accessing this property before it gets its value will result in an exception.
|
||||
* You can use [valueOrigin] property to find out whether the property has been already set.
|
||||
*
|
||||
* @see ArgumentValueDelegate.value
|
||||
*/
|
||||
var value: TResult
|
||||
get() = delegate.value
|
||||
set(value) { delegate.value = value }
|
||||
set(value) {
|
||||
check((delegate as ParsingValue<*, *>).valueOrigin != ArgParser.ValueOrigin.UNDEFINED) {
|
||||
"Resetting value of option/argument is only possible after parsing command line arguments." +
|
||||
" ArgParser.parse(...) method should be called before"
|
||||
}
|
||||
delegate.value = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Origin of argument value.
|
||||
* The origin of the option/argument value.
|
||||
*/
|
||||
val valueOrigin: ArgParser.ValueOrigin
|
||||
get() = (delegate as ParsingValue<*, *>).valueOrigin
|
||||
|
||||
private var delegateProvided = false
|
||||
|
||||
/**
|
||||
* Returns the delegate object for property delegation and initializes it with the name of the delegated property.
|
||||
*
|
||||
* This operator makes it possible to delegate a property to this instance. It returns [delegate] object
|
||||
* to be used as an actual delegate and uses the name of the delegated property to initialize the full name
|
||||
* of the option/argument if it wasn't done during construction of that option/argument.
|
||||
*
|
||||
* @throws IllegalStateException in case of trying to use same delegate several times.
|
||||
*/
|
||||
operator fun provideDelegate(thisRef: Any?, prop: KProperty<*>): ArgumentValueDelegate<TResult> {
|
||||
check(!delegateProvided) {
|
||||
"There is already used delegate for ${(delegate as ParsingValue<*, *>).descriptor.fullName}."
|
||||
}
|
||||
(delegate as ParsingValue<*, *>).provideName(prop.name)
|
||||
delegateProvided = true
|
||||
return delegate
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Argument instance.
|
||||
* The base class for command line arguments.
|
||||
*
|
||||
* You can use [ArgParser.argument] function to declare an argument.
|
||||
*/
|
||||
abstract class Argument<TResult> internal constructor(owner: CLIEntityWrapper): CLIEntity<TResult>(owner)
|
||||
abstract class Argument<TResult> internal constructor(delegate: ArgumentValueDelegate<TResult>,
|
||||
owner: CLIEntityWrapper): CLIEntity<TResult>(delegate, owner)
|
||||
|
||||
/**
|
||||
* Common single argument instance.
|
||||
* The base class of an argument with a single value.
|
||||
*
|
||||
* A non-optional argument or an optional argument with a default value is represented with the [SingleArgument] inheritor.
|
||||
* An optional argument having nullable value is represented with the [SingleNullableArgument] inheritor.
|
||||
*/
|
||||
abstract class AbstractSingleArgument<T: Any, TResult> internal constructor(owner: CLIEntityWrapper): Argument<TResult>(owner) {
|
||||
// TODO: investigate if we can collapse two inheritors into the single base class and specialize extensions by TResult upper bound
|
||||
abstract class AbstractSingleArgument<T: Any, TResult, DefaultRequired: DefaultRequiredType> internal constructor(
|
||||
delegate: ArgumentValueDelegate<TResult>,
|
||||
owner: CLIEntityWrapper):
|
||||
Argument<TResult>(delegate, owner) {
|
||||
/**
|
||||
* Check descriptor for this kind of argument.
|
||||
*/
|
||||
@@ -60,51 +112,55 @@ abstract class AbstractSingleArgument<T: Any, TResult> internal constructor(owne
|
||||
}
|
||||
|
||||
/**
|
||||
* Argument with single non-nullable value.
|
||||
* A non-optional argument or an optional argument with a default value.
|
||||
*
|
||||
* The [value] of such argument is non-null.
|
||||
*/
|
||||
class SingleArgument<T : Any> internal constructor(descriptor: ArgDescriptor<T, T>, owner: CLIEntityWrapper):
|
||||
AbstractSingleArgument<T, T>(owner) {
|
||||
class SingleArgument<T : Any, DefaultRequired: DefaultRequiredType> internal constructor(descriptor: ArgDescriptor<T, T>,
|
||||
owner: CLIEntityWrapper):
|
||||
AbstractSingleArgument<T, T, DefaultRequired>(ArgumentSingleValue(descriptor), owner) {
|
||||
init {
|
||||
checkDescriptor(descriptor)
|
||||
delegate = ArgumentSingleValue(descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Argument with single nullable value.
|
||||
* An optional argument with nullable [value].
|
||||
*/
|
||||
class SingleNullableArgument<T : Any> internal constructor(descriptor: ArgDescriptor<T, T>, owner: CLIEntityWrapper):
|
||||
AbstractSingleArgument<T, T?>(owner){
|
||||
AbstractSingleArgument<T, T?, DefaultRequiredType.None>(ArgumentSingleNullableValue(descriptor), owner) {
|
||||
init {
|
||||
checkDescriptor(descriptor)
|
||||
delegate = ArgumentSingleNullableValue(descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Argument with multiple values.
|
||||
* An argument that allows several values to be provided in command line string.
|
||||
*
|
||||
* The [value] property of such argument has type `List<T>`.
|
||||
*/
|
||||
class MultipleArgument<T : Any> internal constructor(descriptor: ArgDescriptor<T, List<T>>, owner: CLIEntityWrapper):
|
||||
Argument<List<T>>(owner) {
|
||||
class MultipleArgument<T : Any, DefaultRequired: DefaultRequiredType> internal constructor(
|
||||
descriptor: ArgDescriptor<T, List<T>>, owner: CLIEntityWrapper):
|
||||
Argument<List<T>>(ArgumentMultipleValues(descriptor), owner) {
|
||||
init {
|
||||
if (descriptor.number != null && descriptor.number < 2) {
|
||||
failAssertion("Argument with multiple values can't be initialized with descriptor for single one.")
|
||||
}
|
||||
delegate = ArgumentMultipleValues(descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow argument have several values.
|
||||
* Allows the argument to have several values specified in command line string.
|
||||
*
|
||||
* @param value number of arguments are expected.
|
||||
* @param number the exact number of values expected for this argument, but at least 2.
|
||||
*
|
||||
* @throws IllegalArgumentException if number of values expected for this argument less than 2.
|
||||
*/
|
||||
fun <T : Any, TResult> AbstractSingleArgument<T, TResult>.multiple(value: Int): MultipleArgument<T> {
|
||||
if (value < 2) {
|
||||
error("multiple() modifier with value less than 2 is unavailable. It's already set to 1.")
|
||||
}
|
||||
fun <T : Any, TResult, DefaultRequired: DefaultRequiredType>
|
||||
AbstractSingleArgument<T, TResult, DefaultRequired>.multiple(number: Int): MultipleArgument<T, DefaultRequired> {
|
||||
require(number >= 2) { "multiple() modifier with value less than 2 is unavailable. It's already set to 1." }
|
||||
val newArgument = with((delegate as ParsingValue<T, T>).descriptor as ArgDescriptor) {
|
||||
MultipleArgument(ArgDescriptor(type, fullName, value, description, listOfNotNull(defaultValue),
|
||||
MultipleArgument<T, DefaultRequired>(ArgDescriptor(type, fullName, number, description, listOfNotNull(defaultValue),
|
||||
required, deprecatedWarning), owner)
|
||||
}
|
||||
owner.entity = newArgument
|
||||
@@ -112,11 +168,12 @@ fun <T : Any, TResult> AbstractSingleArgument<T, TResult>.multiple(value: Int):
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow argument have several values.
|
||||
* Allows the last argument to take all the trailing values in command line string.
|
||||
*/
|
||||
fun <T : Any, TResult> AbstractSingleArgument<T, TResult>.vararg(): MultipleArgument<T> {
|
||||
fun <T : Any, TResult, DefaultRequired: DefaultRequiredType> AbstractSingleArgument<T, TResult, DefaultRequired>.vararg():
|
||||
MultipleArgument<T, DefaultRequired> {
|
||||
val newArgument = with((delegate as ParsingValue<T, T>).descriptor as ArgDescriptor) {
|
||||
MultipleArgument(ArgDescriptor(type, fullName, null, description, listOfNotNull(defaultValue),
|
||||
MultipleArgument<T, DefaultRequired>(ArgDescriptor(type, fullName, null, description, listOfNotNull(defaultValue),
|
||||
required, deprecatedWarning), owner)
|
||||
}
|
||||
owner.entity = newArgument
|
||||
@@ -124,29 +181,35 @@ fun <T : Any, TResult> AbstractSingleArgument<T, TResult>.vararg(): MultipleArgu
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default value for argument.
|
||||
* Specifies the default value for the argument, that will be used when no value is provided for the argument
|
||||
* in command line string.
|
||||
*
|
||||
* @param value default value.
|
||||
* Argument becomes optional, because value for it is set even if it isn't provided in command line.
|
||||
*
|
||||
* @param value the default value.
|
||||
*/
|
||||
fun <T: Any, TResult> AbstractSingleArgument<T, TResult>.default(value: T): SingleArgument<T> {
|
||||
fun <T: Any> SingleNullableArgument<T>.default(value: T): SingleArgument<T, DefaultRequiredType.Default> {
|
||||
val newArgument = with((delegate as ParsingValue<T, T>).descriptor as ArgDescriptor) {
|
||||
SingleArgument(ArgDescriptor(type, fullName, number, description, value, required, deprecatedWarning), owner)
|
||||
SingleArgument<T, DefaultRequiredType.Default>(ArgDescriptor(type, fullName, number, description, value,
|
||||
false, deprecatedWarning), owner)
|
||||
}
|
||||
owner.entity = newArgument
|
||||
return newArgument
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default value for argument.
|
||||
* Specifies the default value for the argument with multiple values, that will be used when no values are provided
|
||||
* for the argument in command line string.
|
||||
*
|
||||
* @param value default value.
|
||||
* Argument becomes optional, because value for it is set even if it isn't provided in command line.
|
||||
*
|
||||
* @param value the default value, must be a non-empty collection.
|
||||
*/
|
||||
fun <T: Any> MultipleArgument<T>.default(value: Collection<T>): MultipleArgument<T> {
|
||||
if (value.isEmpty()) {
|
||||
error("Default value for argument can't be empty collection.")
|
||||
}
|
||||
fun <T: Any> MultipleArgument<T, DefaultRequiredType.None>.default(value: Collection<T>):
|
||||
MultipleArgument<T, DefaultRequiredType.Default> {
|
||||
require (value.isNotEmpty()) { "Default value for argument can't be empty collection." }
|
||||
val newArgument = with((delegate as ParsingValue<T, List<T>>).descriptor as ArgDescriptor) {
|
||||
MultipleArgument(ArgDescriptor(type, fullName, number, description, value.toList(),
|
||||
MultipleArgument<T, DefaultRequiredType.Default>(ArgDescriptor(type, fullName, number, description, value.toList(),
|
||||
required, deprecatedWarning), owner)
|
||||
}
|
||||
owner.entity = newArgument
|
||||
@@ -154,9 +217,13 @@ fun <T: Any> MultipleArgument<T>.default(value: Collection<T>): MultipleArgument
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow argument be unprovided in command line.
|
||||
* Allows the argument to have no value specified in command line string.
|
||||
*
|
||||
* The value of the argument is `null` in case if no value was specified in command line string.
|
||||
*
|
||||
* Note that only trailing arguments can be optional, i.e. no required arguments can follow optional ones.
|
||||
*/
|
||||
fun <T: Any> SingleArgument<T>.optional(): SingleNullableArgument<T> {
|
||||
fun <T: Any> SingleArgument<T, DefaultRequiredType.Required>.optional(): SingleNullableArgument<T> {
|
||||
val newArgument = with((delegate as ParsingValue<T, T>).descriptor as ArgDescriptor) {
|
||||
SingleNullableArgument(ArgDescriptor(type, fullName, number, description, defaultValue,
|
||||
false, deprecatedWarning), owner)
|
||||
@@ -166,15 +233,19 @@ fun <T: Any> SingleArgument<T>.optional(): SingleNullableArgument<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow argument be unprovided in command line.
|
||||
* Allows the argument with multiple values to have no values specified in command line string.
|
||||
*
|
||||
* The value of the argument is an empty list in case if no value was specified in command line string.
|
||||
*
|
||||
* Note that only trailing arguments can be optional: no required arguments can follow the optional ones.
|
||||
*/
|
||||
fun <T: Any> MultipleArgument<T>.optional(): MultipleArgument<T> {
|
||||
fun <T: Any> MultipleArgument<T, DefaultRequiredType.Required>.optional(): MultipleArgument<T, DefaultRequiredType.None> {
|
||||
val newArgument = with((delegate as ParsingValue<T, List<T>>).descriptor as ArgDescriptor) {
|
||||
MultipleArgument(ArgDescriptor(type, fullName, number, description,
|
||||
MultipleArgument<T, DefaultRequiredType.None>(ArgDescriptor(type, fullName, number, description,
|
||||
defaultValue?.toList() ?: listOf(), false, deprecatedWarning), owner)
|
||||
}
|
||||
owner.entity = newArgument
|
||||
return newArgument
|
||||
}
|
||||
|
||||
fun failAssertion(message: String): Nothing = throw AssertionError(message)
|
||||
internal fun failAssertion(message: String): Nothing = throw AssertionError(message)
|
||||
@@ -11,7 +11,7 @@ package kotlinx.cli
|
||||
* @property fullName option/argument full name.
|
||||
* @property description text description of option/argument.
|
||||
* @property defaultValue default value for option/argument.
|
||||
* @property required if option/argument is required or not. If it's required and not provided in command line and have no default value, error will be generated.
|
||||
* @property required if option/argument is required or not. If it's required and not provided in command line, error will be generated.
|
||||
* @property deprecatedWarning text message with information in case if option is deprecated.
|
||||
*/
|
||||
internal abstract class Descriptor<T : Any, TResult>(val type: ArgType<T>,
|
||||
@@ -36,7 +36,7 @@ internal abstract class Descriptor<T : Any, TResult>(val type: ArgType<T>,
|
||||
*/
|
||||
fun valueDescription(value: TResult?) = value?.let {
|
||||
if (it is List<*> && it.isNotEmpty())
|
||||
" [${it.joinToString { it.toString() }}]"
|
||||
" [${it.joinToString()}]"
|
||||
else if (it !is List<*>)
|
||||
" [$it]"
|
||||
else null
|
||||
@@ -62,7 +62,7 @@ internal abstract class Descriptor<T : Any, TResult>(val type: ArgType<T>,
|
||||
* @property shortName option short name.
|
||||
* @property description text description of option.
|
||||
* @property defaultValue default value for option.
|
||||
* @property required if option is required or not. If it's required and not provided in command line and have no default value, error will be generated.
|
||||
* @property required if option is required or not. If it's required and not provided in command line, error will be generated.
|
||||
* @property multiple if option can be repeated several times in command line with different values. All values are stored.
|
||||
* @property delimiter delimiter that separate option provided as one string to several values.
|
||||
* @property deprecatedWarning text message with information in case if option is deprecated.
|
||||
@@ -90,9 +90,9 @@ internal class OptionDescriptor<T : Any, TResult>(
|
||||
result.append(" $optionFullFormPrefix$fullName")
|
||||
shortName?.let { result.append(", $optionShortFromPrefix$it") }
|
||||
valueDescription(defaultValue)?.let {
|
||||
result.append("$it")
|
||||
result.append(it)
|
||||
}
|
||||
description?.let {result.append(" -> ${it}")}
|
||||
description?.let {result.append(" -> $it")}
|
||||
if (required) result.append(" (always required)")
|
||||
result.append(" ${type.description}")
|
||||
deprecatedWarning?.let { result.append(" Warning: $it") }
|
||||
@@ -140,9 +140,9 @@ internal class ArgDescriptor<T : Any, TResult>(
|
||||
val result = StringBuilder()
|
||||
result.append(" ${fullName}")
|
||||
valueDescription(defaultValue)?.let {
|
||||
result.append("$it")
|
||||
result.append(it)
|
||||
}
|
||||
description?.let { result.append(" -> ${it}") }
|
||||
description?.let { result.append(" -> $it") }
|
||||
if (!required) result.append(" (optional)")
|
||||
result.append(" ${type.description}")
|
||||
deprecatedWarning?.let { result.append(" Warning: $it") }
|
||||
|
||||
@@ -32,5 +32,5 @@ import kotlin.annotation.AnnotationTarget.*
|
||||
PROPERTY_SETTER,
|
||||
TYPEALIAS
|
||||
)
|
||||
@SinceKotlin("1.3")
|
||||
|
||||
public annotation class ExperimentalCli
|
||||
@@ -4,37 +4,49 @@
|
||||
*/
|
||||
package kotlinx.cli
|
||||
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
/**
|
||||
* Base interface for all possible types of options with multiple values.
|
||||
* Provides limitations for API that is accessible for options with several values.
|
||||
* Allows to save the way of providing several values in command line.
|
||||
*
|
||||
* @see [MultipleOption]
|
||||
*/
|
||||
interface MultipleOptionType
|
||||
interface MultipleOptionType {
|
||||
/**
|
||||
* Type of an option with multiple values allowed to be provided several times in command line.
|
||||
*/
|
||||
class Repeated : MultipleOptionType
|
||||
|
||||
/**
|
||||
* Type of an option with multiple values allowed to be provided using delimiter in one command line value.
|
||||
*/
|
||||
class Delimited : MultipleOptionType
|
||||
|
||||
/**
|
||||
* Type of an option with multiple values allowed to be provided several times in command line
|
||||
* both with specifying several values and with delimiter.
|
||||
*/
|
||||
class RepeatedDelimited : MultipleOptionType
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of option with multiple values that can be provided several times in command line.
|
||||
* The base class for command line options.
|
||||
*
|
||||
* You can use [ArgParser.option] function to declare an option.
|
||||
*/
|
||||
class RepeatedOption: MultipleOptionType
|
||||
abstract class Option<TResult> internal constructor(delegate: ArgumentValueDelegate<TResult>,
|
||||
owner: CLIEntityWrapper) : CLIEntity<TResult>(delegate, owner)
|
||||
|
||||
/**
|
||||
* Type of option with multiple values that are provided using delimiter.
|
||||
* The base class of an option with a single value.
|
||||
*
|
||||
* A required option or an option with a default value is represented with the [SingleOption] inheritor.
|
||||
* An option having nullable value is represented with the [SingleNullableOption] inheritor.
|
||||
*/
|
||||
class DelimitedOption: MultipleOptionType
|
||||
|
||||
/**
|
||||
* Type of option with multiple values that can be both provided several times in command line and using delimiter.
|
||||
*/
|
||||
class RepeatedDelimitedOption: MultipleOptionType
|
||||
|
||||
/**
|
||||
* Option instance.
|
||||
*/
|
||||
abstract class Option<TResult> internal constructor(owner: CLIEntityWrapper): CLIEntity<TResult>(owner)
|
||||
|
||||
/**
|
||||
* Common single option instance.
|
||||
*/
|
||||
abstract class AbstractSingleOption<T: Any, TResult> internal constructor(owner: CLIEntityWrapper): Option<TResult>(owner) {
|
||||
abstract class AbstractSingleOption<T : Any, TResult, DefaultRequired: DefaultRequiredType> internal constructor(
|
||||
delegate: ArgumentValueDelegate<TResult>,
|
||||
owner: CLIEntityWrapper) :
|
||||
Option<TResult>(delegate, owner) {
|
||||
/**
|
||||
* Check descriptor for this kind of option.
|
||||
*/
|
||||
@@ -46,154 +58,208 @@ abstract class AbstractSingleOption<T: Any, TResult> internal constructor(owner:
|
||||
}
|
||||
|
||||
/**
|
||||
* Option wit single non-nullable value.
|
||||
* A required option or an option with a default value.
|
||||
*
|
||||
* The [value] of such option is non-null.
|
||||
*/
|
||||
class SingleOption<T : Any> internal constructor(descriptor: OptionDescriptor<T, T>, owner: CLIEntityWrapper):
|
||||
AbstractSingleOption<T, T>(owner) {
|
||||
class SingleOption<T : Any, DefaultType: DefaultRequiredType> internal constructor(descriptor: OptionDescriptor<T, T>,
|
||||
owner: CLIEntityWrapper) :
|
||||
AbstractSingleOption<T, T, DefaultRequiredType>(ArgumentSingleValue(descriptor), owner) {
|
||||
init {
|
||||
checkDescriptor(descriptor)
|
||||
delegate = ArgumentSingleValue(descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Option with single nullable value.
|
||||
* An option with nullable [value].
|
||||
*/
|
||||
class SingleNullableOption<T : Any> internal constructor(descriptor: OptionDescriptor<T, T>, owner: CLIEntityWrapper):
|
||||
AbstractSingleOption<T, T?>(owner) {
|
||||
class SingleNullableOption<T : Any> internal constructor(descriptor: OptionDescriptor<T, T>, owner: CLIEntityWrapper) :
|
||||
AbstractSingleOption<T, T?, DefaultRequiredType.None>(ArgumentSingleNullableValue(descriptor), owner) {
|
||||
init {
|
||||
checkDescriptor(descriptor)
|
||||
delegate = ArgumentSingleNullableValue(descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Option with multiple values.
|
||||
* An option that allows several values to be provided in command line string.
|
||||
*
|
||||
* The [value] property of such option has type `List<T>`.
|
||||
*/
|
||||
class MultipleOption<T : Any, OptionType: MultipleOptionType> internal constructor(descriptor: OptionDescriptor<T, List<T>>, owner: CLIEntityWrapper):
|
||||
Option<List<T>>(owner) {
|
||||
class MultipleOption<T : Any, OptionType : MultipleOptionType, DefaultType: DefaultRequiredType> internal constructor(
|
||||
descriptor: OptionDescriptor<T, List<T>>,
|
||||
owner: CLIEntityWrapper
|
||||
) :
|
||||
Option<List<T>>( ArgumentMultipleValues(descriptor), owner) {
|
||||
init {
|
||||
if (!descriptor.multiple && descriptor.delimiter == null) {
|
||||
failAssertion("Option with multiple values can't be initialized with descriptor for single one.")
|
||||
}
|
||||
delegate = ArgumentMultipleValues(descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow option have several values.
|
||||
* Allows the option to have several values specified in command line string.
|
||||
* Number of values is unlimited.
|
||||
*/
|
||||
fun <T : Any, TResult> AbstractSingleOption<T, TResult>.multiple(): MultipleOption<T, RepeatedOption> {
|
||||
fun <T : Any, TResult, DefaultType: DefaultRequiredType> AbstractSingleOption<T, TResult, DefaultType>.multiple():
|
||||
MultipleOption<T, MultipleOptionType.Repeated, DefaultType> {
|
||||
val newOption = with((delegate as ParsingValue<T, T>).descriptor as OptionDescriptor) {
|
||||
MultipleOption<T, RepeatedOption>(OptionDescriptor(optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
|
||||
MultipleOption<T, MultipleOptionType.Repeated, DefaultType>(
|
||||
OptionDescriptor(
|
||||
optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
|
||||
description, listOfNotNull(defaultValue),
|
||||
required, true, delimiter, deprecatedWarning), owner)
|
||||
required, true, delimiter, deprecatedWarning
|
||||
), owner
|
||||
)
|
||||
}
|
||||
owner.entity = newOption
|
||||
return newOption
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow option have several values.
|
||||
* Allows the option to have several values specified in command line string.
|
||||
* Number of values is unlimited.
|
||||
*/
|
||||
fun <T : Any> MultipleOption<T, DelimitedOption>.multiple(): MultipleOption<T, RepeatedDelimitedOption> {
|
||||
fun <T : Any, DefaultType: DefaultRequiredType> MultipleOption<T, MultipleOptionType.Delimited, DefaultType>.multiple():
|
||||
MultipleOption<T, MultipleOptionType.RepeatedDelimited, DefaultRequiredType> {
|
||||
val newOption = with((delegate as ParsingValue<T, List<T>>).descriptor as OptionDescriptor) {
|
||||
if (multiple) {
|
||||
error("Try to use modifier multiple() twice on option ${fullName ?: ""}")
|
||||
}
|
||||
MultipleOption<T, RepeatedDelimitedOption>(OptionDescriptor(optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
|
||||
MultipleOption<T, MultipleOptionType.RepeatedDelimited, DefaultRequiredType>(
|
||||
OptionDescriptor(
|
||||
optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
|
||||
description, defaultValue?.toList() ?: listOf(),
|
||||
required, true, delimiter, deprecatedWarning), owner)
|
||||
required, true, delimiter, deprecatedWarning
|
||||
), owner
|
||||
)
|
||||
}
|
||||
owner.entity = newOption
|
||||
return newOption
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default option value.
|
||||
* Specifies the default value for the option, that will be used when no value is provided for it
|
||||
* in command line string.
|
||||
*
|
||||
* @param value default value.
|
||||
* @param value the default value.
|
||||
*/
|
||||
fun <T: Any, TResult> AbstractSingleOption<T, TResult>.default(value: T): SingleOption<T> {
|
||||
fun <T : Any> SingleNullableOption<T>.default(value: T): SingleOption<T, DefaultRequiredType.Default> {
|
||||
val newOption = with((delegate as ParsingValue<T, T>).descriptor as OptionDescriptor) {
|
||||
SingleOption(OptionDescriptor(optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
|
||||
description, value, required, multiple, delimiter, deprecatedWarning), owner)
|
||||
SingleOption<T, DefaultRequiredType.Default>(
|
||||
OptionDescriptor(
|
||||
optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
|
||||
description, value, required, multiple, delimiter, deprecatedWarning
|
||||
), owner
|
||||
)
|
||||
}
|
||||
owner.entity = newOption
|
||||
return newOption
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default option value.
|
||||
* Specifies the default value for the option with multiple values, that will be used when no values are provided
|
||||
* for it in command line string.
|
||||
*
|
||||
* @param value default value.
|
||||
* @param value the default value, must be a non-empty collection.
|
||||
* @throws IllegalArgumentException if provided default value is empty collection.
|
||||
*/
|
||||
fun <T: Any, OptionType: MultipleOptionType>
|
||||
MultipleOption<T, OptionType>.default(value: Collection<T>): MultipleOption<T, OptionType> {
|
||||
fun <T : Any, OptionType : MultipleOptionType>
|
||||
MultipleOption<T, OptionType, DefaultRequiredType.None>.default(value: Collection<T>):
|
||||
MultipleOption<T, OptionType, DefaultRequiredType.Default> {
|
||||
val newOption = with((delegate as ParsingValue<T, List<T>>).descriptor as OptionDescriptor) {
|
||||
if (value.isEmpty()) {
|
||||
error("Default value for option can't be empty collection.")
|
||||
}
|
||||
MultipleOption<T, OptionType>(OptionDescriptor(optionFullFormPrefix, optionShortFromPrefix, type, fullName,
|
||||
require(value.isNotEmpty()) { "Default value for option can't be empty collection." }
|
||||
MultipleOption<T, OptionType, DefaultRequiredType.Default>(
|
||||
OptionDescriptor(
|
||||
optionFullFormPrefix, optionShortFromPrefix, type, fullName,
|
||||
shortName, description, value.toList(),
|
||||
required, multiple, delimiter, deprecatedWarning), owner)
|
||||
required, multiple, delimiter, deprecatedWarning
|
||||
), owner
|
||||
)
|
||||
}
|
||||
owner.entity = newOption
|
||||
return newOption
|
||||
}
|
||||
|
||||
/**
|
||||
* Require option to be always provided in command line.
|
||||
* Requires the option to be always provided in command line.
|
||||
*/
|
||||
fun <T: Any> SingleNullableOption<T>.required(): SingleOption<T> {
|
||||
fun <T : Any> SingleNullableOption<T>.required(): SingleOption<T, DefaultRequiredType.Required> {
|
||||
val newOption = with((delegate as ParsingValue<T, T>).descriptor as OptionDescriptor) {
|
||||
SingleOption(OptionDescriptor(optionFullFormPrefix, optionShortFromPrefix, type, fullName,
|
||||
SingleOption<T, DefaultRequiredType.Required>(
|
||||
OptionDescriptor(
|
||||
optionFullFormPrefix, optionShortFromPrefix, type, fullName,
|
||||
shortName, description, defaultValue,
|
||||
true, multiple, delimiter, deprecatedWarning), owner)
|
||||
true, multiple, delimiter, deprecatedWarning
|
||||
), owner
|
||||
)
|
||||
}
|
||||
owner.entity = newOption
|
||||
return newOption
|
||||
}
|
||||
|
||||
/**
|
||||
* Require option to be always provided in command line.
|
||||
* Requires the option to be always provided in command line.
|
||||
*/
|
||||
fun <T: Any, OptionType: MultipleOptionType>
|
||||
MultipleOption<T, OptionType>.required(): MultipleOption<T, OptionType> {
|
||||
fun <T : Any, OptionType : MultipleOptionType>
|
||||
MultipleOption<T, OptionType, DefaultRequiredType.None>.required():
|
||||
MultipleOption<T, OptionType, DefaultRequiredType.Required> {
|
||||
val newOption = with((delegate as ParsingValue<T, List<T>>).descriptor as OptionDescriptor) {
|
||||
MultipleOption<T, OptionType>(OptionDescriptor(optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
|
||||
MultipleOption<T, OptionType, DefaultRequiredType.Required>(
|
||||
OptionDescriptor(
|
||||
optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
|
||||
description, defaultValue?.toList() ?: listOf(),
|
||||
true, multiple, delimiter, deprecatedWarning), owner)
|
||||
true, multiple, delimiter, deprecatedWarning
|
||||
), owner
|
||||
)
|
||||
}
|
||||
owner.entity = newOption
|
||||
return newOption
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow provide several options using [delimiter].
|
||||
* Allows the option to have several values joined with [delimiter] specified in command line string.
|
||||
* Number of values is unlimited.
|
||||
*
|
||||
* @param delimiterValue delimiter used to separate string value to option values.
|
||||
* The value of the argument is an empty list in case if no value was specified in command line string.
|
||||
*
|
||||
* @param delimiterValue delimiter used to separate string value to option values list.
|
||||
*/
|
||||
fun <T : Any, TResult> AbstractSingleOption<T, TResult>.delimiter(delimiterValue: String): MultipleOption<T, DelimitedOption> {
|
||||
fun <T : Any, DefaultRequired: DefaultRequiredType> AbstractSingleOption<T, *, DefaultRequired>.delimiter(
|
||||
delimiterValue: String):
|
||||
MultipleOption<T, MultipleOptionType.Delimited, DefaultRequired> {
|
||||
val newOption = with((delegate as ParsingValue<T, T>).descriptor as OptionDescriptor) {
|
||||
MultipleOption<T, DelimitedOption>(OptionDescriptor(optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
|
||||
MultipleOption<T, MultipleOptionType.Delimited, DefaultRequired>(
|
||||
OptionDescriptor(
|
||||
optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
|
||||
description, listOfNotNull(defaultValue),
|
||||
required, multiple, delimiterValue, deprecatedWarning), owner)
|
||||
required, multiple, delimiterValue, deprecatedWarning
|
||||
), owner
|
||||
)
|
||||
}
|
||||
owner.entity = newOption
|
||||
return newOption
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow provide several options using [delimiter].
|
||||
* Allows the option to have several values joined with [delimiter] specified in command line string.
|
||||
* Number of values is unlimited.
|
||||
*
|
||||
* @param delimiterValue delimiter used to separate string value to option values.
|
||||
* The value of the argument is an empty list in case if no value was specified in command line string.
|
||||
*
|
||||
* @param delimiterValue delimiter used to separate string value to option values list.
|
||||
*/
|
||||
fun <T : Any> MultipleOption<T, RepeatedOption>.delimiter(delimiterValue: String): MultipleOption<T, RepeatedDelimitedOption> {
|
||||
fun <T : Any, DefaultRequired: DefaultRequiredType> MultipleOption<T, MultipleOptionType.Repeated, DefaultRequired>.delimiter(
|
||||
delimiterValue: String):
|
||||
MultipleOption<T, MultipleOptionType.RepeatedDelimited, DefaultRequired> {
|
||||
val newOption = with((delegate as ParsingValue<T, List<T>>).descriptor as OptionDescriptor) {
|
||||
MultipleOption<T, RepeatedDelimitedOption>(OptionDescriptor(optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
|
||||
MultipleOption<T, MultipleOptionType.RepeatedDelimited, DefaultRequired>(
|
||||
OptionDescriptor(
|
||||
optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
|
||||
description, defaultValue?.toList() ?: listOf(),
|
||||
required, multiple, delimiterValue, deprecatedWarning), owner)
|
||||
required, multiple, delimiterValue, deprecatedWarning
|
||||
), owner
|
||||
)
|
||||
}
|
||||
owner.entity = newOption
|
||||
return newOption
|
||||
|
||||
@@ -28,6 +28,8 @@ class HelpTests {
|
||||
val user by argParser.option(ArgType.String, shortName = "u", description = "User access information for authorization")
|
||||
argParser.parse(arrayOf("main.txt"))
|
||||
val helpOutput = argParser.makeUsage().trimIndent()
|
||||
@Suppress("CanBeVal") // can't be val in order to build expectedOutput only in run time.
|
||||
var epsDefault = 1.0
|
||||
val expectedOutput = """
|
||||
Usage: test options_list
|
||||
Arguments:
|
||||
@@ -35,7 +37,7 @@ Arguments:
|
||||
compareToReport -> Report to compare to (optional) { String }
|
||||
Options:
|
||||
--output, -o -> Output file { String }
|
||||
--eps, -e [1.0] -> Meaningful performance changes { Double }
|
||||
--eps, -e [$epsDefault] -> Meaningful performance changes { Double }
|
||||
--short, -s [false] -> Show short version of report
|
||||
--renders, -r [text] -> Renders for showing information { Value should be one of [text, html, teamcity, statistics, metrics] }
|
||||
--user, -u -> User access information for authorization { String }
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
|
||||
package kotlinx.cli
|
||||
|
||||
import kotlinx.cli.ArgParser
|
||||
import kotlinx.cli.ArgType
|
||||
import kotlin.test.*
|
||||
|
||||
class OptionsTests {
|
||||
@@ -32,7 +30,7 @@ class OptionsTests {
|
||||
|
||||
@Test
|
||||
fun testJavaPrefix() {
|
||||
val argParser = ArgParser("testParser", prefixStyle = ArgParser.OPTION_PREFIX_STYLE.JVM)
|
||||
val argParser = ArgParser("testParser", prefixStyle = ArgParser.OptionPrefixStyle.JVM)
|
||||
val output by argParser.option(ArgType.String, "output", "o", "Output file")
|
||||
val input by argParser.option(ArgType.String, "input", "i", "Input file")
|
||||
argParser.parse(arrayOf("-output", "out.txt", "-i", "input.txt"))
|
||||
|
||||
Reference in New Issue
Block a user