Updated kotlinx.cli to version 0.3.1

This commit is contained in:
Elena Lepilkina
2020-11-25 11:20:40 +03:00
committed by Stanislav Erokhin
parent 2d30796483
commit cb642bca69
11 changed files with 247 additions and 82 deletions
@@ -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)
@@ -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)
}
@@ -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)
}
}