diff --git a/kotlin-native/endorsedLibraries/kotlinx.cli/src/main/kotlin/kotlinx/cli/ArgParser.kt b/kotlin-native/endorsedLibraries/kotlinx.cli/src/main/kotlin/kotlinx/cli/ArgParser.kt index 241ececc491..01362c149b6 100644 --- a/kotlin-native/endorsedLibraries/kotlinx.cli/src/main/kotlin/kotlinx/cli/ArgParser.kt +++ b/kotlin-native/endorsedLibraries/kotlinx.cli/src/main/kotlin/kotlinx/cli/ArgParser.kt @@ -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() + + /** + * Options which should be parsed with subcommands. + */ + private val subcommandsOptions = mutableListOf() + + /** + * 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") -} \ No newline at end of file +} diff --git a/kotlin-native/endorsedLibraries/kotlinx.cli/src/main/kotlin/kotlinx/cli/ArgType.kt b/kotlin-native/endorsedLibraries/kotlinx.cli/src/main/kotlin/kotlinx/cli/ArgType.kt index d5da8e21da6..333aa89e915 100644 --- a/kotlin-native/endorsedLibraries/kotlinx.cli/src/main/kotlin/kotlinx/cli/ArgType.kt +++ b/kotlin-native/endorsedLibraries/kotlinx.cli/src/main/kotlin/kotlinx/cli/ArgType.kt @@ -70,17 +70,46 @@ abstract class ArgType(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 > Choice( + noinline toVariant: (kotlin.String) -> T = { + enumValues().find { e -> e.toString().equals(it, ignoreCase = true) } ?: + throw IllegalArgumentException("No enum constant $it") + }, + noinline toString: (T) -> kotlin.String = { it.toString().toLowerCase() }): Choice { + return Choice(enumValues().toList(), toVariant, toString) + } + } + /** * Type for arguments that have limited set of possible values. */ - class Choice(val values: List) : ArgType(true) { - override val description: kotlin.String - get() = "{ Value should be one of $values }" + class Choice(choices: List, + val toVariant: (kotlin.String) -> T, + val variantToString: (T) -> kotlin.String = { it.toString() }): ArgType(true) { + private val choicesMap: Map = 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) \ No newline at end of file +internal class ParsingException(message: String) : Exception(message) diff --git a/kotlin-native/endorsedLibraries/kotlinx.cli/src/main/kotlin/kotlinx/cli/Arguments.kt b/kotlin-native/endorsedLibraries/kotlinx.cli/src/main/kotlin/kotlinx/cli/Arguments.kt index e25d860e5dc..23e3019b281 100644 --- a/kotlin-native/endorsedLibraries/kotlinx.cli/src/main/kotlin/kotlinx/cli/Arguments.kt +++ b/kotlin-native/endorsedLibraries/kotlinx.cli/src/main/kotlin/kotlinx/cli/Arguments.kt @@ -159,7 +159,7 @@ class MultipleArgument internal c fun AbstractSingleArgument.multiple(number: Int): MultipleArgument { require(number >= 2) { "multiple() modifier with value less than 2 is unavailable. It's already set to 1." } - val newArgument = with(delegate.cast>().descriptor as ArgDescriptor) { + val newArgument = with((delegate.cast>()).descriptor as ArgDescriptor) { MultipleArgument(ArgDescriptor(type, fullName, number, description, listOfNotNull(defaultValue), required, deprecatedWarning), owner) } @@ -172,7 +172,7 @@ fun */ fun AbstractSingleArgument.vararg(): MultipleArgument { - val newArgument = with(delegate.cast>().descriptor as ArgDescriptor) { + val newArgument = with((delegate.cast>()).descriptor as ArgDescriptor) { MultipleArgument(ArgDescriptor(type, fullName, null, description, listOfNotNull(defaultValue), required, deprecatedWarning), owner) } @@ -189,7 +189,7 @@ fun AbstractSingleArgum * @param value the default value. */ fun SingleNullableArgument.default(value: T): SingleArgument { - val newArgument = with(delegate.cast>().descriptor as ArgDescriptor) { + val newArgument = with((delegate.cast>()).descriptor as ArgDescriptor) { SingleArgument(ArgDescriptor(type, fullName, number, description, value, false, deprecatedWarning), owner) } @@ -208,7 +208,7 @@ fun SingleNullableArgument.default(value: T): SingleArgument MultipleArgument.default(value: Collection): MultipleArgument { require (value.isNotEmpty()) { "Default value for argument can't be empty collection." } - val newArgument = with(delegate.cast>>().descriptor as ArgDescriptor) { + val newArgument = with((delegate.cast>>()).descriptor as ArgDescriptor) { MultipleArgument(ArgDescriptor(type, fullName, number, description, value.toList(), required, deprecatedWarning), owner) } @@ -224,7 +224,7 @@ fun MultipleArgument.default(value: Collec * Note that only trailing arguments can be optional, i.e. no required arguments can follow optional ones. */ fun SingleArgument.optional(): SingleNullableArgument { - val newArgument = with(delegate.cast>().descriptor as ArgDescriptor) { + val newArgument = with((delegate.cast>()).descriptor as ArgDescriptor) { SingleNullableArgument(ArgDescriptor(type, fullName, number, description, defaultValue, false, deprecatedWarning), owner) } @@ -240,7 +240,7 @@ fun SingleArgument.optional(): SingleN * Note that only trailing arguments can be optional: no required arguments can follow the optional ones. */ fun MultipleArgument.optional(): MultipleArgument { - val newArgument = with(delegate.cast>>().descriptor as ArgDescriptor) { + val newArgument = with((delegate.cast>>()).descriptor as ArgDescriptor) { MultipleArgument(ArgDescriptor(type, fullName, number, description, defaultValue?.toList() ?: listOf(), false, deprecatedWarning), owner) } diff --git a/kotlin-native/endorsedLibraries/kotlinx.cli/src/main/kotlin/kotlinx/cli/ExperimentalCli.kt b/kotlin-native/endorsedLibraries/kotlinx.cli/src/main/kotlin/kotlinx/cli/ExperimentalCli.kt index d35f6be01bf..7f8f2e9fc14 100644 --- a/kotlin-native/endorsedLibraries/kotlinx.cli/src/main/kotlin/kotlinx/cli/ExperimentalCli.kt +++ b/kotlin-native/endorsedLibraries/kotlinx.cli/src/main/kotlin/kotlinx/cli/ExperimentalCli.kt @@ -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, diff --git a/kotlin-native/endorsedLibraries/kotlinx.cli/src/main/kotlin/kotlinx/cli/Options.kt b/kotlin-native/endorsedLibraries/kotlinx.cli/src/main/kotlin/kotlinx/cli/Options.kt index 69999451842..defa00dd4ca 100644 --- a/kotlin-native/endorsedLibraries/kotlinx.cli/src/main/kotlin/kotlinx/cli/Options.kt +++ b/kotlin-native/endorsedLibraries/kotlinx.cli/src/main/kotlin/kotlinx/cli/Options.kt @@ -103,7 +103,7 @@ class MultipleOption AbstractSingleOption.multiple(): MultipleOption { - val newOption = with(delegate.cast>().descriptor as OptionDescriptor) { + val newOption = with((delegate.cast>()).descriptor as OptionDescriptor) { MultipleOption( OptionDescriptor( optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName, @@ -122,7 +122,7 @@ fun AbstractSingleOption MultipleOption.multiple(): MultipleOption { - val newOption = with(delegate.cast>>().descriptor as OptionDescriptor) { + val newOption = with((delegate.cast>>()).descriptor as OptionDescriptor) { if (multiple) { error("Try to use modifier multiple() twice on option ${fullName ?: ""}") } @@ -145,7 +145,7 @@ fun MultipleOption SingleNullableOption.default(value: T): SingleOption { - val newOption = with(delegate.cast>().descriptor as OptionDescriptor) { + val newOption = with((delegate.cast>()).descriptor as OptionDescriptor) { SingleOption( OptionDescriptor( optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName, @@ -167,7 +167,7 @@ fun SingleNullableOption.default(value: T): SingleOption MultipleOption.default(value: Collection): MultipleOption { - val newOption = with(delegate.cast>>().descriptor as OptionDescriptor) { + val newOption = with((delegate.cast>>()).descriptor as OptionDescriptor) { require(value.isNotEmpty()) { "Default value for option can't be empty collection." } MultipleOption( OptionDescriptor( @@ -185,7 +185,7 @@ fun * Requires the option to be always provided in command line. */ fun SingleNullableOption.required(): SingleOption { - val newOption = with(delegate.cast>().descriptor as OptionDescriptor) { + val newOption = with((delegate.cast>()).descriptor as OptionDescriptor) { SingleOption( OptionDescriptor( optionFullFormPrefix, optionShortFromPrefix, type, fullName, @@ -204,7 +204,7 @@ fun SingleNullableOption.required(): SingleOption MultipleOption.required(): MultipleOption { - val newOption = with(delegate.cast>>().descriptor as OptionDescriptor) { + val newOption = with((delegate.cast>>()).descriptor as OptionDescriptor) { MultipleOption( OptionDescriptor( optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName, @@ -228,7 +228,7 @@ fun fun AbstractSingleOption.delimiter( delimiterValue: String): MultipleOption { - val newOption = with(delegate.cast>().descriptor as OptionDescriptor) { + val newOption = with((delegate.cast>()).descriptor as OptionDescriptor) { MultipleOption( OptionDescriptor( optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName, @@ -252,7 +252,7 @@ fun AbstractSingleOption MultipleOption.delimiter( delimiterValue: String): MultipleOption { - val newOption = with(delegate.cast>>().descriptor as OptionDescriptor) { + val newOption = with((delegate.cast>>()).descriptor as OptionDescriptor) { MultipleOption( OptionDescriptor( optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName, diff --git a/kotlin-native/endorsedLibraries/kotlinx.cli/src/tests/ArgumentsTests.kt b/kotlin-native/endorsedLibraries/kotlinx.cli/src/tests/ArgumentsTests.kt index db49aa8e2b4..b5e737cfdf1 100644 --- a/kotlin-native/endorsedLibraries/kotlinx.cli/src/tests/ArgumentsTests.kt +++ b/kotlin-native/endorsedLibraries/kotlinx.cli/src/tests/ArgumentsTests.kt @@ -3,6 +3,8 @@ * that can be found in the LICENSE file. */ +@file:Suppress("UNUSED_VARIABLE") + package kotlinx.cli import kotlinx.cli.ArgParser diff --git a/kotlin-native/endorsedLibraries/kotlinx.cli/src/tests/DataSourceEnum.kt b/kotlin-native/endorsedLibraries/kotlinx.cli/src/tests/DataSourceEnum.kt new file mode 100644 index 00000000000..e9f43ec062b --- /dev/null +++ b/kotlin-native/endorsedLibraries/kotlinx.cli/src/tests/DataSourceEnum.kt @@ -0,0 +1,9 @@ +package kotlinx.cli + +enum class DataSourceEnum { + LOCAL, + STAGING, + PRODUCTION; + + override fun toString(): String = name.toLowerCase() +} diff --git a/kotlin-native/endorsedLibraries/kotlinx.cli/src/tests/ErrorTests.kt b/kotlin-native/endorsedLibraries/kotlinx.cli/src/tests/ErrorTests.kt index 411d3f48738..1e5cf5b9055 100644 --- a/kotlin-native/endorsedLibraries/kotlinx.cli/src/tests/ErrorTests.kt +++ b/kotlin-native/endorsedLibraries/kotlinx.cli/src/tests/ErrorTests.kt @@ -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(), + "renders", "r", "Renders for showing information").multiple().default(listOf(RenderEnum.TEXT)) val exception = assertFailsWith { 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(), + "sources", "s", "Data sources").multiple().default(listOf(DataSourceEnum.PRODUCTION)) + val exception = assertFailsWith { + argParser.parse(arrayOf("-s", "debug")) + } + assertTrue("Option sources is expected to be one of [local, staging, production]. debug is provided." in exception.message!!) + } } diff --git a/kotlin-native/endorsedLibraries/kotlinx.cli/src/tests/HelpTests.kt b/kotlin-native/endorsedLibraries/kotlinx.cli/src/tests/HelpTests.kt index eafa058a663..eef98fb8881 100644 --- a/kotlin-native/endorsedLibraries/kotlinx.cli/src/tests/HelpTests.kt +++ b/kotlin-native/endorsedLibraries/kotlinx.cli/src/tests/HelpTests.kt @@ -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(), + "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(), + 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(), + 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(), + 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(), + 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() diff --git a/kotlin-native/endorsedLibraries/kotlinx.cli/src/tests/OptionsTests.kt b/kotlin-native/endorsedLibraries/kotlinx.cli/src/tests/OptionsTests.kt index 8a883c1fca3..df25e5ad1b3 100644 --- a/kotlin-native/endorsedLibraries/kotlinx.cli/src/tests/OptionsTests.kt +++ b/kotlin-native/endorsedLibraries/kotlinx.cli/src/tests/OptionsTests.kt @@ -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", "r", "Renders for showing information").multiple().default(listOf(Renders.TEXT)) + val sources by argParser.option(ArgType.Choice(), + "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", "r", "Renders for showing information").multiple().default(listOf(Renders.TEXT)) + val sources by argParser.option(ArgType.Choice(), + "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", "r", "Renders for showing information").multiple().default(listOf(Renders.TEXT)) var renders by rendersOption + val sourcesOption = argParser.option(ArgType.Choice(), + "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) } } diff --git a/kotlin-native/endorsedLibraries/kotlinx.cli/src/tests/SubcommandsTests.kt b/kotlin-native/endorsedLibraries/kotlinx.cli/src/tests/SubcommandsTests.kt index 2d13d10d11b..02a5b59857a 100644 --- a/kotlin-native/endorsedLibraries/kotlinx.cli/src/tests/SubcommandsTests.kt +++ b/kotlin-native/endorsedLibraries/kotlinx.cli/src/tests/SubcommandsTests.kt @@ -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) + } }