Skip to content

Added DefaultValuePattern to set default value using values of other … #22

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/commonMain/src/ArgumentValues.kt
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ internal abstract class ParsingValue<T: Any, TResult: Any>(val descriptor: Descr
*/
fun addDefaultValue() {
if (descriptor.defaultValueSet) {
parsedValue = descriptor.defaultValue!!
parsedValue = descriptor.defaultValue!!.value
valueOrigin = ArgParser.ValueOrigin.SET_DEFAULT_VALUE
}
}
Expand Down
44 changes: 31 additions & 13 deletions core/commonMain/src/Arguments.kt
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,8 @@ 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<T, DefaultRequired>(ArgDescriptor(type, fullName, number, description, listOfNotNull(defaultValue),
MultipleArgument<T, DefaultRequired>(ArgDescriptor(type, fullName, number, description,
defaultValue?.toMultiple() ?: SimpleDefaultValue(emptyList()),
required, deprecatedWarning), owner)
}
owner.entity = newArgument
Expand All @@ -173,13 +174,24 @@ fun <T : Any, TResult, DefaultRequired: DefaultRequiredType>
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<T, DefaultRequired>(ArgDescriptor(type, fullName, null, description, listOfNotNull(defaultValue),
MultipleArgument<T, DefaultRequired>(ArgDescriptor(type, fullName, null, description,
defaultValue?.toMultiple() ?: SimpleDefaultValue(emptyList()),
required, deprecatedWarning), owner)
}
owner.entity = newArgument
return newArgument
}

internal fun <T: Any> SingleNullableArgument<T>.toArgumentWithDefault(value: DefaultValue<T>):
SingleArgument<T, DefaultRequiredType.Default> {
val newArgument = with((delegate as ParsingValue<T, T>).descriptor as ArgDescriptor) {
SingleArgument<T, DefaultRequiredType.Default>(ArgDescriptor(type, fullName, number, description,
value, false, deprecatedWarning), owner)
}
owner.entity = newArgument
return newArgument
}

/**
* Specifies the default value for the argument, that will be used when no value is provided for the argument
* in command line string.
Expand All @@ -188,14 +200,20 @@ fun <T : Any, TResult, DefaultRequired: DefaultRequiredType> AbstractSingleArgum
*
* @param value the default value.
*/
fun <T: Any> SingleNullableArgument<T>.default(value: T): SingleArgument<T, DefaultRequiredType.Default> {
val newArgument = with((delegate as ParsingValue<T, T>).descriptor as ArgDescriptor) {
SingleArgument<T, DefaultRequiredType.Default>(ArgDescriptor(type, fullName, number, description, value,
false, deprecatedWarning), owner)
}
owner.entity = newArgument
return newArgument
}
fun <T: Any> SingleNullableArgument<T>.default(value: T): SingleArgument<T, DefaultRequiredType.Default> =
toArgumentWithDefault(SimpleDefaultValue(value))


/**
* Specifies the default value for the argument, that will be used when no value is provided for the argument
* in command line string.
*
* 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> SingleNullableArgument<T>.default(value: DefaultValuePattern<T>):
SingleArgument<T, DefaultRequiredType.Default> = toArgumentWithDefault(value)

/**
* Specifies the default value for the argument with multiple values, that will be used when no values are provided
Expand All @@ -209,8 +227,8 @@ fun <T: Any> MultipleArgument<T, DefaultRequiredType.None>.default(value: Collec
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<T, DefaultRequiredType.Default>(ArgDescriptor(type, fullName, number, description, value.toList(),
required, deprecatedWarning), owner)
MultipleArgument<T, DefaultRequiredType.Default>(ArgDescriptor(type, fullName, number, description,
SimpleDefaultValue(value.toList()), required, deprecatedWarning), owner)
}
owner.entity = newArgument
return newArgument
Expand Down Expand Up @@ -242,7 +260,7 @@ fun <T: Any> SingleArgument<T, DefaultRequiredType.Required>.optional(): SingleN
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<T, DefaultRequiredType.None>(ArgDescriptor(type, fullName, number, description,
defaultValue?.toList() ?: listOf(), false, deprecatedWarning), owner)
defaultValue ?: SimpleDefaultValue(listOf()), false, deprecatedWarning), owner)
}
owner.entity = newArgument
return newArgument
Expand Down
91 changes: 73 additions & 18 deletions core/commonMain/src/Descriptors.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,74 @@
*/
package kotlinx.cli

/**
* Wrapper for default value.
*/
abstract class DefaultValue<T> {
abstract val value: T
abstract val helpMessage: String

/**
* Provide text description of value.
*
* @param value value got getting text description for.
*/
fun valueDescription(value: T) =
if (value is List<*> && value.isNotEmpty())
" [${value.joinToString()}]"
else if (value !is List<*>)
" [$value]"
else ""

abstract fun toMultiple(): DefaultValue<List<T>>
}

/**
* Simple default value which just wrap value of any argument type.
*/
internal class SimpleDefaultValue<T>(private val simpleValue: T) : DefaultValue<T>() {
override val value: T
get() = simpleValue
override val helpMessage: String
get() = valueDescription(value)

override fun toMultiple(): DefaultValue<List<T>> = SimpleDefaultValue(listOf(value))
}

/**
* Default value pattern which allows to use create default value
* based on values of other command line options/arguments.
*/
class DefaultValuePattern<T>(val cliEntities: List<CLIEntity<T>>,
val expression: (values: List<T>) -> T,
val helpMessageGenerator: (values: List<String>) -> String) :
DefaultValue<T>() {

init {
cliEntities.forEach {
with((it.delegate as ParsingValue<*, *>).descriptor) {
require(defaultValue != null || required) {
"It's possible to use only arguments/options which always have values."
}
}
}
}

override val value: T by lazy {
expression(cliEntities.map { it.value })
}

override val helpMessage: String
get() = " [${helpMessageGenerator(cliEntities.
map { "\${${(it.delegate as ParsingValue<*, *>).descriptor.fullName!!}}" })}]"

