CLI fixes (#3331)
This commit is contained in:
@@ -180,14 +180,14 @@ open class ArgParser(val programName: String, var useDefaultHelpShortName: Boole
|
|||||||
// Previous argument has default value.
|
// Previous argument has default value.
|
||||||
it.descriptor.defaultValue?.let {
|
it.descriptor.defaultValue?.let {
|
||||||
if (currentArgument.descriptor.defaultValue == null && currentArgument.descriptor.required) {
|
if (currentArgument.descriptor.defaultValue == null && currentArgument.descriptor.required) {
|
||||||
printWarning("Default value of argument ${previousArgument.descriptor.fullName} will be unused, " +
|
printError("Default value of argument ${previousArgument.descriptor.fullName} will be unused, " +
|
||||||
"because next argument ${currentArgument.descriptor.fullName} is always required and has no default value.")
|
"because next argument ${currentArgument.descriptor.fullName} is always required and has no default value.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Previous argument is optional.
|
// Previous argument is optional.
|
||||||
if (!it.descriptor.required) {
|
if (!it.descriptor.required) {
|
||||||
if (currentArgument.descriptor.defaultValue == null && currentArgument.descriptor.required) {
|
if (currentArgument.descriptor.defaultValue == null && currentArgument.descriptor.required) {
|
||||||
printWarning("Argument ${previousArgument.descriptor.fullName} will be always required, " +
|
printError("Argument ${previousArgument.descriptor.fullName} will be always required, " +
|
||||||
"because next argument ${currentArgument.descriptor.fullName} is always required.")
|
"because next argument ${currentArgument.descriptor.fullName} is always required.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -313,7 +313,7 @@ open class ArgParser(val programName: String, var useDefaultHelpShortName: Boole
|
|||||||
|
|
||||||
// Add default list with arguments if there can be extra free arguments.
|
// Add default list with arguments if there can be extra free arguments.
|
||||||
if (skipExtraArguments) {
|
if (skipExtraArguments) {
|
||||||
argument(ArgType.String, "").number()
|
argument(ArgType.String, "").vararg()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map declared options and arguments to maps.
|
// Map declared options and arguments to maps.
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ class SingleNullableArgument<T : Any> internal constructor(descriptor: ArgDescri
|
|||||||
class MultipleArgument<T : Any> internal constructor(descriptor: ArgDescriptor<T, List<T>>, owner: CLIEntityWrapper):
|
class MultipleArgument<T : Any> internal constructor(descriptor: ArgDescriptor<T, List<T>>, owner: CLIEntityWrapper):
|
||||||
Argument<List<T>>(owner) {
|
Argument<List<T>>(owner) {
|
||||||
init {
|
init {
|
||||||
if (descriptor.number != null && descriptor.number == 1) {
|
if (descriptor.number != null && descriptor.number < 2) {
|
||||||
failAssertion("Argument with multiple values can't be initialized with descriptor for single one.")
|
failAssertion("Argument with multiple values can't be initialized with descriptor for single one.")
|
||||||
}
|
}
|
||||||
delegate = ArgumentMultipleValues(descriptor)
|
delegate = ArgumentMultipleValues(descriptor)
|
||||||
@@ -97,11 +97,11 @@ class MultipleArgument<T : Any> internal constructor(descriptor: ArgDescriptor<T
|
|||||||
/**
|
/**
|
||||||
* Allow argument have several values.
|
* Allow argument have several values.
|
||||||
*
|
*
|
||||||
* @param number number of arguments are expected. In case of null value any number of arguments can be set.
|
* @param value number of arguments are expected.
|
||||||
*/
|
*/
|
||||||
fun <T : Any, TResult> AbstractSingleArgument<T, TResult>.number(value: Int? = null): MultipleArgument<T> {
|
fun <T : Any, TResult> AbstractSingleArgument<T, TResult>.multiple(value: Int): MultipleArgument<T> {
|
||||||
if (value != null && value == 1) {
|
if (value < 2) {
|
||||||
error("number() modifier with value 1 is unavailable. It's already set to 1.")
|
error("multiple() modifier with value less than 2 is unavailable. It's already set to 1.")
|
||||||
}
|
}
|
||||||
val newArgument = with((delegate as ParsingValue<T, T>).descriptor as ArgDescriptor) {
|
val newArgument = with((delegate as ParsingValue<T, T>).descriptor as ArgDescriptor) {
|
||||||
MultipleArgument(ArgDescriptor(type, fullName, value, description, listOfNotNull(defaultValue),
|
MultipleArgument(ArgDescriptor(type, fullName, value, description, listOfNotNull(defaultValue),
|
||||||
@@ -111,6 +111,18 @@ fun <T : Any, TResult> AbstractSingleArgument<T, TResult>.number(value: Int? = n
|
|||||||
return newArgument
|
return newArgument
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow argument have several values.
|
||||||
|
*/
|
||||||
|
fun <T : Any, TResult> AbstractSingleArgument<T, TResult>.vararg(): MultipleArgument<T> {
|
||||||
|
val newArgument = with((delegate as ParsingValue<T, T>).descriptor as ArgDescriptor) {
|
||||||
|
MultipleArgument(ArgDescriptor(type, fullName, null, description, listOfNotNull(defaultValue),
|
||||||
|
required, deprecatedWarning), owner)
|
||||||
|
}
|
||||||
|
owner.entity = newArgument
|
||||||
|
return newArgument
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set default value for argument.
|
* Set default value for argument.
|
||||||
*
|
*
|
||||||
@@ -130,6 +142,9 @@ fun <T: Any, TResult> AbstractSingleArgument<T, TResult>.default(value: T): Sing
|
|||||||
* @param value default value.
|
* @param value default value.
|
||||||
*/
|
*/
|
||||||
fun <T: Any> MultipleArgument<T>.default(value: Collection<T>): MultipleArgument<T> {
|
fun <T: Any> MultipleArgument<T>.default(value: Collection<T>): MultipleArgument<T> {
|
||||||
|
if (value.isEmpty()) {
|
||||||
|
error("Default value for argument can't be empty collection.")
|
||||||
|
}
|
||||||
val newArgument = with((delegate as ParsingValue<T, List<T>>).descriptor as ArgDescriptor) {
|
val newArgument = with((delegate as ParsingValue<T, List<T>>).descriptor as ArgDescriptor) {
|
||||||
MultipleArgument(ArgDescriptor(type, fullName, number, description, value.toList(),
|
MultipleArgument(ArgDescriptor(type, fullName, number, description, value.toList(),
|
||||||
required, deprecatedWarning), owner)
|
required, deprecatedWarning), owner)
|
||||||
@@ -141,7 +156,7 @@ fun <T: Any> MultipleArgument<T>.default(value: Collection<T>): MultipleArgument
|
|||||||
/**
|
/**
|
||||||
* Allow argument be unprovided in command line.
|
* Allow argument be unprovided in command line.
|
||||||
*/
|
*/
|
||||||
fun <T: Any, TResult> AbstractSingleArgument<T, TResult>.optional(): SingleNullableArgument<T> {
|
fun <T: Any> SingleArgument<T>.optional(): SingleNullableArgument<T> {
|
||||||
val newArgument = with((delegate as ParsingValue<T, T>).descriptor as ArgDescriptor) {
|
val newArgument = with((delegate as ParsingValue<T, T>).descriptor as ArgDescriptor) {
|
||||||
SingleNullableArgument(ArgDescriptor(type, fullName, number, description, defaultValue,
|
SingleNullableArgument(ArgDescriptor(type, fullName, number, description, defaultValue,
|
||||||
false, deprecatedWarning), owner)
|
false, deprecatedWarning), owner)
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ internal class ArgDescriptor<T : Any, TResult>(
|
|||||||
init {
|
init {
|
||||||
// Check arguments number correctness.
|
// Check arguments number correctness.
|
||||||
number?.let {
|
number?.let {
|
||||||
if (it < 0)
|
if (it < 1)
|
||||||
error("Number of arguments for argument description $fullName should be greater than zero.")
|
error("Number of arguments for argument description $fullName should be greater than zero.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,9 +116,6 @@ fun <T : Any> MultipleOption<T, DelimitedOption>.multiple(): MultipleOption<T, R
|
|||||||
*/
|
*/
|
||||||
fun <T: Any, TResult> AbstractSingleOption<T, TResult>.default(value: T): SingleOption<T> {
|
fun <T: Any, TResult> AbstractSingleOption<T, TResult>.default(value: T): SingleOption<T> {
|
||||||
val newOption = with((delegate as ParsingValue<T, T>).descriptor as OptionDescriptor) {
|
val newOption = with((delegate as ParsingValue<T, T>).descriptor as OptionDescriptor) {
|
||||||
if (required) {
|
|
||||||
printWarning("required() is unneeded, because option with default value is defined.")
|
|
||||||
}
|
|
||||||
SingleOption(OptionDescriptor(optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
|
SingleOption(OptionDescriptor(optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
|
||||||
description, value, required, multiple, delimiter, deprecatedWarning), owner)
|
description, value, required, multiple, delimiter, deprecatedWarning), owner)
|
||||||
}
|
}
|
||||||
@@ -134,8 +131,8 @@ fun <T: Any, TResult> AbstractSingleOption<T, TResult>.default(value: T): Single
|
|||||||
fun <T: Any, OptionType: MultipleOptionType>
|
fun <T: Any, OptionType: MultipleOptionType>
|
||||||
MultipleOption<T, OptionType>.default(value: Collection<T>): MultipleOption<T, OptionType> {
|
MultipleOption<T, OptionType>.default(value: Collection<T>): MultipleOption<T, OptionType> {
|
||||||
val newOption = with((delegate as ParsingValue<T, List<T>>).descriptor as OptionDescriptor) {
|
val newOption = with((delegate as ParsingValue<T, List<T>>).descriptor as OptionDescriptor) {
|
||||||
if (required) {
|
if (value.isEmpty()) {
|
||||||
printWarning("required() is unneeded, because option with default value is defined.")
|
error("Default value for option can't be empty collection.")
|
||||||
}
|
}
|
||||||
MultipleOption<T, OptionType>(OptionDescriptor(optionFullFormPrefix, optionShortFromPrefix, type, fullName,
|
MultipleOption<T, OptionType>(OptionDescriptor(optionFullFormPrefix, optionShortFromPrefix, type, fullName,
|
||||||
shortName, description, value.toList(),
|
shortName, description, value.toList(),
|
||||||
@@ -148,11 +145,8 @@ fun <T: Any, OptionType: MultipleOptionType>
|
|||||||
/**
|
/**
|
||||||
* Require option to be always provided in command line.
|
* Require option to be always provided in command line.
|
||||||
*/
|
*/
|
||||||
fun <T: Any, TResult> AbstractSingleOption<T, TResult>.required(): SingleOption<T> {
|
fun <T: Any> SingleNullableOption<T>.required(): SingleOption<T> {
|
||||||
val newOption = with((delegate as ParsingValue<T, T>).descriptor as OptionDescriptor) {
|
val newOption = with((delegate as ParsingValue<T, T>).descriptor as OptionDescriptor) {
|
||||||
defaultValue?.let {
|
|
||||||
printWarning("required() is unneeded, because option with default value is defined.")
|
|
||||||
}
|
|
||||||
SingleOption(OptionDescriptor(optionFullFormPrefix, optionShortFromPrefix, type, fullName,
|
SingleOption(OptionDescriptor(optionFullFormPrefix, optionShortFromPrefix, type, fullName,
|
||||||
shortName, description, defaultValue,
|
shortName, description, defaultValue,
|
||||||
true, multiple, delimiter, deprecatedWarning), owner)
|
true, multiple, delimiter, deprecatedWarning), owner)
|
||||||
@@ -167,9 +161,6 @@ fun <T: Any, TResult> AbstractSingleOption<T, TResult>.required(): SingleOption<
|
|||||||
fun <T: Any, OptionType: MultipleOptionType>
|
fun <T: Any, OptionType: MultipleOptionType>
|
||||||
MultipleOption<T, OptionType>.required(): MultipleOption<T, OptionType> {
|
MultipleOption<T, OptionType>.required(): MultipleOption<T, OptionType> {
|
||||||
val newOption = with((delegate as ParsingValue<T, List<T>>).descriptor as OptionDescriptor) {
|
val newOption = with((delegate as ParsingValue<T, List<T>>).descriptor as OptionDescriptor) {
|
||||||
if (required) {
|
|
||||||
printWarning("required() is unneeded, because option with default value is defined.")
|
|
||||||
}
|
|
||||||
MultipleOption<T, OptionType>(OptionDescriptor(optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
|
MultipleOption<T, OptionType>(OptionDescriptor(optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
|
||||||
description, defaultValue?.toList() ?: listOf(),
|
description, defaultValue?.toList() ?: listOf(),
|
||||||
true, multiple, delimiter, deprecatedWarning), owner)
|
true, multiple, delimiter, deprecatedWarning), owner)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class ArgumentsTests {
|
|||||||
fun testArgumetsWithAnyNumberOfValues() {
|
fun testArgumetsWithAnyNumberOfValues() {
|
||||||
val argParser = ArgParser("testParser")
|
val argParser = ArgParser("testParser")
|
||||||
val output by argParser.argument(ArgType.String, "output", "Output file")
|
val output by argParser.argument(ArgType.String, "output", "Output file")
|
||||||
val inputs by argParser.argument(ArgType.String, description = "Input files").number()
|
val inputs by argParser.argument(ArgType.String, description = "Input files").vararg()
|
||||||
argParser.parse(arrayOf("out.txt", "input1.txt", "input2.txt", "input3.txt",
|
argParser.parse(arrayOf("out.txt", "input1.txt", "input2.txt", "input3.txt",
|
||||||
"input4.txt"))
|
"input4.txt"))
|
||||||
assertEquals("out.txt", output)
|
assertEquals("out.txt", output)
|
||||||
@@ -36,7 +36,7 @@ class ArgumentsTests {
|
|||||||
@Test
|
@Test
|
||||||
fun testArgumetsWithSeveralValues() {
|
fun testArgumetsWithSeveralValues() {
|
||||||
val argParser = ArgParser("testParser")
|
val argParser = ArgParser("testParser")
|
||||||
val addendums by argParser.argument(ArgType.Int, "addendums", description = "Addendums").number(2)
|
val addendums by argParser.argument(ArgType.Int, "addendums", description = "Addendums").multiple(2)
|
||||||
val output by argParser.argument(ArgType.String, "output", "Output file")
|
val output by argParser.argument(ArgType.String, "output", "Output file")
|
||||||
val debugMode by argParser.option(ArgType.Boolean, "debug", "d", "Debug mode")
|
val debugMode by argParser.option(ArgType.Boolean, "debug", "d", "Debug mode")
|
||||||
argParser.parse(arrayOf("2", "-d", "3", "out.txt"))
|
argParser.parse(arrayOf("2", "-d", "3", "out.txt"))
|
||||||
@@ -50,7 +50,7 @@ class ArgumentsTests {
|
|||||||
@Test
|
@Test
|
||||||
fun testSkippingExtraArguments() {
|
fun testSkippingExtraArguments() {
|
||||||
val argParser = ArgParser("testParser", skipExtraArguments = true)
|
val argParser = ArgParser("testParser", skipExtraArguments = true)
|
||||||
val addendums by argParser.argument(ArgType.Int, "addendums", description = "Addendums").number(2)
|
val addendums by argParser.argument(ArgType.Int, "addendums", description = "Addendums").multiple(2)
|
||||||
val output by argParser.argument(ArgType.String, "output", "Output file")
|
val output by argParser.argument(ArgType.String, "output", "Output file")
|
||||||
val debugMode by argParser.option(ArgType.Boolean, "debug", "d", "Debug mode")
|
val debugMode by argParser.option(ArgType.Boolean, "debug", "d", "Debug mode")
|
||||||
argParser.parse(arrayOf("2", "-d", "3", "out.txt", "something", "else", "in", "string"))
|
argParser.parse(arrayOf("2", "-d", "3", "out.txt", "something", "else", "in", "string"))
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class ErrorTests {
|
|||||||
@Test
|
@Test
|
||||||
fun testExtraArguments() {
|
fun testExtraArguments() {
|
||||||
val argParser = ArgParser("testParser")
|
val argParser = ArgParser("testParser")
|
||||||
val addendums by argParser.argument(ArgType.Int, "addendums", description = "Addendums").number(2)
|
val addendums by argParser.argument(ArgType.Int, "addendums", description = "Addendums").multiple(2)
|
||||||
val output by argParser.argument(ArgType.String, "output", "Output file")
|
val output by argParser.argument(ArgType.String, "output", "Output file")
|
||||||
val debugMode by argParser.option(ArgType.Boolean, "debug", "d", "Debug mode")
|
val debugMode by argParser.option(ArgType.Boolean, "debug", "d", "Debug mode")
|
||||||
val exception = assertFailsWith<IllegalStateException> { argParser.parse(
|
val exception = assertFailsWith<IllegalStateException> { argParser.parse(
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class SubcommandsTests {
|
|||||||
val output by argParser.option(ArgType.String, "output", "o", "Output file")
|
val output by argParser.option(ArgType.String, "output", "o", "Output file")
|
||||||
class Summary: Subcommand("summary") {
|
class Summary: Subcommand("summary") {
|
||||||
val invert by option(ArgType.Boolean, "invert", "i", "Invert results")
|
val invert by option(ArgType.Boolean, "invert", "i", "Invert results")
|
||||||
val addendums by argument(ArgType.Int, "addendums", description = "Addendums").number()
|
val addendums by argument(ArgType.Int, "addendums", description = "Addendums").vararg()
|
||||||
var result: Int = 0
|
var result: Int = 0
|
||||||
|
|
||||||
override fun execute() {
|
override fun execute() {
|
||||||
@@ -36,7 +36,7 @@ class SubcommandsTests {
|
|||||||
@Test
|
@Test
|
||||||
fun testCommonOptions() {
|
fun testCommonOptions() {
|
||||||
abstract class CommonOptions(name: String): Subcommand(name) {
|
abstract class CommonOptions(name: String): Subcommand(name) {
|
||||||
val numbers by argument(ArgType.Int, "numbers", description = "Numbers").number()
|
val numbers by argument(ArgType.Int, "numbers", description = "Numbers").vararg()
|
||||||
}
|
}
|
||||||
class Summary: CommonOptions("summary") {
|
class Summary: CommonOptions("summary") {
|
||||||
val invert by option(ArgType.Boolean, "invert", "i", "Invert results")
|
val invert by option(ArgType.Boolean, "invert", "i", "Invert results")
|
||||||
@@ -74,7 +74,7 @@ class SubcommandsTests {
|
|||||||
val argParser = ArgParser("testParser")
|
val argParser = ArgParser("testParser")
|
||||||
|
|
||||||
class Summary: Subcommand("summary") {
|
class Summary: Subcommand("summary") {
|
||||||
val addendums by argument(ArgType.Int, "addendums", description = "Addendums").number()
|
val addendums by argument(ArgType.Int, "addendums", description = "Addendums").vararg()
|
||||||
var result: Int = 0
|
var result: Int = 0
|
||||||
|
|
||||||
override fun execute() {
|
override fun execute() {
|
||||||
|
|||||||
Reference in New Issue
Block a user