Updated kotlinx.cli to version 0.3.1
This commit is contained in:
committed by
Stanislav Erokhin
parent
2d30796483
commit
cb642bca69
+60
-33
@@ -143,12 +143,12 @@ open class ArgParser(
|
||||
/**
|
||||
* Used prefix form for full option form.
|
||||
*/
|
||||
private val optionFullFormPrefix = if (prefixStyle == OptionPrefixStyle.JVM) "-" else "--"
|
||||
protected val optionFullFormPrefix = if (prefixStyle == OptionPrefixStyle.JVM) "-" else "--"
|
||||
|
||||
/**
|
||||
* Used prefix form for short option form.
|
||||
*/
|
||||
private val optionShortFromPrefix = "-"
|
||||
protected val optionShortFromPrefix = "-"
|
||||
|
||||
/**
|
||||
* Name with all commands that should be executed.
|
||||
@@ -160,6 +160,21 @@ open class ArgParser(
|
||||
*/
|
||||
protected var treatAsOption = true
|
||||
|
||||
/**
|
||||
* Arguments which should be parsed with subcommands.
|
||||
*/
|
||||
private val subcommandsArguments = mutableListOf<String>()
|
||||
|
||||
/**
|
||||
* Options which should be parsed with subcommands.
|
||||
*/
|
||||
private val subcommandsOptions = mutableListOf<String>()
|
||||
|
||||
/**
|
||||
* Subcommand used in commmand line arguments.
|
||||
*/
|
||||
private var usedSubcommand: Subcommand? = null
|
||||
|
||||
/**
|
||||
* The way an option/argument has got its value.
|
||||
*/
|
||||
@@ -229,7 +244,7 @@ open class ArgParser(
|
||||
if (prefixStyle == OptionPrefixStyle.GNU && shortName != null)
|
||||
require(shortName.length == 1) {
|
||||
"""
|
||||
GNU standart for options allow to use short form whuch consists of one character.
|
||||
GNU standart for options allow to use short form which consists of one character.
|
||||
For more information, please, see https://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html
|
||||
""".trimIndent()
|
||||
}
|
||||
@@ -359,7 +374,9 @@ open class ArgParser(
|
||||
*/
|
||||
private fun treatAsArgument(arg: String, argumentsQueue: ArgumentsQueue) {
|
||||
if (!saveAsArg(arg, argumentsQueue)) {
|
||||
printError("Too many arguments! Couldn't process argument $arg!")
|
||||
usedSubcommand?.let {
|
||||
(if (treatAsOption) subcommandsOptions else subcommandsArguments).add(arg)
|
||||
} ?: printError("Too many arguments! Couldn't process argument $arg!")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -416,9 +433,12 @@ open class ArgParser(
|
||||
*
|
||||
* @param argValue argument value with all information about option.
|
||||
*/
|
||||
private fun saveOptionWithoutParameter(argValue: ParsingValue<*, *>) {
|
||||
internal fun saveOptionWithoutParameter(argValue: ParsingValue<*, *>) {
|
||||
// Boolean flags.
|
||||
if (argValue.descriptor.fullName == "help") {
|
||||
usedSubcommand?.let {
|
||||
it.parse(listOf("${it.optionFullFormPrefix}${argValue.descriptor.fullName}"))
|
||||
}
|
||||
println(makeUsage())
|
||||
exitProcess(0)
|
||||
}
|
||||
@@ -569,43 +589,42 @@ open class ArgParser(
|
||||
}
|
||||
|
||||
val argumentsQueue = ArgumentsQueue(arguments.map { it.value.descriptor as ArgDescriptor<*, *> })
|
||||
usedSubcommand = null
|
||||
subcommandsOptions.clear()
|
||||
subcommandsArguments.clear()
|
||||
|
||||
val argIterator = args.listIterator()
|
||||
try {
|
||||
while (argIterator.hasNext()) {
|
||||
val arg = argIterator.next()
|
||||
// Check for subcommands.
|
||||
@OptIn(ExperimentalCli::class)
|
||||
subcommands.forEach { (name, subcommand) ->
|
||||
if (arg == name) {
|
||||
// Use parser for this subcommand.
|
||||
subcommand.parse(args.slice(argIterator.nextIndex() until args.size))
|
||||
subcommand.execute()
|
||||
parsingState = ArgParserResult(name)
|
||||
|
||||
return parsingState!!
|
||||
}
|
||||
}
|
||||
// Parse arguments from command line.
|
||||
if (treatAsOption && arg.startsWith('-')) {
|
||||
// Candidate in being option.
|
||||
// Option is found.
|
||||
if (!(recognizeAndSaveOptionShortForm(arg, argIterator) ||
|
||||
recognizeAndSaveOptionFullForm(arg, argIterator))) {
|
||||
// State is changed so next options are arguments.
|
||||
if (!treatAsOption) {
|
||||
// Argument is found.
|
||||
treatAsArgument(argIterator.next(), argumentsQueue)
|
||||
} else {
|
||||
// Try save as argument.
|
||||
if (!saveAsArg(arg, argumentsQueue)) {
|
||||
printError("Unknown option $arg")
|
||||
if (arg !in subcommands) {
|
||||
// Parse arguments from command line.
|
||||
if (treatAsOption && arg.startsWith('-')) {
|
||||
// Candidate in being option.
|
||||
// Option is found.
|
||||
if (!(recognizeAndSaveOptionShortForm(arg, argIterator) ||
|
||||
recognizeAndSaveOptionFullForm(arg, argIterator))
|
||||
) {
|
||||
// State is changed so next options are arguments.
|
||||
if (!treatAsOption) {
|
||||
// Argument is found.
|
||||
treatAsArgument(argIterator.next(), argumentsQueue)
|
||||
} else {
|
||||
usedSubcommand?.let { subcommandsOptions.add(arg) } ?: run {
|
||||
// Try save as argument.
|
||||
if (!saveAsArg(arg, argumentsQueue)) {
|
||||
printError("Unknown option $arg")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Argument is found.
|
||||
treatAsArgument(arg, argumentsQueue)
|
||||
}
|
||||
} else {
|
||||
// Argument is found.
|
||||
treatAsArgument(arg, argumentsQueue)
|
||||
usedSubcommand = subcommands[arg]
|
||||
}
|
||||
}
|
||||
// Postprocess results of parsing.
|
||||
@@ -618,6 +637,14 @@ open class ArgParser(
|
||||
printError("Value for ${value.descriptor.textDescription} should be always provided in command line.")
|
||||
}
|
||||
}
|
||||
// Parse arguments for subcommand.
|
||||
usedSubcommand?.let {
|
||||
it.parse(subcommandsOptions + listOfNotNull("--".takeUnless { treatAsOption }) + subcommandsArguments)
|
||||
it.execute()
|
||||
parsingState = ArgParserResult(it.name)
|
||||
|
||||
return parsingState!!
|
||||
}
|
||||
} catch (exception: ParsingException) {
|
||||
printError(exception.message!!)
|
||||
}
|
||||
@@ -661,4 +688,4 @@ open class ArgParser(
|
||||
*/
|
||||
internal fun printWarning(message: String) {
|
||||
println("WARNING $message")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,17 +70,46 @@ abstract class ArgType<T : Any>(val hasParameter: kotlin.Boolean) {
|
||||
?: throw ParsingException("Option $name is expected to be double number. $value is provided.")
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Helper for arguments that have limited set of possible values represented as enumeration constants.
|
||||
*/
|
||||
inline fun <reified T: Enum<T>> Choice(
|
||||
noinline toVariant: (kotlin.String) -> T = {
|
||||
enumValues<T>().find { e -> e.toString().equals(it, ignoreCase = true) } ?:
|
||||
throw IllegalArgumentException("No enum constant $it")
|
||||
},
|
||||
noinline toString: (T) -> kotlin.String = { it.toString().toLowerCase() }): Choice<T> {
|
||||
return Choice(enumValues<T>().toList(), toVariant, toString)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type for arguments that have limited set of possible values.
|
||||
*/
|
||||
class Choice(val values: List<kotlin.String>) : ArgType<kotlin.String>(true) {
|
||||
override val description: kotlin.String
|
||||
get() = "{ Value should be one of $values }"
|
||||
class Choice<T: Any>(choices: List<T>,
|
||||
val toVariant: (kotlin.String) -> T,
|
||||
val variantToString: (T) -> kotlin.String = { it.toString() }): ArgType<T>(true) {
|
||||
private val choicesMap: Map<kotlin.String, T> = choices.associateBy { variantToString(it) }
|
||||
|
||||
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.")
|
||||
init {
|
||||
require(choicesMap.size == choices.size) {
|
||||
"Command line representations of enum choices are not distinct"
|
||||
}
|
||||
}
|
||||
|
||||
override val description: kotlin.String
|
||||
get() {
|
||||
return "{ Value should be one of ${choicesMap.keys} }"
|
||||
}
|
||||
|
||||
override fun convert(value: kotlin.String, name: kotlin.String) =
|
||||
try {
|
||||
toVariant(value)
|
||||
} catch (e: Exception) {
|
||||
throw ParsingException("Option $name is expected to be one of ${choicesMap.keys}. $value is provided.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class ParsingException(message: String) : Exception(message)
|
||||
internal class ParsingException(message: String) : Exception(message)
|
||||
|
||||
+6
-6
@@ -159,7 +159,7 @@ class MultipleArgument<T : Any, DefaultRequired: DefaultRequiredType> internal c
|
||||
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.cast<ParsingValue<T, T>>().descriptor as ArgDescriptor) {
|
||||
val newArgument = with((delegate.cast<ParsingValue<T, T>>()).descriptor as ArgDescriptor) {
|
||||
MultipleArgument<T, DefaultRequired>(ArgDescriptor(type, fullName, number, description, listOfNotNull(defaultValue),
|
||||
required, deprecatedWarning), owner)
|
||||
}
|
||||
@@ -172,7 +172,7 @@ fun <T : Any, TResult, DefaultRequired: DefaultRequiredType>
|
||||
*/
|
||||
fun <T : Any, TResult, DefaultRequired: DefaultRequiredType> AbstractSingleArgument<T, TResult, DefaultRequired>.vararg():
|
||||
MultipleArgument<T, DefaultRequired> {
|
||||
val newArgument = with(delegate.cast<ParsingValue<T, T>>().descriptor as ArgDescriptor) {
|
||||
val newArgument = with((delegate.cast<ParsingValue<T, T>>()).descriptor as ArgDescriptor) {
|
||||
MultipleArgument<T, DefaultRequired>(ArgDescriptor(type, fullName, null, description, listOfNotNull(defaultValue),
|
||||
required, deprecatedWarning), owner)
|
||||
}
|
||||
@@ -189,7 +189,7 @@ 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.cast<ParsingValue<T, T>>().descriptor as ArgDescriptor) {
|
||||
val newArgument = with((delegate.cast<ParsingValue<T, T>>()).descriptor as ArgDescriptor) {
|
||||
SingleArgument<T, DefaultRequiredType.Default>(ArgDescriptor(type, fullName, number, description, value,
|
||||
false, deprecatedWarning), owner)
|
||||
}
|
||||
@@ -208,7 +208,7 @@ fun <T: Any> SingleNullableArgument<T>.default(value: T): SingleArgument<T, Defa
|
||||
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.cast<ParsingValue<T, List<T>>>().descriptor as ArgDescriptor) {
|
||||
val newArgument = with((delegate.cast<ParsingValue<T, List<T>>>()).descriptor as ArgDescriptor) {
|
||||
MultipleArgument<T, DefaultRequiredType.Default>(ArgDescriptor(type, fullName, number, description, value.toList(),
|
||||
required, deprecatedWarning), owner)
|
||||
}
|
||||
@@ -224,7 +224,7 @@ fun <T: Any> MultipleArgument<T, DefaultRequiredType.None>.default(value: Collec
|
||||
* Note that only trailing arguments can be optional, i.e. no required arguments can follow optional ones.
|
||||
*/
|
||||
fun <T: Any> SingleArgument<T, DefaultRequiredType.Required>.optional(): SingleNullableArgument<T> {
|
||||
val newArgument = with(delegate.cast<ParsingValue<T, T>>().descriptor as ArgDescriptor) {
|
||||
val newArgument = with((delegate.cast<ParsingValue<T, T>>()).descriptor as ArgDescriptor) {
|
||||
SingleNullableArgument(ArgDescriptor(type, fullName, number, description, defaultValue,
|
||||
false, deprecatedWarning), owner)
|
||||
}
|
||||
@@ -240,7 +240,7 @@ fun <T: Any> SingleArgument<T, DefaultRequiredType.Required>.optional(): SingleN
|
||||
* Note that only trailing arguments can be optional: no required arguments can follow the optional ones.
|
||||
*/
|
||||
fun <T: Any> MultipleArgument<T, DefaultRequiredType.Required>.optional(): MultipleArgument<T, DefaultRequiredType.None> {
|
||||
val newArgument = with(delegate.cast<ParsingValue<T, List<T>>>().descriptor as ArgDescriptor) {
|
||||
val newArgument = with((delegate.cast<ParsingValue<T, List<T>>>()).descriptor as ArgDescriptor) {
|
||||
MultipleArgument<T, DefaultRequiredType.None>(ArgDescriptor(type, fullName, number, description,
|
||||
defaultValue?.toList() ?: listOf(), false, deprecatedWarning), owner)
|
||||
}
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ import kotlin.annotation.AnnotationTarget.*
|
||||
* annotating that usage with the [UseExperimental] annotation, e.g. `@UseExperimental(ExperimentalCli::class)`,
|
||||
* or by using the compiler argument `-Xuse-experimental=kotlinx.cli.ExperimentalCli`.
|
||||
*/
|
||||
@RequiresOptIn("This API is experimental. It may be changed in the future without notice.", level = RequiresOptIn.Level.WARNING)
|
||||
@RequiresOptIn("This API is experimental. It may be changed in the future without notice.", RequiresOptIn.Level.WARNING)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@Target(
|
||||
CLASS,
|
||||
|
||||
@@ -103,7 +103,7 @@ class MultipleOption<T : Any, OptionType : MultipleOptionType, DefaultType: Defa
|
||||
*/
|
||||
fun <T : Any, TResult, DefaultType: DefaultRequiredType> AbstractSingleOption<T, TResult, DefaultType>.multiple():
|
||||
MultipleOption<T, MultipleOptionType.Repeated, DefaultType> {
|
||||
val newOption = with(delegate.cast<ParsingValue<T, T>>().descriptor as OptionDescriptor) {
|
||||
val newOption = with((delegate.cast<ParsingValue<T, T>>()).descriptor as OptionDescriptor) {
|
||||
MultipleOption<T, MultipleOptionType.Repeated, DefaultType>(
|
||||
OptionDescriptor(
|
||||
optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
|
||||
@@ -122,7 +122,7 @@ fun <T : Any, TResult, DefaultType: DefaultRequiredType> AbstractSingleOption<T,
|
||||
*/
|
||||
fun <T : Any, DefaultType: DefaultRequiredType> MultipleOption<T, MultipleOptionType.Delimited, DefaultType>.multiple():
|
||||
MultipleOption<T, MultipleOptionType.RepeatedDelimited, DefaultRequiredType> {
|
||||
val newOption = with(delegate.cast<ParsingValue<T, List<T>>>().descriptor as OptionDescriptor) {
|
||||
val newOption = with((delegate.cast<ParsingValue<T, List<T>>>()).descriptor as OptionDescriptor) {
|
||||
if (multiple) {
|
||||
error("Try to use modifier multiple() twice on option ${fullName ?: ""}")
|
||||
}
|
||||
@@ -145,7 +145,7 @@ fun <T : Any, DefaultType: DefaultRequiredType> MultipleOption<T, MultipleOption
|
||||
* @param value the default value.
|
||||
*/
|
||||
fun <T : Any> SingleNullableOption<T>.default(value: T): SingleOption<T, DefaultRequiredType.Default> {
|
||||
val newOption = with(delegate.cast<ParsingValue<T, T>>().descriptor as OptionDescriptor) {
|
||||
val newOption = with((delegate.cast<ParsingValue<T, T>>()).descriptor as OptionDescriptor) {
|
||||
SingleOption<T, DefaultRequiredType.Default>(
|
||||
OptionDescriptor(
|
||||
optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
|
||||
@@ -167,7 +167,7 @@ fun <T : Any> SingleNullableOption<T>.default(value: T): SingleOption<T, Default
|
||||
fun <T : Any, OptionType : MultipleOptionType>
|
||||
MultipleOption<T, OptionType, DefaultRequiredType.None>.default(value: Collection<T>):
|
||||
MultipleOption<T, OptionType, DefaultRequiredType.Default> {
|
||||
val newOption = with(delegate.cast<ParsingValue<T, List<T>>>().descriptor as OptionDescriptor) {
|
||||
val newOption = with((delegate.cast<ParsingValue<T, List<T>>>()).descriptor as OptionDescriptor) {
|
||||
require(value.isNotEmpty()) { "Default value for option can't be empty collection." }
|
||||
MultipleOption<T, OptionType, DefaultRequiredType.Default>(
|
||||
OptionDescriptor(
|
||||
@@ -185,7 +185,7 @@ fun <T : Any, OptionType : MultipleOptionType>
|
||||
* Requires the option to be always provided in command line.
|
||||
*/
|
||||
fun <T : Any> SingleNullableOption<T>.required(): SingleOption<T, DefaultRequiredType.Required> {
|
||||
val newOption = with(delegate.cast<ParsingValue<T, T>>().descriptor as OptionDescriptor) {
|
||||
val newOption = with((delegate.cast<ParsingValue<T, T>>()).descriptor as OptionDescriptor) {
|
||||
SingleOption<T, DefaultRequiredType.Required>(
|
||||
OptionDescriptor(
|
||||
optionFullFormPrefix, optionShortFromPrefix, type, fullName,
|
||||
@@ -204,7 +204,7 @@ fun <T : Any> SingleNullableOption<T>.required(): SingleOption<T, DefaultRequire
|
||||
fun <T : Any, OptionType : MultipleOptionType>
|
||||
MultipleOption<T, OptionType, DefaultRequiredType.None>.required():
|
||||
MultipleOption<T, OptionType, DefaultRequiredType.Required> {
|
||||
val newOption = with(delegate.cast<ParsingValue<T, List<T>>>().descriptor as OptionDescriptor) {
|
||||
val newOption = with((delegate.cast<ParsingValue<T, List<T>>>()).descriptor as OptionDescriptor) {
|
||||
MultipleOption<T, OptionType, DefaultRequiredType.Required>(
|
||||
OptionDescriptor(
|
||||
optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
|
||||
@@ -228,7 +228,7 @@ fun <T : Any, OptionType : MultipleOptionType>
|
||||
fun <T : Any, DefaultRequired: DefaultRequiredType> AbstractSingleOption<T, *, DefaultRequired>.delimiter(
|
||||
delimiterValue: String):
|
||||
MultipleOption<T, MultipleOptionType.Delimited, DefaultRequired> {
|
||||
val newOption = with(delegate.cast<ParsingValue<T, T>>().descriptor as OptionDescriptor) {
|
||||
val newOption = with((delegate.cast<ParsingValue<T, T>>()).descriptor as OptionDescriptor) {
|
||||
MultipleOption<T, MultipleOptionType.Delimited, DefaultRequired>(
|
||||
OptionDescriptor(
|
||||
optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
|
||||
@@ -252,7 +252,7 @@ fun <T : Any, DefaultRequired: DefaultRequiredType> AbstractSingleOption<T, *, D
|
||||
fun <T : Any, DefaultRequired: DefaultRequiredType> MultipleOption<T, MultipleOptionType.Repeated, DefaultRequired>.delimiter(
|
||||
delimiterValue: String):
|
||||
MultipleOption<T, MultipleOptionType.RepeatedDelimited, DefaultRequired> {
|
||||
val newOption = with(delegate.cast<ParsingValue<T, List<T>>>().descriptor as OptionDescriptor) {
|
||||
val newOption = with((delegate.cast<ParsingValue<T, List<T>>>()).descriptor as OptionDescriptor) {
|
||||
MultipleOption<T, MultipleOptionType.RepeatedDelimited, DefaultRequired>(
|
||||
OptionDescriptor(
|
||||
optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
@file:Suppress("UNUSED_VARIABLE")
|
||||
|
||||
package kotlinx.cli
|
||||
|
||||
import kotlinx.cli.ArgParser
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package kotlinx.cli
|
||||
|
||||
enum class DataSourceEnum {
|
||||
LOCAL,
|
||||
STAGING,
|
||||
PRODUCTION;
|
||||
|
||||
override fun toString(): String = name.toLowerCase()
|
||||
}
|
||||
@@ -3,10 +3,10 @@
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
@file:Suppress("UNUSED_VARIABLE")
|
||||
|
||||
package kotlinx.cli
|
||||
|
||||
import kotlinx.cli.ArgParser
|
||||
import kotlinx.cli.ArgType
|
||||
import kotlin.test.*
|
||||
|
||||
class ErrorTests {
|
||||
@@ -42,15 +42,31 @@ class ErrorTests {
|
||||
assertTrue("Option number is expected to be integer number. out.txt is provided." in exception.message!!)
|
||||
}
|
||||
|
||||
enum class RenderEnum {
|
||||
TEXT,
|
||||
HTML;
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testWrongChoice() {
|
||||
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")),
|
||||
"renders", "r", "Renders for showing information").multiple().default(listOf("text"))
|
||||
val renders by argParser.option(ArgType.Choice<RenderEnum>(),
|
||||
"renders", "r", "Renders for showing information").multiple().default(listOf(RenderEnum.TEXT))
|
||||
val exception = assertFailsWith<IllegalStateException> {
|
||||
argParser.parse(arrayOf("-r", "xml"))
|
||||
}
|
||||
assertTrue("Option renders is expected to be one of [text, html]. xml is provided." in exception.message!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testWrongEnumChoice() {
|
||||
val argParser = ArgParser("testParser")
|
||||
val sources by argParser.option(ArgType.Choice<DataSourceEnum>(),
|
||||
"sources", "s", "Data sources").multiple().default(listOf(DataSourceEnum.PRODUCTION))
|
||||
val exception = assertFailsWith<IllegalStateException> {
|
||||
argParser.parse(arrayOf("-s", "debug"))
|
||||
}
|
||||
assertTrue("Option sources is expected to be one of [local, staging, production]. debug is provided." in exception.message!!)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,20 @@
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
@file:OptIn(ExperimentalCli::class)
|
||||
@file:Suppress("UNUSED_VARIABLE")
|
||||
|
||||
package kotlinx.cli
|
||||
|
||||
import kotlinx.cli.ArgParser
|
||||
import kotlinx.cli.ArgType
|
||||
import kotlinx.cli.ExperimentalCli
|
||||
import kotlinx.cli.Subcommand
|
||||
import kotlin.math.exp
|
||||
import kotlin.test.*
|
||||
|
||||
class HelpTests {
|
||||
enum class Renders {
|
||||
TEXT,
|
||||
HTML,
|
||||
TEAMCITY,
|
||||
STATISTICS,
|
||||
METRICS
|
||||
}
|
||||
@Test
|
||||
fun testHelpMessage() {
|
||||
val argParser = ArgParser("test")
|
||||
@@ -23,8 +27,10 @@ class HelpTests {
|
||||
val epsValue by argParser.option(ArgType.Double, "eps", "e", "Meaningful performance changes").default(1.0)
|
||||
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", "teamcity", "statistics", "metrics")),
|
||||
val renders by argParser.option(ArgType.Choice(listOf("text", "html", "teamcity", "statistics", "metrics"), { it }),
|
||||
shortName = "r", description = "Renders for showing information").multiple().default(listOf("text"))
|
||||
val sources by argParser.option(ArgType.Choice<DataSourceEnum>(),
|
||||
"sources", "ds", "Data sources").multiple().default(listOf(DataSourceEnum.PRODUCTION))
|
||||
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()
|
||||
@@ -40,33 +46,47 @@ Options:
|
||||
--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] }
|
||||
--sources, -ds [production] -> Data sources { Value should be one of [local, staging, production] }
|
||||
--user, -u -> User access information for authorization { String }
|
||||
--help, -h -> Usage info
|
||||
""".trimIndent()
|
||||
assertEquals(expectedOutput, helpOutput)
|
||||
}
|
||||
|
||||
enum class MetricType {
|
||||
SAMPLES,
|
||||
GEOMEAN;
|
||||
|
||||
override fun toString() = name.toLowerCase()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHelpForSubcommands() {
|
||||
class Summary: Subcommand("summary", "Get summary information") {
|
||||
val exec by option(ArgType.Choice(listOf("samples", "geomean")),
|
||||
description = "Execution time way of calculation").default("geomean")
|
||||
val exec by option(ArgType.Choice<MetricType>(),
|
||||
description = "Execution time way of calculation").default(MetricType.GEOMEAN)
|
||||
val execSamples by option(ArgType.String, "exec-samples",
|
||||
description = "Samples used for execution time metric (value 'all' allows use all samples)").delimiter(",")
|
||||
val execNormalize by option(ArgType.String, "exec-normalize",
|
||||
description = "File with golden results which should be used for normalization")
|
||||
val compile by option(ArgType.Choice(listOf("samples", "geomean")),
|
||||
description = "Compile time way of calculation").default("geomean")
|
||||
val compile by option(ArgType.Choice<MetricType>(),
|
||||
description = "Compile time way of calculation").default(MetricType.GEOMEAN)
|
||||
val compileSamples by option(ArgType.String, "compile-samples",
|
||||
description = "Samples used for compile time metric (value 'all' allows use all samples)").delimiter(",")
|
||||
val compileNormalize by option(ArgType.String, "compile-normalize",
|
||||
description = "File with golden results which should be used for normalization")
|
||||
val codesize by option(ArgType.Choice(listOf("samples", "geomean")),
|
||||
description = "Code size way of calculation").default("geomean")
|
||||
val codesize by option(ArgType.Choice<MetricType>(),
|
||||
description = "Code size way of calculation").default(MetricType.GEOMEAN)
|
||||
val codesizeSamples by option(ArgType.String, "codesize-samples",
|
||||
description = "Samples used for code size metric (value 'all' allows use all samples)").delimiter(",")
|
||||
val codesizeNormalize by option(ArgType.String, "codesize-normalize",
|
||||
description = "File with golden results which should be used for normalization")
|
||||
val source by option(ArgType.Choice<DataSourceEnum>(),
|
||||
description = "Data source").default(DataSourceEnum.PRODUCTION)
|
||||
val sourceSamples by option(ArgType.String, "source-samples",
|
||||
description = "Samples used for code size metric (value 'all' allows use all samples)").delimiter(",")
|
||||
val sourceNormalize by option(ArgType.String, "source-normalize",
|
||||
description = "File with golden results which should be used for normalization")
|
||||
val user by option(ArgType.String, shortName = "u", description = "User access information for authorization")
|
||||
val mainReport by argument(ArgType.String, description = "Main report for analysis")
|
||||
|
||||
@@ -94,6 +114,9 @@ Options:
|
||||
--codesize [geomean] -> Code size way of calculation { Value should be one of [samples, geomean] }
|
||||
--codesize-samples -> Samples used for code size metric (value 'all' allows use all samples) { String }
|
||||
--codesize-normalize -> File with golden results which should be used for normalization { String }
|
||||
--source [production] -> Data source { Value should be one of [local, staging, production] }
|
||||
--source-samples -> Samples used for code size metric (value 'all' allows use all samples) { String }
|
||||
--source-normalize -> File with golden results which should be used for normalization { String }
|
||||
--user, -u -> User access information for authorization { String }
|
||||
--help, -h -> Usage info
|
||||
""".trimIndent()
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
@file:Suppress("UNUSED_VARIABLE")
|
||||
|
||||
package kotlinx.cli
|
||||
|
||||
import kotlin.test.*
|
||||
@@ -38,6 +40,13 @@ class OptionsTests {
|
||||
assertEquals("input.txt", input)
|
||||
}
|
||||
|
||||
enum class Renders {
|
||||
TEXT,
|
||||
HTML,
|
||||
XML,
|
||||
JSON
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGNUPrefix() {
|
||||
val argParser = ArgParser("testParser", prefixStyle = ArgParser.OptionPrefixStyle.GNU)
|
||||
@@ -74,26 +83,37 @@ class OptionsTests {
|
||||
fun testMultipleOptions() {
|
||||
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"))
|
||||
argParser.parse(arrayOf("-s", "-r", "text", "-r", "json"))
|
||||
val renders by argParser.option(ArgType.Choice<Renders>(),
|
||||
"renders", "r", "Renders for showing information").multiple().default(listOf(Renders.TEXT))
|
||||
val sources by argParser.option(ArgType.Choice<DataSourceEnum>(),
|
||||
"sources", "ds", "Data sources").multiple().default(listOf(DataSourceEnum.PRODUCTION))
|
||||
argParser.parse(arrayOf("-s", "-r", "text", "-r", "json", "-ds", "local", "-ds", "production"))
|
||||
assertEquals(true, useShortForm)
|
||||
|
||||
assertEquals(2, renders.size)
|
||||
val (firstRender, secondRender) = renders
|
||||
assertEquals("text", firstRender)
|
||||
assertEquals("json", secondRender)
|
||||
assertEquals(Renders.TEXT, firstRender)
|
||||
assertEquals(Renders.JSON, secondRender)
|
||||
|
||||
assertEquals(2, sources.size)
|
||||
val (firstSource, secondSource) = sources
|
||||
assertEquals(DataSourceEnum.LOCAL, firstSource)
|
||||
assertEquals(DataSourceEnum.PRODUCTION, secondSource)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDefaultOptions() {
|
||||
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 renders by argParser.option(ArgType.Choice<Renders>(),
|
||||
"renders", "r", "Renders for showing information").multiple().default(listOf(Renders.TEXT))
|
||||
val sources by argParser.option(ArgType.Choice<DataSourceEnum>(),
|
||||
"sources", "ds", "Data sources").multiple().default(listOf(DataSourceEnum.PRODUCTION))
|
||||
val output by argParser.option(ArgType.String, "output", "o", "Output file")
|
||||
argParser.parse(arrayOf("-o", "out.txt"))
|
||||
assertEquals(false, useShortForm)
|
||||
assertEquals("text", renders[0])
|
||||
assertEquals(Renders.TEXT, renders[0])
|
||||
assertEquals(DataSourceEnum.PRODUCTION, sources[0])
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -101,20 +121,26 @@ class OptionsTests {
|
||||
val argParser = ArgParser("testParser")
|
||||
val useShortFormOption = argParser.option(ArgType.Boolean, "short", "s", "Show short version of report").default(false)
|
||||
var useShortForm by useShortFormOption
|
||||
val rendersOption = argParser.option(ArgType.Choice(listOf("text", "html", "xml", "json")),
|
||||
"renders", "r", "Renders for showing information").multiple().default(listOf("text"))
|
||||
val rendersOption = argParser.option(ArgType.Choice<Renders>(),
|
||||
"renders", "r", "Renders for showing information").multiple().default(listOf(Renders.TEXT))
|
||||
var renders by rendersOption
|
||||
val sourcesOption = argParser.option(ArgType.Choice<DataSourceEnum>(),
|
||||
"sources", "ds", "Data sources").multiple().default(listOf(DataSourceEnum.PRODUCTION))
|
||||
var sources by sourcesOption
|
||||
val outputOption = argParser.option(ArgType.String, "output", "o", "Output file")
|
||||
var output by outputOption
|
||||
argParser.parse(arrayOf("-o", "out.txt"))
|
||||
output = null
|
||||
useShortForm = true
|
||||
renders = listOf()
|
||||
sources = listOf()
|
||||
assertEquals(true, useShortForm)
|
||||
assertEquals(null, output)
|
||||
assertEquals(0, renders.size)
|
||||
assertEquals(0, sources.size)
|
||||
assertEquals(ArgParser.ValueOrigin.REDEFINED, outputOption.valueOrigin)
|
||||
assertEquals(ArgParser.ValueOrigin.REDEFINED, useShortFormOption.valueOrigin)
|
||||
assertEquals(ArgParser.ValueOrigin.REDEFINED, rendersOption.valueOrigin)
|
||||
assertEquals(ArgParser.ValueOrigin.REDEFINED, sourcesOption.valueOrigin)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ class SubcommandsTests {
|
||||
}
|
||||
val action = Summary()
|
||||
argParser.subcommands(action)
|
||||
argParser.parse(arrayOf("-o", "out.txt", "summary", "-i", "2", "3", "5"))
|
||||
argParser.parse(arrayOf("summary", "-o", "out.txt", "-i", "2", "3", "5"))
|
||||
assertEquals("out.txt", output)
|
||||
assertEquals(-10, action.result)
|
||||
}
|
||||
@@ -100,4 +100,37 @@ class SubcommandsTests {
|
||||
argParser.parse(arrayOf("calc", "-i", "summary", "2", "3", "5"))
|
||||
assertEquals(-10, action.result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCommonDefaultInSubcommand() {
|
||||
val parser = ArgParser("testParser")
|
||||
val output by parser.option(ArgType.String, "output", "o", "Output file")
|
||||
.default("any_file")
|
||||
class Summary: Subcommand("summary", "Calculate summary") {
|
||||
val invert by option(ArgType.Boolean, "invert", "i", "Invert results").default(false)
|
||||
val addendums by argument(ArgType.Int, "addendums", description = "Addendums").vararg()
|
||||
var result: Int = 0
|
||||
|
||||
override fun execute() {
|
||||
result = addendums.sum()
|
||||
result = if (invert) -1 * result else result
|
||||
println("result is: $result and will output to $output")
|
||||
}
|
||||
}
|
||||
class Multiply: Subcommand("mul", "Multiply") {
|
||||
val numbers by argument(ArgType.Int, description = "Addendums").vararg()
|
||||
var result: Int = 0
|
||||
|
||||
override fun execute() {
|
||||
result = numbers.reduce{ acc, it -> acc * it }
|
||||
}
|
||||
}
|
||||
val summary = Summary()
|
||||
val multiple = Multiply()
|
||||
parser.subcommands(summary, multiple)
|
||||
|
||||
parser.parse(arrayOf("summary", "1", "2", "4"))
|
||||
assertEquals("any_file", output)
|
||||
assertEquals(7, summary.result)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user