override fun toMultiple(): DefaultValuePattern<List<T>> {
// For custom generator it's impossible to convert value to another type.
error("Conversion to multiple default values is unknown in case " +
"of provided by user pattern for default value.")
}
}

/**
* Common descriptor both for options and positional arguments.
*
Expand All @@ -17,7 +85,7 @@ package kotlinx.cli
internal abstract class Descriptor<T : Any, TResult>(val type: ArgType<T>,
var fullName: String? = null,
val description: String? = null,
val defaultValue: TResult? = null,
val defaultValue: DefaultValue<TResult>? = null,
val required: Boolean = false,
val deprecatedWarning: String? = null) {
/**
Expand All @@ -29,19 +97,6 @@ internal abstract class Descriptor<T : Any, TResult>(val type: ArgType<T>,
*/
abstract val helpMessage: String

/**
* Provide text description of value.
*
* @param value value got getting text description for.
*/
fun valueDescription(value: TResult?) = value?.let {
if (it is List<*> && it.isNotEmpty())
" [${it.joinToString()}]"
else if (it !is List<*>)
" [$it]"
else null
}

/**
* Flag to check if descriptor has set default value for option/argument.
*/
Expand Down Expand Up @@ -74,7 +129,7 @@ internal class OptionDescriptor<T : Any, TResult>(
fullName: String? = null,
val shortName: String ? = null,
description: String? = null,
defaultValue: TResult? = null,
defaultValue: DefaultValue<TResult>? = null,
required: Boolean = false,
val multiple: Boolean = false,
val delimiter: String? = null,
Expand All @@ -89,7 +144,7 @@ internal class OptionDescriptor<T : Any, TResult>(
val result = StringBuilder()
result.append(" $optionFullFormPrefix$fullName")
shortName?.let { result.append(", $optionShortFromPrefix$it") }
valueDescription(defaultValue)?.let {
defaultValue?.helpMessage?.let {
result.append(it)
}
description?.let {result.append(" -> $it")}
Expand Down Expand Up @@ -119,7 +174,7 @@ internal class ArgDescriptor<T : Any, TResult>(
fullName: String?,
val number: Int? = null,
description: String? = null,
defaultValue: TResult? = null,
defaultValue: DefaultValue<TResult>? = null,
required: Boolean = true,
deprecatedWarning: String? = null) : Descriptor<T, TResult>(type, fullName, description, defaultValue,
required, deprecatedWarning) {
Expand All @@ -139,7 +194,7 @@ internal class ArgDescriptor<T : Any, TResult>(
get() {
val result = StringBuilder()
result.append(" ${fullName}")
valueDescription(defaultValue)?.let {
defaultValue?.helpMessage?.let {
result.append(it)
}
description?.let { result.append(" -> $it") }
Expand Down
46 changes: 29 additions & 17 deletions core/commonMain/src/Options.kt
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ fun <T : Any, TResult, DefaultType: DefaultRequiredType> AbstractSingleOption<T,
MultipleOption<T, MultipleOptionType.Repeated, DefaultType>(
OptionDescriptor(
optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
description, listOfNotNull(defaultValue),
description, defaultValue?.toMultiple() ?: SimpleDefaultValue(emptyList()),
required, true, delimiter, deprecatedWarning
), owner
)
Expand All @@ -129,7 +129,7 @@ fun <T : Any, DefaultType: DefaultRequiredType> MultipleOption<T, MultipleOption
MultipleOption<T, MultipleOptionType.RepeatedDelimited, DefaultRequiredType>(
OptionDescriptor(
optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
description, defaultValue?.toList() ?: listOf(),
description, defaultValue ?: SimpleDefaultValue(listOf()),
required, true, delimiter, deprecatedWarning
), owner
)
Expand All @@ -138,25 +138,37 @@ fun <T : Any, DefaultType: DefaultRequiredType> MultipleOption<T, MultipleOption
return newOption
}

/**
* Specifies the default value for the option, that will be used when no value is provided for it
* in command line string.
*
* @param value the default value.
*/
fun <T : Any> SingleNullableOption<T>.default(value: T): SingleOption<T, DefaultRequiredType.Default> {
internal fun <T : Any> SingleNullableOption<T>.toOptionWithDefault(value: DefaultValue<T>): SingleOption<T, DefaultRequiredType.Default> {
val newOption = with((delegate as ParsingValue<T, T>).descriptor as OptionDescriptor) {
SingleOption<T, DefaultRequiredType.Default>(
OptionDescriptor(
optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
description, value, required, multiple, delimiter, deprecatedWarning
), owner
OptionDescriptor(
optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
description, value, required, multiple, delimiter, deprecatedWarning
), owner
)
}
owner.entity = newOption
return newOption
}

/**
* Specifies the default value for the option, that will be used when no value is provided for it
* in command line string.
*
* @param value the default value.
*/
fun <T : Any> SingleNullableOption<T>.default(value: T): SingleOption<T, DefaultRequiredType.Default> =
toOptionWithDefault(SimpleDefaultValue(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 the default value.
*/
fun <T : Any> SingleNullableOption<T>.default(value: DefaultValuePattern<T>):
SingleOption<T, DefaultRequiredType.Default> = toOptionWithDefault(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.
Expand All @@ -172,7 +184,7 @@ fun <T : Any, OptionType : MultipleOptionType>
MultipleOption<T, OptionType, DefaultRequiredType.Default>(
OptionDescriptor(
optionFullFormPrefix, optionShortFromPrefix, type, fullName,
shortName, description, value.toList(),
shortName, description, SimpleDefaultValue(value.toList()),
required, multiple, delimiter, deprecatedWarning
), owner
)
Expand Down Expand Up @@ -208,7 +220,7 @@ fun <T : Any, OptionType : MultipleOptionType>
MultipleOption<T, OptionType, DefaultRequiredType.Required>(
OptionDescriptor(
optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
description, defaultValue?.toList() ?: listOf(),
description, defaultValue ?: SimpleDefaultValue(listOf()),
true, multiple, delimiter, deprecatedWarning
), owner
)
Expand All @@ -232,7 +244,7 @@ fun <T : Any, DefaultRequired: DefaultRequiredType> AbstractSingleOption<T, *, D
MultipleOption<T, MultipleOptionType.Delimited, DefaultRequired>(
OptionDescriptor(
optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
description, listOfNotNull(defaultValue),
description, defaultValue?.toMultiple() ?: SimpleDefaultValue(emptyList()),
required, multiple, delimiterValue, deprecatedWarning
), owner
)
Expand All @@ -256,7 +268,7 @@ fun <T : Any, DefaultRequired: DefaultRequiredType> MultipleOption<T, MultipleOp
MultipleOption<T, MultipleOptionType.RepeatedDelimited, DefaultRequired>(
OptionDescriptor(
optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
description, defaultValue?.toList() ?: listOf(),
description, defaultValue ?: SimpleDefaultValue(listOf()),
required, multiple, delimiterValue, deprecatedWarning
), owner
)
Expand Down
17 changes: 17 additions & 0 deletions core/commonTest/src/OptionsTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,23 @@ class OptionsTests {
assertEquals("text", renders[0])
}

@Test
fun testDefaultValuePattern() {
val argParser = ArgParser("testParser")
val useShortForm by argParser.option(ArgType.Boolean, "short", "s",
"Show short version of report").default(false)
val renders by argParser.option(ArgType.Choice(listOf("text", "html", "xml", "json")),
"renders", "r", "Renders for showing information").multiple().default(listOf("text"))
val output = argParser.option(ArgType.String, "output", "o", "Output file")
.default("output.txt")
val outputLog by argParser.option(ArgType.String, "log", "l", "Output log file")
.default(DefaultValuePattern(listOf(output), { values -> "${values[0]}.log"},
{ values -> "${values[0]}.log" } ))
argParser.parse(arrayOf("-o", "out.txt"))
assertEquals("out.txt.log", outputLog)
assertEquals("text", renders[0])
}

@Test
fun testResetOptionsValues() {
val argParser = ArgParser("testParser")
Expand Down