Move everything under kotlin-native folder
I was forced to manually do update the following files, because otherwise they would be ignored according .gitignore settings. Probably they should be deleted from repo. Interop/.idea/compiler.xml Interop/.idea/gradle.xml Interop/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_3.xml Interop/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_3.xml Interop/.idea/modules.xml Interop/.idea/modules/Indexer/Indexer.iml Interop/.idea/modules/Runtime/Runtime.iml Interop/.idea/modules/StubGenerator/StubGenerator.iml backend.native/backend.native.iml backend.native/bc.frontend/bc.frontend.iml backend.native/cli.bc/cli.bc.iml backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.kt backend.native/tests/link/lib/foo.kt backend.native/tests/link/lib/foo2.kt backend.native/tests/teamcity-test.property
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
import org.jetbrains.kotlin.UtilsKt
|
||||
import org.jetbrains.kotlin.gradle.plugin.konan.tasks.KonanCacheTask
|
||||
import org.jetbrains.kotlin.EndorsedLibraryInfo
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
maven { url 'https://cache-redirector.jetbrains.com/jcenter' }
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-native-gradle-plugin:$gradlePluginVersion"
|
||||
}
|
||||
}
|
||||
|
||||
ext {
|
||||
endorsedLibraries = [
|
||||
new EndorsedLibraryInfo(project('kotlinx.cli'), "org.jetbrains.kotlinx.kotlinx-cli")
|
||||
].collectEntries { [it.project, it] }
|
||||
}
|
||||
|
||||
Collection<EndorsedLibraryInfo> endorsedLibrariesList = ext.endorsedLibraries.values()
|
||||
|
||||
task clean {
|
||||
doLast {
|
||||
delete buildDir
|
||||
}
|
||||
}
|
||||
|
||||
task jvmJar {
|
||||
endorsedLibrariesList.each { library ->
|
||||
dependsOn "$library.projectName:jvmJar"
|
||||
}
|
||||
}
|
||||
|
||||
// Build all default libraries.
|
||||
targetList.each { target ->
|
||||
task("${target}EndorsedLibraries", type: Copy) {
|
||||
endorsedLibrariesList.each { library ->
|
||||
dependsOn "$library.projectName:${target}${library.taskName}"
|
||||
}
|
||||
|
||||
destinationDir project.buildDir
|
||||
|
||||
endorsedLibrariesList.each { library ->
|
||||
from(library.project.file("build/${target}${library.taskName}")) {
|
||||
include('**')
|
||||
into(library.name)
|
||||
eachFile {
|
||||
if (name == 'manifest') {
|
||||
def existingManifest = file("$destinationDir/$path")
|
||||
if (existingManifest.exists()) {
|
||||
UtilsKt.mergeManifestsByTargets(project, file, existingManifest)
|
||||
exclude()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (target in cacheableTargetNames) {
|
||||
def cacheTask = task("${target}Cache")
|
||||
|
||||
endorsedLibrariesList.each { library ->
|
||||
def dist = rootProject.file("dist")
|
||||
task("${target}${library.taskName}Cache", type: KonanCacheTask) {
|
||||
it.target = target
|
||||
originalKlib = file("${project.buildDir}/${library.name}")
|
||||
cacheRoot = file("$dist/klib/cache")
|
||||
compilerDistributionPath.set(distDir)
|
||||
|
||||
dependsOn "${target}EndorsedLibraries"
|
||||
dependsOn ":${target}CrossDistRuntime"
|
||||
dependsOn ":${target}StdlibCache"
|
||||
|
||||
cacheTask.dependsOn it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
endorsedLibrariesList.each { library ->
|
||||
task("${library.taskName}CommonSources", type: Zip) {
|
||||
destinationDirectory = file("${rootProject.projectDir}/dist/sources")
|
||||
archiveFileName = "${library.name}-common-sources.zip"
|
||||
|
||||
includeEmptyDirs = false
|
||||
include('**/*.kt')
|
||||
|
||||
from(library.project.file('src/main/kotlin'))
|
||||
}
|
||||
task("${library.taskName}NativeSources", type: Zip) {
|
||||
destinationDirectory = file("${rootProject.projectDir}/dist/sources")
|
||||
archiveFileName = "${library.name}-native-sources.zip"
|
||||
|
||||
includeEmptyDirs = false
|
||||
include('**/*.kt')
|
||||
|
||||
from(library.project.file('src/main/kotlin-native'))
|
||||
}
|
||||
}
|
||||
|
||||
task endorsedLibsSources {
|
||||
endorsedLibrariesList.each { library ->
|
||||
dependsOn "${library.taskName}CommonSources"
|
||||
dependsOn "${library.taskName}NativeSources"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import org.jetbrains.kotlin.konan.target.HostManager
|
||||
import org.jetbrains.kotlin.konan.util.PlatformLibsInfo
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://cache-redirector.jetbrains.com/jcenter'
|
||||
}
|
||||
jcenter()
|
||||
maven {
|
||||
url kotlinCompilerRepo
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin-multiplatform'
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://cache-redirector.jetbrains.com/jcenter'
|
||||
}
|
||||
jcenter()
|
||||
maven {
|
||||
url kotlinCompilerRepo
|
||||
}
|
||||
maven {
|
||||
url buildKotlinCompilerRepo
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlinVersion"
|
||||
}
|
||||
kotlin.srcDir 'src/main/kotlin'
|
||||
|
||||
}
|
||||
commonTest {
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-test-common:$kotlinVersion"
|
||||
implementation "org.jetbrains.kotlin:kotlin-test-annotations-common:$kotlinVersion"
|
||||
}
|
||||
kotlin.srcDir 'src/tests'
|
||||
}
|
||||
jvm().compilations.main.defaultSourceSet {
|
||||
dependencies {
|
||||
implementation kotlin('stdlib-jdk8')
|
||||
}
|
||||
kotlin.srcDir 'src/main/kotlin-jvm'
|
||||
}
|
||||
// JVM-specific tests and their dependencies:
|
||||
jvm().compilations.test.defaultSourceSet {
|
||||
dependencies {
|
||||
implementation kotlin('test-junit')
|
||||
}
|
||||
}
|
||||
|
||||
jvm().compilations.all {
|
||||
kotlinOptions {
|
||||
freeCompilerArgs = ["-Xopt-in=kotlinx.cli.ExperimentalCli", "-Xopt-in=kotlin.RequiresOptIn"]
|
||||
suppressWarnings = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def commonSrc = new File("$projectDir/src/main/kotlin")
|
||||
def nativeSrc = new File("$projectDir/src/main/kotlin-native")
|
||||
|
||||
targetList.each { target ->
|
||||
def konanJvmArgs = [*HostManager.regularJvmArgs]
|
||||
|
||||
def defaultArgs = ['-nopack', '-no-default-libs', '-no-endorsed-libs']
|
||||
if (target != "wasm32") defaultArgs += '-g'
|
||||
def konanArgs = [*defaultArgs,
|
||||
'-target', target,
|
||||
"-Xruntime=${project(':runtime').file('build/bitcode/main/' + target + '/runtime.bc')}",
|
||||
*project.globalBuildArgs]
|
||||
|
||||
task("${target}KotlinxCli", type: JavaExec) {
|
||||
def outputFile = project.file("build/${target}KotlinxCli")
|
||||
// See :endorsedLibraries.ext for full endorsedLibraries list.
|
||||
def moduleName = endorsedLibraries[project].name
|
||||
|
||||
dependsOn ":distCompiler"
|
||||
dependsOn ":${target}CrossDistRuntime"
|
||||
|
||||
main = 'org.jetbrains.kotlin.cli.bc.K2NativeKt'
|
||||
// This task depends on distCompiler, so the compiler jar is already in the dist directory.
|
||||
classpath = fileTree("${rootProject.projectDir}/dist/konan/lib") {
|
||||
include "*.jar"
|
||||
}
|
||||
jvmArgs = konanJvmArgs
|
||||
args = [*konanArgs,
|
||||
'-output', outputFile,
|
||||
'-produce', 'library', '-module-name', moduleName, '-XXLanguage:+AllowContractsForCustomFunctions',
|
||||
'-Xmulti-platform', '-Xopt-in=kotlinx.cli.ExperimentalCli',
|
||||
'-Xopt-in=kotlin.ExperimentalMultiplatform',
|
||||
'-Xallow-result-return-type', '-Werror', '-Xopt-in=kotlin.RequiresOptIn',
|
||||
commonSrc.absolutePath,
|
||||
"-Xcommon-sources=${commonSrc.absolutePath}",
|
||||
nativeSrc]
|
||||
inputs.dir(nativeSrc)
|
||||
inputs.dir(commonSrc)
|
||||
outputs.dir(outputFile)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.jetbrains.kotlin.native.home=../../dist
|
||||
org.jetbrains.kotlin.native.jvmArgs=-Xmx6G
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package kotlinx.cli
|
||||
|
||||
internal actual fun exitProcess(status: Int): Nothing {
|
||||
error("Not implemented for JS!")
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package kotlinx.cli
|
||||
|
||||
internal actual fun exitProcess(status: Int): Nothing {
|
||||
kotlin.system.exitProcess(0)
|
||||
}
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package kotlinx.cli
|
||||
|
||||
internal actual fun exitProcess(status: Int): Nothing {
|
||||
kotlin.system.exitProcess(0)
|
||||
}
|
||||
@@ -0,0 +1,664 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package kotlinx.cli
|
||||
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
internal expect fun exitProcess(status: Int): Nothing
|
||||
|
||||
/**
|
||||
* Queue of arguments descriptors.
|
||||
* Arguments can have several values, so one descriptor can be returned several times.
|
||||
*/
|
||||
internal class ArgumentsQueue(argumentsDescriptors: List<ArgDescriptor<*, *>>) {
|
||||
/**
|
||||
* Map of arguments descriptors and their current usage number.
|
||||
*/
|
||||
private val argumentsUsageNumber = linkedMapOf(*argumentsDescriptors.map { it to 0 }.toTypedArray())
|
||||
|
||||
/**
|
||||
* Get next descriptor from queue.
|
||||
*/
|
||||
fun pop(): String? {
|
||||
if (argumentsUsageNumber.isEmpty())
|
||||
return null
|
||||
|
||||
val (currentDescriptor, usageNumber) = argumentsUsageNumber.iterator().next()
|
||||
currentDescriptor.number?.let {
|
||||
// Parse all arguments for current argument description.
|
||||
if (usageNumber + 1 >= currentDescriptor.number) {
|
||||
// All needed arguments were provided.
|
||||
argumentsUsageNumber.remove(currentDescriptor)
|
||||
} else {
|
||||
argumentsUsageNumber[currentDescriptor] = usageNumber + 1
|
||||
}
|
||||
}
|
||||
return currentDescriptor.fullName
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A property delegate that provides access to the argument/option value.
|
||||
*/
|
||||
interface ArgumentValueDelegate<T> {
|
||||
/**
|
||||
* The value of an option or argument parsed from command line.
|
||||
*
|
||||
* Accessing this value before [ArgParser.parse] method is called will result in an exception.
|
||||
*
|
||||
* @see CLIEntity.value
|
||||
*/
|
||||
var value: T
|
||||
|
||||
/** Provides the value for the delegated property getter. Returns the [value] property.
|
||||
* @throws IllegalStateException in case of accessing the value before [ArgParser.parse] method is called.
|
||||
*/
|
||||
operator fun getValue(thisRef: Any?, property: KProperty<*>): T = value
|
||||
|
||||
/** Sets the [value] to the [ArgumentValueDelegate.value] property from the delegated property setter.
|
||||
* This operation is possible only after command line arguments were parsed with [ArgParser.parse]
|
||||
* @throws IllegalStateException in case of resetting value before command line arguments are parsed.
|
||||
*/
|
||||
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
||||
this.value = value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract base class for subcommands.
|
||||
*/
|
||||
@ExperimentalCli
|
||||
abstract class Subcommand(val name: String, val actionDescription: String): ArgParser(name) {
|
||||
/**
|
||||
* Execute action if subcommand was provided.
|
||||
*/
|
||||
abstract fun execute()
|
||||
|
||||
val helpMessage: String
|
||||
get() = " $name - $actionDescription\n"
|
||||
}
|
||||
|
||||
/**
|
||||
* Argument parsing result.
|
||||
* Contains name of subcommand which was called.
|
||||
*
|
||||
* @property commandName name of command which was called.
|
||||
*/
|
||||
class ArgParserResult(val commandName: String)
|
||||
|
||||
/**
|
||||
* Arguments parser.
|
||||
*
|
||||
* @property programName the name of the current program.
|
||||
* @property useDefaultHelpShortName specifies whether to register "-h" option for printing the usage information.
|
||||
* @property prefixStyle the style of option prefixing.
|
||||
* @property skipExtraArguments specifies whether the extra unmatched arguments in a command line string
|
||||
* can be skipped without producing an error message.
|
||||
*/
|
||||
open class ArgParser(
|
||||
val programName: String,
|
||||
var useDefaultHelpShortName: Boolean = true,
|
||||
var prefixStyle: OptionPrefixStyle = OptionPrefixStyle.LINUX,
|
||||
var skipExtraArguments: Boolean = false
|
||||
) {
|
||||
|
||||
/**
|
||||
* Map of options: key - full name of option, value - pair of descriptor and parsed values.
|
||||
*/
|
||||
private val options = mutableMapOf<String, ParsingValue<*, *>>()
|
||||
/**
|
||||
* Map of arguments: key - full name of argument, value - pair of descriptor and parsed values.
|
||||
*/
|
||||
private val arguments = mutableMapOf<String, ParsingValue<*, *>>()
|
||||
|
||||
/**
|
||||
* Map with declared options.
|
||||
*/
|
||||
private val declaredOptions = mutableListOf<CLIEntityWrapper>()
|
||||
|
||||
/**
|
||||
* Map with declared arguments.
|
||||
*/
|
||||
private val declaredArguments = mutableListOf<CLIEntityWrapper>()
|
||||
|
||||
/**
|
||||
* State of parser. Stores last parsing result or null.
|
||||
*/
|
||||
private var parsingState: ArgParserResult? = null
|
||||
|
||||
/**
|
||||
* Map of subcommands.
|
||||
*/
|
||||
@OptIn(ExperimentalCli::class)
|
||||
protected val subcommands = mutableMapOf<String, Subcommand>()
|
||||
|
||||
/**
|
||||
* Mapping for short options names for quick search.
|
||||
*/
|
||||
private val shortNames = mutableMapOf<String, ParsingValue<*, *>>()
|
||||
|
||||
/**
|
||||
* Used prefix form for full option form.
|
||||
*/
|
||||
private val optionFullFormPrefix = if (prefixStyle == OptionPrefixStyle.JVM) "-" else "--"
|
||||
|
||||
/**
|
||||
* Used prefix form for short option form.
|
||||
*/
|
||||
private val optionShortFromPrefix = "-"
|
||||
|
||||
/**
|
||||
* Name with all commands that should be executed.
|
||||
*/
|
||||
protected val fullCommandName = mutableListOf(programName)
|
||||
|
||||
/**
|
||||
* Flag to recognize if CLI entities can be treated as options.
|
||||
*/
|
||||
protected var treatAsOption = true
|
||||
|
||||
/**
|
||||
* The way an option/argument has got its value.
|
||||
*/
|
||||
enum class ValueOrigin {
|
||||
/* The value was parsed from command line arguments. */
|
||||
SET_BY_USER,
|
||||
/* The value was missing in command line, therefore the default value was used. */
|
||||
SET_DEFAULT_VALUE,
|
||||
/* The value is not initialized by command line values or by default values. */
|
||||
UNSET,
|
||||
/* The value was redefined after parsing manually (usually with the property setter). */
|
||||
REDEFINED,
|
||||
/* The value is undefined, because parsing wasn't called. */
|
||||
UNDEFINED
|
||||
}
|
||||
|
||||
/**
|
||||
* The style of option prefixing.
|
||||
*/
|
||||
enum class OptionPrefixStyle {
|
||||
/* Linux style: the full name of an option is prefixed with two hyphens "--" and the short name — with one "-". */
|
||||
LINUX,
|
||||
/* JVM style: both full and short names are prefixed with one hyphen "-". */
|
||||
JVM,
|
||||
/* GNU style: the full name of an option is prefixed with two hyphens "--" and "=" between options and value
|
||||
and the short name — with one "-".
|
||||
Detailed information https://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html
|
||||
*/
|
||||
GNU
|
||||
}
|
||||
|
||||
@Deprecated("OPTION_PREFIX_STYLE is deprecated. Please, use OptionPrefixStyle.",
|
||||
ReplaceWith("OptionPrefixStyle", "kotlinx.cli.OptionPrefixStyle"))
|
||||
@Suppress("TOPLEVEL_TYPEALIASES_ONLY")
|
||||
typealias OPTION_PREFIX_STYLE = OptionPrefixStyle
|
||||
|
||||
/**
|
||||
* Declares a named option and returns an object which can be used to access the option value
|
||||
* after all arguments are parsed or to delegate a property for accessing the option value to.
|
||||
*
|
||||
* By default, the option supports only a single value, is optional, and has no default value,
|
||||
* therefore its value's type is `T?`.
|
||||
*
|
||||
* You can alter the option properties by chaining extensions for the option type on the returned object:
|
||||
* - [AbstractSingleOption.default] to provide a default value that is used when the option is not specified;
|
||||
* - [SingleNullableOption.required] to make the option non-optional;
|
||||
* - [AbstractSingleOption.delimiter] to allow specifying multiple values in one command line argument with a delimiter;
|
||||
* - [AbstractSingleOption.multiple] to allow specifying the option several times.
|
||||
*
|
||||
* @param type The type describing how to parse an option value from a string,
|
||||
* an instance of [ArgType], e.g. [ArgType.String] or [ArgType.Choice].
|
||||
* @param fullName the full name of the option, can be omitted if the option name is inferred
|
||||
* from the name of a property delegated to this option.
|
||||
* @param shortName the short name of the option, `null` if the option cannot be specified in a short form.
|
||||
* @param description the description of the option used when rendering the usage information.
|
||||
* @param deprecatedWarning the deprecation message for the option.
|
||||
* Specifying anything except `null` makes this option deprecated. The message is rendered in a help message and
|
||||
* issued as a warning when the option is encountered when parsing command line arguments.
|
||||
*/
|
||||
fun <T : Any> option(
|
||||
type: ArgType<T>,
|
||||
fullName: String? = null,
|
||||
shortName: String ? = null,
|
||||
description: String? = null,
|
||||
deprecatedWarning: String? = null
|
||||
): SingleNullableOption<T> {
|
||||
if (prefixStyle == OptionPrefixStyle.GNU && shortName != null)
|
||||
require(shortName.length == 1) {
|
||||
"""
|
||||
GNU standart for options allow to use short form whuch consists of one character.
|
||||
For more information, please, see https://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html
|
||||
""".trimIndent()
|
||||
}
|
||||
val option = SingleNullableOption(OptionDescriptor(optionFullFormPrefix, optionShortFromPrefix, type,
|
||||
fullName, shortName, description, deprecatedWarning = deprecatedWarning), CLIEntityWrapper())
|
||||
option.owner.entity = option
|
||||
declaredOptions.add(option.owner)
|
||||
return option
|
||||
}
|
||||
|
||||
/**
|
||||
* Check usage of required property for arguments.
|
||||
* Make sense only for several last arguments.
|
||||
*/
|
||||
private fun inspectRequiredAndDefaultUsage() {
|
||||
var previousArgument: ParsingValue<*, *>? = null
|
||||
arguments.forEach { (_, currentArgument) ->
|
||||
previousArgument?.let { previous ->
|
||||
// Previous argument has default value.
|
||||
if (previous.descriptor.defaultValueSet) {
|
||||
if (!currentArgument.descriptor.defaultValueSet && currentArgument.descriptor.required) {
|
||||
error("Default value of argument ${previous.descriptor.fullName} will be unused, " +
|
||||
"because next argument ${currentArgument.descriptor.fullName} is always required and has no default value.")
|
||||
}
|
||||
}
|
||||
// Previous argument is optional.
|
||||
if (!previous.descriptor.required) {
|
||||
if (!currentArgument.descriptor.defaultValueSet && currentArgument.descriptor.required) {
|
||||
error("Argument ${previous.descriptor.fullName} will be always required, " +
|
||||
"because next argument ${currentArgument.descriptor.fullName} is always required.")
|
||||
}
|
||||
}
|
||||
}
|
||||
previousArgument = currentArgument
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares an argument and returns an object which can be used to access the argument value
|
||||
* after all arguments are parsed or to delegate a property for accessing the argument value to.
|
||||
*
|
||||
* By default, the argument supports only a single value, is required, and has no default value,
|
||||
* therefore its value's type is `T`.
|
||||
*
|
||||
* You can alter the argument properties by chaining extensions for the argument type on the returned object:
|
||||
* - [AbstractSingleArgument.default] to provide a default value that is used when the argument is not specified;
|
||||
* - [SingleArgument.optional] to allow omitting the argument;
|
||||
* - [AbstractSingleArgument.multiple] to require the argument to have exactly the number of values specified;
|
||||
* - [AbstractSingleArgument.vararg] to allow specifying an unlimited number of values for the _last_ argument.
|
||||
*
|
||||
* @param type The type describing how to parse an option value from a string,
|
||||
* an instance of [ArgType], e.g. [ArgType.String] or [ArgType.Choice].
|
||||
* @param fullName the full name of the argument, can be omitted if the argument name is inferred
|
||||
* from the name of a property delegated to this argument.
|
||||
* @param description the description of the argument used when rendering the usage information.
|
||||
* @param deprecatedWarning the deprecation message for the argument.
|
||||
* Specifying anything except `null` makes this argument deprecated. The message is rendered in a help message and
|
||||
* issued as a warning when the argument is encountered when parsing command line arguments.
|
||||
*/
|
||||
fun <T : Any> argument(
|
||||
type: ArgType<T>,
|
||||
fullName: String? = null,
|
||||
description: String? = null,
|
||||
deprecatedWarning: String? = null
|
||||
) : SingleArgument<T, DefaultRequiredType.Required> {
|
||||
val argument = SingleArgument<T, DefaultRequiredType.Required>(ArgDescriptor(type, fullName, 1,
|
||||
description, deprecatedWarning = deprecatedWarning), CLIEntityWrapper())
|
||||
argument.owner.entity = argument
|
||||
declaredArguments.add(argument.owner)
|
||||
return argument
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers one or more subcommands.
|
||||
*
|
||||
* @param subcommandsList subcommands to add.
|
||||
*/
|
||||
@ExperimentalCli
|
||||
fun subcommands(vararg subcommandsList: Subcommand) {
|
||||
subcommandsList.forEach {
|
||||
if (it.name in subcommands) {
|
||||
error("Subcommand with name ${it.name} was already defined.")
|
||||
}
|
||||
|
||||
// Set same settings as main parser.
|
||||
it.prefixStyle = prefixStyle
|
||||
it.useDefaultHelpShortName = useDefaultHelpShortName
|
||||
fullCommandName.forEachIndexed { index, namePart ->
|
||||
it.fullCommandName.add(index, namePart)
|
||||
}
|
||||
subcommands[it.name] = it
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs an error message adding the usage information after it.
|
||||
*
|
||||
* @param message error message.
|
||||
*/
|
||||
fun printError(message: String): Nothing {
|
||||
error("$message\n${makeUsage()}")
|
||||
}
|
||||
|
||||
/**
|
||||
* Save value as argument value.
|
||||
*
|
||||
* @param arg string with argument value.
|
||||
* @param argumentsQueue queue with active argument descriptors.
|
||||
*/
|
||||
private fun saveAsArg(arg: String, argumentsQueue: ArgumentsQueue): Boolean {
|
||||
// Find next uninitialized arguments.
|
||||
val name = argumentsQueue.pop()
|
||||
name?.let {
|
||||
val argumentValue = arguments[name]!!
|
||||
argumentValue.descriptor.deprecatedWarning?.let { printWarning(it) }
|
||||
argumentValue.addValue(arg)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Treat value as argument value.
|
||||
*
|
||||
* @param arg string with argument value.
|
||||
* @param argumentsQueue queue with active argument descriptors.
|
||||
*/
|
||||
private fun treatAsArgument(arg: String, argumentsQueue: ArgumentsQueue) {
|
||||
if (!saveAsArg(arg, argumentsQueue)) {
|
||||
printError("Too many arguments! Couldn't process argument $arg!")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save value as option value.
|
||||
*/
|
||||
private fun <T : Any, U: Any> saveAsOption(parsingValue: ParsingValue<T, U>, value: String) {
|
||||
parsingValue.addValue(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to recognize and save command line element as full form of option.
|
||||
*
|
||||
* @param candidate string with candidate in options.
|
||||
* @param argIterator iterator over command line arguments.
|
||||
*/
|
||||
private fun recognizeAndSaveOptionFullForm(candidate: String, argIterator: Iterator<String>): Boolean {
|
||||
if (prefixStyle == OptionPrefixStyle.GNU && candidate == optionFullFormPrefix) {
|
||||
// All other arguments after `--` are treated as non-option arguments.
|
||||
treatAsOption = false
|
||||
return false
|
||||
}
|
||||
if (!candidate.startsWith(optionFullFormPrefix))
|
||||
return false
|
||||
|
||||
val optionString = candidate.substring(optionFullFormPrefix.length)
|
||||
val argValue = if (prefixStyle == OptionPrefixStyle.GNU) null else options[optionString]
|
||||
if (argValue != null) {
|
||||
saveStandardOptionForm(argValue, argIterator)
|
||||
return true
|
||||
} else {
|
||||
// Check GNU style of options.
|
||||
if (prefixStyle == OptionPrefixStyle.GNU) {
|
||||
// Option without a parameter.
|
||||
if (options[optionString]?.descriptor?.type?.hasParameter == false) {
|
||||
saveOptionWithoutParameter(options[optionString]!!)
|
||||
return true
|
||||
}
|
||||
// Option with parameters.
|
||||
val optionParts = optionString.split('=', limit = 2)
|
||||
if (optionParts.size != 2)
|
||||
return false
|
||||
if (options[optionParts[0]] != null) {
|
||||
saveAsOption(options[optionParts[0]]!!, optionParts[1])
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Save option without parameter.
|
||||
*
|
||||
* @param argValue argument value with all information about option.
|
||||
*/
|
||||
private fun saveOptionWithoutParameter(argValue: ParsingValue<*, *>) {
|
||||
// Boolean flags.
|
||||
if (argValue.descriptor.fullName == "help") {
|
||||
println(makeUsage())
|
||||
exitProcess(0)
|
||||
}
|
||||
saveAsOption(argValue, "true")
|
||||
}
|
||||
|
||||
/**
|
||||
* Save option described with standard separated form `--name value`.
|
||||
*
|
||||
* @param argValue argument value with all information about option.
|
||||
* @param argIterator iterator over command line arguments.
|
||||
*/
|
||||
private fun saveStandardOptionForm(argValue: ParsingValue<*, *>, argIterator: Iterator<String>) {
|
||||
if (argValue.descriptor.type.hasParameter) {
|
||||
if (argIterator.hasNext()) {
|
||||
saveAsOption(argValue, argIterator.next())
|
||||
} else {
|
||||
// An error, option with value without value.
|
||||
printError("No value for ${argValue.descriptor.textDescription}")
|
||||
}
|
||||
} else {
|
||||
saveOptionWithoutParameter(argValue)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to recognize and save command line element as short form of option.
|
||||
*
|
||||
* @param candidate string with candidate in options.
|
||||
* @param argIterator iterator over command line arguments.
|
||||
*/
|
||||
private fun recognizeAndSaveOptionShortForm(candidate: String, argIterator: Iterator<String>): Boolean {
|
||||
if (!candidate.startsWith(optionShortFromPrefix) ||
|
||||
optionFullFormPrefix != optionShortFromPrefix && candidate.startsWith(optionFullFormPrefix)) return false
|
||||
// Try to find exact match.
|
||||
val option = candidate.substring(optionShortFromPrefix.length)
|
||||
val argValue = shortNames[option]
|
||||
if (argValue != null) {
|
||||
saveStandardOptionForm(argValue, argIterator)
|
||||
} else {
|
||||
if (prefixStyle != OptionPrefixStyle.GNU || option.isEmpty())
|
||||
return false
|
||||
|
||||
// Try to find collapsed form.
|
||||
val firstOption = shortNames["${option[0]}"] ?: return false
|
||||
// Form with value after short form without separator.
|
||||
if (firstOption.descriptor.type.hasParameter) {
|
||||
saveAsOption(firstOption, option.substring(1))
|
||||
} else {
|
||||
// Form with several short forms as one string.
|
||||
val otherBooleanOptions = option.substring(1)
|
||||
saveOptionWithoutParameter(firstOption)
|
||||
for (opt in otherBooleanOptions) {
|
||||
shortNames["$opt"]?.let {
|
||||
if (it.descriptor.type.hasParameter) {
|
||||
printError(
|
||||
"Option $optionShortFromPrefix$opt can't be used in option combination $candidate, " +
|
||||
"because parameter value of type ${it.descriptor.type.description} should be " +
|
||||
"provided for current option."
|
||||
)
|
||||
}
|
||||
}?: printError("Unknown option $optionShortFromPrefix$opt in option combination $candidate.")
|
||||
|
||||
saveOptionWithoutParameter(shortNames["$opt"]!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the provided array of command line arguments.
|
||||
* After a successful parsing, the options and arguments declared in this parser get their values and can be accessed
|
||||
* with the properties delegated to them.
|
||||
*
|
||||
* @param args the array with command line arguments.
|
||||
*
|
||||
* @return an [ArgParserResult] if all arguments were parsed successfully.
|
||||
* Otherwise, prints the usage information and terminates the program execution.
|
||||
* @throws IllegalStateException in case of attempt of calling parsing several times.
|
||||
*/
|
||||
fun parse(args: Array<String>): ArgParserResult = parse(args.asList())
|
||||
|
||||
protected fun parse(args: List<String>): ArgParserResult {
|
||||
check(parsingState == null) { "Parsing of command line options can be called only once." }
|
||||
|
||||
// Add help option.
|
||||
val helpDescriptor = if (useDefaultHelpShortName) OptionDescriptor<Boolean, Boolean>(
|
||||
optionFullFormPrefix,
|
||||
optionShortFromPrefix, ArgType.Boolean,
|
||||
"help", "h", "Usage info"
|
||||
)
|
||||
else OptionDescriptor(
|
||||
optionFullFormPrefix, optionShortFromPrefix,
|
||||
ArgType.Boolean, "help", description = "Usage info"
|
||||
)
|
||||
val helpOption = SingleNullableOption(helpDescriptor, CLIEntityWrapper())
|
||||
helpOption.owner.entity = helpOption
|
||||
declaredOptions.add(helpOption.owner)
|
||||
|
||||
// Add default list with arguments if there can be extra free arguments.
|
||||
if (skipExtraArguments) {
|
||||
argument(ArgType.String, "").vararg()
|
||||
}
|
||||
|
||||
// Clean options and arguments maps.
|
||||
options.clear()
|
||||
arguments.clear()
|
||||
|
||||
// Map declared options and arguments to maps.
|
||||
declaredOptions.forEachIndexed { index, option ->
|
||||
val value = option.entity?.delegate as ParsingValue<*, *>
|
||||
value.descriptor.fullName?.let {
|
||||
// Add option.
|
||||
if (options.containsKey(it)) {
|
||||
error("Option with full name $it was already added.")
|
||||
}
|
||||
with(value.descriptor as OptionDescriptor) {
|
||||
if (shortName != null && shortNames.containsKey(shortName)) {
|
||||
error("Option with short name ${shortName} was already added.")
|
||||
}
|
||||
shortName?.let {
|
||||
shortNames[it] = value
|
||||
}
|
||||
}
|
||||
options[it] = value
|
||||
|
||||
} ?: error("Option was added, but unnamed. Added option under №${index + 1}")
|
||||
}
|
||||
|
||||
declaredArguments.forEachIndexed { index, argument ->
|
||||
val value = argument.entity?.delegate as ParsingValue<*, *>
|
||||
value.descriptor.fullName?.let {
|
||||
// Add option.
|
||||
if (arguments.containsKey(it)) {
|
||||
error("Argument with full name $it was already added.")
|
||||
}
|
||||
arguments[it] = value
|
||||
} ?: error("Argument was added, but unnamed. Added argument under №${index + 1}")
|
||||
}
|
||||
// Make inspections for arguments.
|
||||
inspectRequiredAndDefaultUsage()
|
||||
|
||||
listOf(arguments, options).forEach {
|
||||
it.forEach { (_, value) ->
|
||||
value.valueOrigin = ValueOrigin.UNSET
|
||||
}
|
||||
}
|
||||
|
||||
val argumentsQueue = ArgumentsQueue(arguments.map { it.value.descriptor as ArgDescriptor<*, *> })
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Argument is found.
|
||||
treatAsArgument(arg, argumentsQueue)
|
||||
}
|
||||
}
|
||||
// Postprocess results of parsing.
|
||||
options.values.union(arguments.values).forEach { value ->
|
||||
// Not inited, append default value if needed.
|
||||
if (value.isEmpty()) {
|
||||
value.addDefaultValue()
|
||||
}
|
||||
if (value.valueOrigin != ValueOrigin.SET_BY_USER && value.descriptor.required) {
|
||||
printError("Value for ${value.descriptor.textDescription} should be always provided in command line.")
|
||||
}
|
||||
}
|
||||
} catch (exception: ParsingException) {
|
||||
printError(exception.message!!)
|
||||
}
|
||||
parsingState = ArgParserResult(programName)
|
||||
return parsingState!!
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a message with the usage information.
|
||||
*/
|
||||
internal fun makeUsage(): String {
|
||||
val result = StringBuilder()
|
||||
result.append("Usage: ${fullCommandName.joinToString(" ")} options_list\n")
|
||||
if (subcommands.isNotEmpty()) {
|
||||
result.append("Subcommands: \n")
|
||||
subcommands.forEach { (_, subcommand) ->
|
||||
result.append(subcommand.helpMessage)
|
||||
}
|
||||
result.append("\n")
|
||||
}
|
||||
if (arguments.isNotEmpty()) {
|
||||
result.append("Arguments: \n")
|
||||
arguments.forEach {
|
||||
result.append(it.value.descriptor.helpMessage)
|
||||
}
|
||||
}
|
||||
if (options.isNotEmpty()) {
|
||||
result.append("Options: \n")
|
||||
options.forEach {
|
||||
result.append(it.value.descriptor.helpMessage)
|
||||
}
|
||||
}
|
||||
return result.toString()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output warning.
|
||||
*
|
||||
* @param message warning message.
|
||||
*/
|
||||
internal fun printWarning(message: String) {
|
||||
println("WARNING $message")
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package kotlinx.cli
|
||||
|
||||
/**
|
||||
* Possible types of arguments.
|
||||
*
|
||||
* Inheritors describe type of argument value. New types can be added by user.
|
||||
* In case of options type can have parameter or not.
|
||||
*/
|
||||
abstract class ArgType<T : Any>(val hasParameter: kotlin.Boolean) {
|
||||
/**
|
||||
* Text description of type for helpMessage.
|
||||
*/
|
||||
abstract val description: kotlin.String
|
||||
|
||||
/**
|
||||
* Function to convert string argument value to its type.
|
||||
* In case of error during conversion also provides help message.
|
||||
*
|
||||
* @param value value
|
||||
*/
|
||||
abstract fun convert(value: kotlin.String, name: kotlin.String): T
|
||||
|
||||
/**
|
||||
* Argument type for flags that can be only set/unset.
|
||||
*/
|
||||
object Boolean : ArgType<kotlin.Boolean>(false) {
|
||||
override val description: kotlin.String
|
||||
get() = ""
|
||||
|
||||
override fun convert(value: kotlin.String, name: kotlin.String): kotlin.Boolean =
|
||||
value != "false"
|
||||
}
|
||||
|
||||
/**
|
||||
* Argument type for string values.
|
||||
*/
|
||||
object String : ArgType<kotlin.String>(true) {
|
||||
override val description: kotlin.String
|
||||
get() = "{ String }"
|
||||
|
||||
override fun convert(value: kotlin.String, name: kotlin.String): kotlin.String = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Argument type for integer values.
|
||||
*/
|
||||
object Int : ArgType<kotlin.Int>(true) {
|
||||
override val description: kotlin.String
|
||||
get() = "{ Int }"
|
||||
|
||||
override fun convert(value: kotlin.String, name: kotlin.String): kotlin.Int =
|
||||
value.toIntOrNull()
|
||||
?: throw ParsingException("Option $name is expected to be integer number. $value is provided.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Argument type for double values.
|
||||
*/
|
||||
object Double : ArgType<kotlin.Double>(true) {
|
||||
override val description: kotlin.String
|
||||
get() = "{ Double }"
|
||||
|
||||
override fun convert(value: kotlin.String, name: kotlin.String): kotlin.Double =
|
||||
value.toDoubleOrNull()
|
||||
?: throw ParsingException("Option $name is expected to be double number. $value is provided.")
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 }"
|
||||
|
||||
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.")
|
||||
}
|
||||
}
|
||||
|
||||
internal class ParsingException(message: String) : Exception(message)
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
package kotlinx.cli
|
||||
|
||||
/**
|
||||
* Parsing value of option/argument.
|
||||
*/
|
||||
internal abstract class ParsingValue<T: Any, TResult: Any>(val descriptor: Descriptor<T, TResult>) {
|
||||
/**
|
||||
* Values of arguments.
|
||||
*/
|
||||
protected lateinit var parsedValue: TResult
|
||||
|
||||
/**
|
||||
* Value origin.
|
||||
*/
|
||||
var valueOrigin = ArgParser.ValueOrigin.UNDEFINED
|
||||
internal set
|
||||
|
||||
/**
|
||||
* Check if values of argument are empty.
|
||||
*/
|
||||
abstract fun isEmpty(): Boolean
|
||||
|
||||
/**
|
||||
* Check if value of argument was initialized.
|
||||
*/
|
||||
protected fun valueIsInitialized() = ::parsedValue.isInitialized
|
||||
|
||||
/**
|
||||
* Sace value from command line.
|
||||
*
|
||||
* @param stringValue value from command line.
|
||||
*/
|
||||
protected abstract fun saveValue(stringValue: String)
|
||||
|
||||
/**
|
||||
* Set value of delegated property.
|
||||
*/
|
||||
fun setDelegatedValue(providedValue: TResult) {
|
||||
parsedValue = providedValue
|
||||
valueOrigin = ArgParser.ValueOrigin.REDEFINED
|
||||
}
|
||||
|
||||
/**
|
||||
* Add parsed value from command line.
|
||||
*
|
||||
* @param stringValue value from command line.
|
||||
*/
|
||||
internal fun addValue(stringValue: String) {
|
||||
// Check of possibility to set several values to one option/argument.
|
||||
if (descriptor is OptionDescriptor<*, *> && !descriptor.multiple &&
|
||||
!isEmpty() && descriptor.delimiter == null) {
|
||||
throw ParsingException("Try to provide more than one value for ${descriptor.fullName}.")
|
||||
}
|
||||
// Show deprecated warning only first time of using option/argument.
|
||||
descriptor.deprecatedWarning?.let {
|
||||
if (isEmpty())
|
||||
println ("Warning: $it")
|
||||
}
|
||||
// Split value if needed.
|
||||
if (descriptor is OptionDescriptor<*, *> && descriptor.delimiter != null) {
|
||||
stringValue.split(descriptor.delimiter).forEach {
|
||||
saveValue(it)
|
||||
}
|
||||
} else {
|
||||
saveValue(stringValue)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default value to option.
|
||||
*/
|
||||
fun addDefaultValue() {
|
||||
if (descriptor.defaultValueSet) {
|
||||
parsedValue = descriptor.defaultValue!!
|
||||
valueOrigin = ArgParser.ValueOrigin.SET_DEFAULT_VALUE
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide name for CLI entity.
|
||||
*
|
||||
* @param name name for CLI entity.
|
||||
*/
|
||||
fun provideName(name: String) {
|
||||
descriptor.fullName ?: run { descriptor.fullName = name }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Single argument value.
|
||||
*
|
||||
* @property descriptor descriptor of option/argument.
|
||||
*/
|
||||
internal abstract class AbstractArgumentSingleValue<T: Any>(descriptor: Descriptor<T, T>):
|
||||
ParsingValue<T, T>(descriptor) {
|
||||
|
||||
override fun saveValue(stringValue: String) {
|
||||
if (!valueIsInitialized()) {
|
||||
parsedValue = descriptor.type.convert(stringValue, descriptor.fullName!!)
|
||||
valueOrigin = ArgParser.ValueOrigin.SET_BY_USER
|
||||
} else {
|
||||
throw ParsingException("Try to provide more than one value $parsedValue and $stringValue for ${descriptor.fullName}.")
|
||||
}
|
||||
}
|
||||
|
||||
override fun isEmpty(): Boolean = !valueIsInitialized()
|
||||
}
|
||||
|
||||
/**
|
||||
* Single argument value.
|
||||
*
|
||||
* @property descriptor descriptor of option/argument.
|
||||
*/
|
||||
internal class ArgumentSingleValue<T: Any>(descriptor: Descriptor<T, T>): AbstractArgumentSingleValue<T>(descriptor),
|
||||
ArgumentValueDelegate<T> {
|
||||
override var value: T
|
||||
get() = if (!isEmpty()) parsedValue else error("Value for argument ${descriptor.fullName} isn't set. " +
|
||||
"ArgParser.parse(...) method should be called before.")
|
||||
set(value) = setDelegatedValue(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Single nullable argument value.
|
||||
*
|
||||
* @property descriptor descriptor of option/argument.
|
||||
*/
|
||||
internal class ArgumentSingleNullableValue<T : Any>(descriptor: Descriptor<T, T>):
|
||||
AbstractArgumentSingleValue<T>(descriptor), ArgumentValueDelegate<T?> {
|
||||
private var setToNull = false
|
||||
override var value: T?
|
||||
get() = if (!isEmpty() && !setToNull) parsedValue else null
|
||||
set(providedValue) = providedValue?.let {
|
||||
setDelegatedValue(it)
|
||||
setToNull = false
|
||||
} ?: run {
|
||||
setToNull = true
|
||||
valueOrigin = ArgParser.ValueOrigin.REDEFINED
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiple argument values.
|
||||
*
|
||||
* @property descriptor descriptor of option/argument.
|
||||
*/
|
||||
internal class ArgumentMultipleValues<T : Any>(descriptor: Descriptor<T, List<T>>):
|
||||
ParsingValue<T, List<T>>(descriptor), ArgumentValueDelegate<List<T>> {
|
||||
|
||||
private val addedValue = mutableListOf<T>()
|
||||
init {
|
||||
parsedValue = addedValue
|
||||
}
|
||||
|
||||
override var value: List<T>
|
||||
get() = parsedValue
|
||||
set(value) = setDelegatedValue(value)
|
||||
|
||||
override fun saveValue(stringValue: String) {
|
||||
addedValue.add(descriptor.type.convert(stringValue, descriptor.fullName!!))
|
||||
valueOrigin = ArgParser.ValueOrigin.SET_BY_USER
|
||||
}
|
||||
|
||||
override fun isEmpty() = parsedValue.isEmpty()
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
package kotlinx.cli
|
||||
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
internal data class CLIEntityWrapper(var entity: CLIEntity<*>? = null)
|
||||
|
||||
/**
|
||||
* Base interface for all possible types of entities with default and required values.
|
||||
* Provides limitations for API that is accessible for different options/arguments types.
|
||||
* Allows to save the reason why option/argument can(or can't) be omitted in command line.
|
||||
*
|
||||
* @see [SingleOption], [MultipleOption], [SingleArgument], [MultipleArgument].
|
||||
*/
|
||||
interface DefaultRequiredType {
|
||||
/**
|
||||
* Type of an entity with default value.
|
||||
*/
|
||||
class Default : DefaultRequiredType
|
||||
|
||||
/**
|
||||
* Type of an entity which value should always be provided in command line.
|
||||
*/
|
||||
class Required : DefaultRequiredType
|
||||
|
||||
/**
|
||||
* Type of entity which is optional and has no default value.
|
||||
*/
|
||||
class None : DefaultRequiredType
|
||||
}
|
||||
|
||||
/**
|
||||
* The base class for a command line argument or an option.
|
||||
*/
|
||||
abstract class CLIEntity<TResult> internal constructor(val delegate: ArgumentValueDelegate<TResult>,
|
||||
internal val owner: CLIEntityWrapper) {
|
||||
/**
|
||||
* The value of the option or argument parsed from command line.
|
||||
*
|
||||
* Accessing this property before it gets its value will result in an exception.
|
||||
* You can use [valueOrigin] property to find out whether the property has been already set.
|
||||
*
|
||||
* @see ArgumentValueDelegate.value
|
||||
*/
|
||||
var value: TResult
|
||||
get() = delegate.value
|
||||
set(value) {
|
||||
check((delegate as ParsingValue<*, *>).valueOrigin != ArgParser.ValueOrigin.UNDEFINED) {
|
||||
"Resetting value of option/argument is only possible after parsing command line arguments." +
|
||||
" ArgParser.parse(...) method should be called before"
|
||||
}
|
||||
delegate.value = value
|
||||
}
|
||||
|
||||
/**
|
||||
* The origin of the option/argument value.
|
||||
*/
|
||||
val valueOrigin: ArgParser.ValueOrigin
|
||||
get() = (delegate as ParsingValue<*, *>).valueOrigin
|
||||
|
||||
private var delegateProvided = false
|
||||
|
||||
/**
|
||||
* Returns the delegate object for property delegation and initializes it with the name of the delegated property.
|
||||
*
|
||||
* This operator makes it possible to delegate a property to this instance. It returns [delegate] object
|
||||
* to be used as an actual delegate and uses the name of the delegated property to initialize the full name
|
||||
* of the option/argument if it wasn't done during construction of that option/argument.
|
||||
*
|
||||
* @throws IllegalStateException in case of trying to use same delegate several times.
|
||||
*/
|
||||
operator fun provideDelegate(thisRef: Any?, prop: KProperty<*>): ArgumentValueDelegate<TResult> {
|
||||
check(!delegateProvided) {
|
||||
"There is already used delegate for ${(delegate as ParsingValue<*, *>).descriptor.fullName}."
|
||||
}
|
||||
(delegate as ParsingValue<*, *>).provideName(prop.name)
|
||||
delegateProvided = true
|
||||
return delegate
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The base class for command line arguments.
|
||||
*
|
||||
* You can use [ArgParser.argument] function to declare an argument.
|
||||
*/
|
||||
abstract class Argument<TResult> internal constructor(delegate: ArgumentValueDelegate<TResult>,
|
||||
owner: CLIEntityWrapper): CLIEntity<TResult>(delegate, owner)
|
||||
|
||||
/**
|
||||
* The base class of an argument with a single value.
|
||||
*
|
||||
* A non-optional argument or an optional argument with a default value is represented with the [SingleArgument] inheritor.
|
||||
* An optional argument having nullable value is represented with the [SingleNullableArgument] inheritor.
|
||||
*/
|
||||
// TODO: investigate if we can collapse two inheritors into the single base class and specialize extensions by TResult upper bound
|
||||
abstract class AbstractSingleArgument<T: Any, TResult, DefaultRequired: DefaultRequiredType> internal constructor(
|
||||
delegate: ArgumentValueDelegate<TResult>,
|
||||
owner: CLIEntityWrapper):
|
||||
Argument<TResult>(delegate, owner) {
|
||||
/**
|
||||
* Check descriptor for this kind of argument.
|
||||
*/
|
||||
internal fun checkDescriptor(descriptor: ArgDescriptor<*, *>) {
|
||||
if (descriptor.number == null || descriptor.number > 1) {
|
||||
failAssertion("Argument with single value can't be initialized with descriptor for multiple values.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A non-optional argument or an optional argument with a default value.
|
||||
*
|
||||
* The [value] of such argument is non-null.
|
||||
*/
|
||||
class SingleArgument<T : Any, DefaultRequired: DefaultRequiredType> internal constructor(descriptor: ArgDescriptor<T, T>,
|
||||
owner: CLIEntityWrapper):
|
||||
AbstractSingleArgument<T, T, DefaultRequired>(ArgumentSingleValue(descriptor), owner) {
|
||||
init {
|
||||
checkDescriptor(descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An optional argument with nullable [value].
|
||||
*/
|
||||
class SingleNullableArgument<T : Any> internal constructor(descriptor: ArgDescriptor<T, T>, owner: CLIEntityWrapper):
|
||||
AbstractSingleArgument<T, T?, DefaultRequiredType.None>(ArgumentSingleNullableValue(descriptor), owner) {
|
||||
init {
|
||||
checkDescriptor(descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An argument that allows several values to be provided in command line string.
|
||||
*
|
||||
* The [value] property of such argument has type `List<T>`.
|
||||
*/
|
||||
class MultipleArgument<T : Any, DefaultRequired: DefaultRequiredType> internal constructor(
|
||||
descriptor: ArgDescriptor<T, List<T>>, owner: CLIEntityWrapper):
|
||||
Argument<List<T>>(ArgumentMultipleValues(descriptor), owner) {
|
||||
init {
|
||||
if (descriptor.number != null && descriptor.number < 2) {
|
||||
failAssertion("Argument with multiple values can't be initialized with descriptor for single one.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the argument to have several values specified in command line string.
|
||||
*
|
||||
* @param number the exact number of values expected for this argument, but at least 2.
|
||||
*
|
||||
* @throws IllegalArgumentException if number of values expected for this argument less than 2.
|
||||
*/
|
||||
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) {
|
||||
MultipleArgument<T, DefaultRequired>(ArgDescriptor(type, fullName, number, description, listOfNotNull(defaultValue),
|
||||
required, deprecatedWarning), owner)
|
||||
}
|
||||
owner.entity = newArgument
|
||||
return newArgument
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the last argument to take all the trailing values in command line string.
|
||||
*/
|
||||
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) {
|
||||
MultipleArgument<T, DefaultRequired>(ArgDescriptor(type, fullName, null, description, listOfNotNull(defaultValue),
|
||||
required, deprecatedWarning), owner)
|
||||
}
|
||||
owner.entity = newArgument
|
||||
return newArgument
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the default value for the argument, that will be used when no value is provided for the argument
|
||||
* in command line string.
|
||||
*
|
||||
* Argument becomes optional, because value for it is set even if it isn't provided in command line.
|
||||
*
|
||||
* @param value the default value.
|
||||
*/
|
||||
fun <T: Any> SingleNullableArgument<T>.default(value: T): SingleArgument<T, DefaultRequiredType.Default> {
|
||||
val newArgument = with(delegate.cast<ParsingValue<T, T>>().descriptor as ArgDescriptor) {
|
||||
SingleArgument<T, DefaultRequiredType.Default>(ArgDescriptor(type, fullName, number, description, value,
|
||||
false, deprecatedWarning), owner)
|
||||
}
|
||||
owner.entity = newArgument
|
||||
return newArgument
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the default value for the argument with multiple values, that will be used when no values are provided
|
||||
* for the argument in command line string.
|
||||
*
|
||||
* Argument becomes optional, because value for it is set even if it isn't provided in command line.
|
||||
*
|
||||
* @param value the default value, must be a non-empty collection.
|
||||
*/
|
||||
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) {
|
||||
MultipleArgument<T, DefaultRequiredType.Default>(ArgDescriptor(type, fullName, number, description, value.toList(),
|
||||
required, deprecatedWarning), owner)
|
||||
}
|
||||
owner.entity = newArgument
|
||||
return newArgument
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the argument to have no value specified in command line string.
|
||||
*
|
||||
* The value of the argument is `null` in case if no value was specified in command line string.
|
||||
*
|
||||
* 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) {
|
||||
SingleNullableArgument(ArgDescriptor(type, fullName, number, description, defaultValue,
|
||||
false, deprecatedWarning), owner)
|
||||
}
|
||||
owner.entity = newArgument
|
||||
return newArgument
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the argument with multiple values to have no values specified in command line string.
|
||||
*
|
||||
* The value of the argument is an empty list in case if no value was specified in command line string.
|
||||
*
|
||||
* 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) {
|
||||
MultipleArgument<T, DefaultRequiredType.None>(ArgDescriptor(type, fullName, number, description,
|
||||
defaultValue?.toList() ?: listOf(), false, deprecatedWarning), owner)
|
||||
}
|
||||
owner.entity = newArgument
|
||||
return newArgument
|
||||
}
|
||||
|
||||
internal fun failAssertion(message: String): Nothing = throw AssertionError(message)
|
||||
|
||||
internal inline fun <reified T : Any> Any?.cast(): T = this as T
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
package kotlinx.cli
|
||||
|
||||
/**
|
||||
* Common descriptor both for options and positional arguments.
|
||||
*
|
||||
* @property type option/argument type, one of [ArgType].
|
||||
* @property fullName option/argument full name.
|
||||
* @property description text description of option/argument.
|
||||
* @property defaultValue default value for option/argument.
|
||||
* @property required if option/argument is required or not. If it's required and not provided in command line, error will be generated.
|
||||
* @property deprecatedWarning text message with information in case if option is deprecated.
|
||||
*/
|
||||
internal abstract class Descriptor<T : Any, TResult>(val type: ArgType<T>,
|
||||
var fullName: String? = null,
|
||||
val description: String? = null,
|
||||
val defaultValue: TResult? = null,
|
||||
val required: Boolean = false,
|
||||
val deprecatedWarning: String? = null) {
|
||||
/**
|
||||
* Text description for help message.
|
||||
*/
|
||||
abstract val textDescription: String
|
||||
/**
|
||||
* Help message for descriptor.
|
||||
*/
|
||||
abstract val helpMessage: String
|
||||
|
||||
/**
|
||||
* Provide text description of value.
|
||||
*
|
||||
* @param value value got getting text description for.
|
||||
*/
|
||||
fun valueDescription(value: TResult?) = value?.let {
|
||||
if (it is List<*> && it.isNotEmpty())
|
||||
" [${it.joinToString()}]"
|
||||
else if (it !is List<*>)
|
||||
" [$it]"
|
||||
else null
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag to check if descriptor has set default value for option/argument.
|
||||
*/
|
||||
val defaultValueSet by lazy {
|
||||
defaultValue != null && (defaultValue is List<*> && defaultValue.isNotEmpty() || defaultValue !is List<*>)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Option descriptor.
|
||||
*
|
||||
* Command line entity started with some prefix (-/--) and can have value as next entity in command line string.
|
||||
*
|
||||
* @property optionFullFormPrefix prefix used before full form of option.
|
||||
* @property optionShortFromPrefix prefix used before short form of option.
|
||||
* @property type option type, one of [ArgType].
|
||||
* @property fullName option full name.
|
||||
* @property shortName option short name.
|
||||
* @property description text description of option.
|
||||
* @property defaultValue default value for option.
|
||||
* @property required if option is required or not. If it's required and not provided in command line, error will be generated.
|
||||
* @property multiple if option can be repeated several times in command line with different values. All values are stored.
|
||||
* @property delimiter delimiter that separate option provided as one string to several values.
|
||||
* @property deprecatedWarning text message with information in case if option is deprecated.
|
||||
*/
|
||||
internal class OptionDescriptor<T : Any, TResult>(
|
||||
val optionFullFormPrefix: String,
|
||||
val optionShortFromPrefix: String,
|
||||
type: ArgType<T>,
|
||||
fullName: String? = null,
|
||||
val shortName: String ? = null,
|
||||
description: String? = null,
|
||||
defaultValue: TResult? = null,
|
||||
required: Boolean = false,
|
||||
val multiple: Boolean = false,
|
||||
val delimiter: String? = null,
|
||||
deprecatedWarning: String? = null) : Descriptor<T, TResult>(type, fullName, description, defaultValue,
|
||||
required, deprecatedWarning) {
|
||||
|
||||
override val textDescription: String
|
||||
get() = "option $optionFullFormPrefix$fullName"
|
||||
|
||||
override val helpMessage: String
|
||||
get() {
|
||||
val result = StringBuilder()
|
||||
result.append(" $optionFullFormPrefix$fullName")
|
||||
shortName?.let { result.append(", $optionShortFromPrefix$it") }
|
||||
valueDescription(defaultValue)?.let {
|
||||
result.append(it)
|
||||
}
|
||||
description?.let {result.append(" -> $it")}
|
||||
if (required) result.append(" (always required)")
|
||||
result.append(" ${type.description}")
|
||||
deprecatedWarning?.let { result.append(" Warning: $it") }
|
||||
result.append("\n")
|
||||
return result.toString()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Argument descriptor.
|
||||
*
|
||||
* Command line entity which role is connected only with its position.
|
||||
*
|
||||
* @property type argument type, one of [ArgType].
|
||||
* @property fullName argument full name.
|
||||
* @property number expected number of values. Null means any possible number of values.
|
||||
* @property description text description of argument.
|
||||
* @property defaultValue default value for argument.
|
||||
* @property required if argument is required or not. If it's required and not provided in command line and have no default value, error will be generated.
|
||||
* @property deprecatedWarning text message with information in case if argument is deprecated.
|
||||
*/
|
||||
internal class ArgDescriptor<T : Any, TResult>(
|
||||
type: ArgType<T>,
|
||||
fullName: String?,
|
||||
val number: Int? = null,
|
||||
description: String? = null,
|
||||
defaultValue: TResult? = null,
|
||||
required: Boolean = true,
|
||||
deprecatedWarning: String? = null) : Descriptor<T, TResult>(type, fullName, description, defaultValue,
|
||||
required, deprecatedWarning) {
|
||||
|
||||
init {
|
||||
// Check arguments number correctness.
|
||||
number?.let {
|
||||
if (it < 1)
|
||||
error("Number of arguments for argument description $fullName should be greater than zero.")
|
||||
}
|
||||
}
|
||||
|
||||
override val textDescription: String
|
||||
get() = "argument $fullName"
|
||||
|
||||
override val helpMessage: String
|
||||
get() {
|
||||
val result = StringBuilder()
|
||||
result.append(" ${fullName}")
|
||||
valueDescription(defaultValue)?.let {
|
||||
result.append(it)
|
||||
}
|
||||
description?.let { result.append(" -> $it") }
|
||||
if (!required) result.append(" (optional)")
|
||||
result.append(" ${type.description}")
|
||||
deprecatedWarning?.let { result.append(" Warning: $it") }
|
||||
result.append("\n")
|
||||
return result.toString()
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package kotlinx.cli
|
||||
|
||||
import kotlin.annotation.AnnotationTarget.*
|
||||
|
||||
/**
|
||||
* This annotation marks the experimental API for working with command line arguments.
|
||||
*
|
||||
* > Beware using the annotated API especially if you're developing a library, since your library might become binary incompatible
|
||||
* with the future versions of the CLI library.
|
||||
*
|
||||
* Any usage of a declaration annotated with `@ExperimentalCli` must be accepted either by
|
||||
* 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)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@Target(
|
||||
CLASS,
|
||||
ANNOTATION_CLASS,
|
||||
PROPERTY,
|
||||
FIELD,
|
||||
LOCAL_VARIABLE,
|
||||
VALUE_PARAMETER,
|
||||
CONSTRUCTOR,
|
||||
FUNCTION,
|
||||
PROPERTY_GETTER,
|
||||
PROPERTY_SETTER,
|
||||
TYPEALIAS
|
||||
)
|
||||
|
||||
public annotation class ExperimentalCli
|
||||
@@ -0,0 +1,266 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
package kotlinx.cli
|
||||
|
||||
/**
|
||||
* Base interface for all possible types of options with multiple values.
|
||||
* Provides limitations for API that is accessible for options with several values.
|
||||
* Allows to save the way of providing several values in command line.
|
||||
*
|
||||
* @see [MultipleOption]
|
||||
*/
|
||||
interface MultipleOptionType {
|
||||
/**
|
||||
* Type of an option with multiple values allowed to be provided several times in command line.
|
||||
*/
|
||||
class Repeated : MultipleOptionType
|
||||
|
||||
/**
|
||||
* Type of an option with multiple values allowed to be provided using delimiter in one command line value.
|
||||
*/
|
||||
class Delimited : MultipleOptionType
|
||||
|
||||
/**
|
||||
* Type of an option with multiple values allowed to be provided several times in command line
|
||||
* both with specifying several values and with delimiter.
|
||||
*/
|
||||
class RepeatedDelimited : MultipleOptionType
|
||||
}
|
||||
|
||||
/**
|
||||
* The base class for command line options.
|
||||
*
|
||||
* You can use [ArgParser.option] function to declare an option.
|
||||
*/
|
||||
abstract class Option<TResult> internal constructor(delegate: ArgumentValueDelegate<TResult>,
|
||||
owner: CLIEntityWrapper) : CLIEntity<TResult>(delegate, owner)
|
||||
|
||||
/**
|
||||
* The base class of an option with a single value.
|
||||
*
|
||||
* A required option or an option with a default value is represented with the [SingleOption] inheritor.
|
||||
* An option having nullable value is represented with the [SingleNullableOption] inheritor.
|
||||
*/
|
||||
abstract class AbstractSingleOption<T : Any, TResult, DefaultRequired: DefaultRequiredType> internal constructor(
|
||||
delegate: ArgumentValueDelegate<TResult>,
|
||||
owner: CLIEntityWrapper) :
|
||||
Option<TResult>(delegate, owner) {
|
||||
/**
|
||||
* Check descriptor for this kind of option.
|
||||
*/
|
||||
internal fun checkDescriptor(descriptor: OptionDescriptor<*, *>) {
|
||||
if (descriptor.multiple || descriptor.delimiter != null) {
|
||||
failAssertion("Option with single value can't be initialized with descriptor for multiple values.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A required option or an option with a default value.
|
||||
*
|
||||
* The [value] of such option is non-null.
|
||||
*/
|
||||
class SingleOption<T : Any, DefaultType: DefaultRequiredType> internal constructor(descriptor: OptionDescriptor<T, T>,
|
||||
owner: CLIEntityWrapper) :
|
||||
AbstractSingleOption<T, T, DefaultRequiredType>(ArgumentSingleValue(descriptor), owner) {
|
||||
init {
|
||||
checkDescriptor(descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An option with nullable [value].
|
||||
*/
|
||||
class SingleNullableOption<T : Any> internal constructor(descriptor: OptionDescriptor<T, T>, owner: CLIEntityWrapper) :
|
||||
AbstractSingleOption<T, T?, DefaultRequiredType.None>(ArgumentSingleNullableValue(descriptor), owner) {
|
||||
init {
|
||||
checkDescriptor(descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An option that allows several values to be provided in command line string.
|
||||
*
|
||||
* The [value] property of such option has type `List<T>`.
|
||||
*/
|
||||
class MultipleOption<T : Any, OptionType : MultipleOptionType, DefaultType: DefaultRequiredType> internal constructor(
|
||||
descriptor: OptionDescriptor<T, List<T>>,
|
||||
owner: CLIEntityWrapper
|
||||
) :
|
||||
Option<List<T>>( ArgumentMultipleValues(descriptor), owner) {
|
||||
init {
|
||||
if (!descriptor.multiple && descriptor.delimiter == null) {
|
||||
failAssertion("Option with multiple values can't be initialized with descriptor for single one.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the option to have several values specified in command line string.
|
||||
* Number of values is unlimited.
|
||||
*/
|
||||
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) {
|
||||
MultipleOption<T, MultipleOptionType.Repeated, DefaultType>(
|
||||
OptionDescriptor(
|
||||
optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
|
||||
description, listOfNotNull(defaultValue),
|
||||
required, true, delimiter, deprecatedWarning
|
||||
), owner
|
||||
)
|
||||
}
|
||||
owner.entity = newOption
|
||||
return newOption
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the option to have several values specified in command line string.
|
||||
* Number of values is unlimited.
|
||||
*/
|
||||
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) {
|
||||
if (multiple) {
|
||||
error("Try to use modifier multiple() twice on option ${fullName ?: ""}")
|
||||
}
|
||||
MultipleOption<T, MultipleOptionType.RepeatedDelimited, DefaultRequiredType>(
|
||||
OptionDescriptor(
|
||||
optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
|
||||
description, defaultValue?.toList() ?: listOf(),
|
||||
required, true, delimiter, deprecatedWarning
|
||||
), owner
|
||||
)
|
||||
}
|
||||
owner.entity = newOption
|
||||
return newOption
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the default value for the option, that will be used when no value is provided for it
|
||||
* in command line string.
|
||||
*
|
||||
* @param value the default value.
|
||||
*/
|
||||
fun <T : Any> SingleNullableOption<T>.default(value: T): SingleOption<T, DefaultRequiredType.Default> {
|
||||
val newOption = with(delegate.cast<ParsingValue<T, T>>().descriptor as OptionDescriptor) {
|
||||
SingleOption<T, DefaultRequiredType.Default>(
|
||||
OptionDescriptor(
|
||||
optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
|
||||
description, value, required, multiple, delimiter, deprecatedWarning
|
||||
), owner
|
||||
)
|
||||
}
|
||||
owner.entity = newOption
|
||||
return newOption
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the default value for the option with multiple values, that will be used when no values are provided
|
||||
* for it in command line string.
|
||||
*
|
||||
* @param value the default value, must be a non-empty collection.
|
||||
* @throws IllegalArgumentException if provided default value is empty collection.
|
||||
*/
|
||||
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) {
|
||||
require(value.isNotEmpty()) { "Default value for option can't be empty collection." }
|
||||
MultipleOption<T, OptionType, DefaultRequiredType.Default>(
|
||||
OptionDescriptor(
|
||||
optionFullFormPrefix, optionShortFromPrefix, type, fullName,
|
||||
shortName, description, value.toList(),
|
||||
required, multiple, delimiter, deprecatedWarning
|
||||
), owner
|
||||
)
|
||||
}
|
||||
owner.entity = newOption
|
||||
return newOption
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
SingleOption<T, DefaultRequiredType.Required>(
|
||||
OptionDescriptor(
|
||||
optionFullFormPrefix, optionShortFromPrefix, type, fullName,
|
||||
shortName, description, defaultValue,
|
||||
true, multiple, delimiter, deprecatedWarning
|
||||
), owner
|
||||
)
|
||||
}
|
||||
owner.entity = newOption
|
||||
return newOption
|
||||
}
|
||||
|
||||
/**
|
||||
* Requires the option to be always provided in command line.
|
||||
*/
|
||||
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) {
|
||||
MultipleOption<T, OptionType, DefaultRequiredType.Required>(
|
||||
OptionDescriptor(
|
||||
optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
|
||||
description, defaultValue?.toList() ?: listOf(),
|
||||
true, multiple, delimiter, deprecatedWarning
|
||||
), owner
|
||||
)
|
||||
}
|
||||
owner.entity = newOption
|
||||
return newOption
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the option to have several values joined with [delimiter] specified in command line string.
|
||||
* Number of values is unlimited.
|
||||
*
|
||||
* The value of the argument is an empty list in case if no value was specified in command line string.
|
||||
*
|
||||
* @param delimiterValue delimiter used to separate string value to option values list.
|
||||
*/
|
||||
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) {
|
||||
MultipleOption<T, MultipleOptionType.Delimited, DefaultRequired>(
|
||||
OptionDescriptor(
|
||||
optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
|
||||
description, listOfNotNull(defaultValue),
|
||||
required, multiple, delimiterValue, deprecatedWarning
|
||||
), owner
|
||||
)
|
||||
}
|
||||
owner.entity = newOption
|
||||
return newOption
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the option to have several values joined with [delimiter] specified in command line string.
|
||||
* Number of values is unlimited.
|
||||
*
|
||||
* The value of the argument is an empty list in case if no value was specified in command line string.
|
||||
*
|
||||
* @param delimiterValue delimiter used to separate string value to option values list.
|
||||
*/
|
||||
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) {
|
||||
MultipleOption<T, MultipleOptionType.RepeatedDelimited, DefaultRequired>(
|
||||
OptionDescriptor(
|
||||
optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
|
||||
description, defaultValue?.toList() ?: listOf(),
|
||||
required, multiple, delimiterValue, deprecatedWarning
|
||||
), owner
|
||||
)
|
||||
}
|
||||
owner.entity = newOption
|
||||
return newOption
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package kotlinx.cli
|
||||
|
||||
import kotlinx.cli.ArgParser
|
||||
import kotlinx.cli.ArgType
|
||||
import kotlin.test.*
|
||||
|
||||
class ArgumentsTests {
|
||||
@Test
|
||||
fun testPositionalArguments() {
|
||||
val argParser = ArgParser("testParser")
|
||||
val debugMode by argParser.option(ArgType.Boolean, "debug", "d", "Debug mode")
|
||||
val input by argParser.argument(ArgType.String, "input", "Input file")
|
||||
val output by argParser.argument(ArgType.String, "output", "Output file")
|
||||
argParser.parse(arrayOf("-d", "input.txt", "out.txt"))
|
||||
assertEquals(true, debugMode)
|
||||
assertEquals("out.txt", output)
|
||||
assertEquals("input.txt", input)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testArgumetsWithAnyNumberOfValues() {
|
||||
val argParser = ArgParser("testParser")
|
||||
val output by argParser.argument(ArgType.String, "output", "Output file")
|
||||
val inputs by argParser.argument(ArgType.String, description = "Input files").vararg()
|
||||
argParser.parse(arrayOf("out.txt", "input1.txt", "input2.txt", "input3.txt",
|
||||
"input4.txt"))
|
||||
assertEquals("out.txt", output)
|
||||
assertEquals(4, inputs.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testArgumetsWithSeveralValues() {
|
||||
val argParser = ArgParser("testParser")
|
||||
val addendums by argParser.argument(ArgType.Int, "addendums", description = "Addendums").multiple(2)
|
||||
val output by argParser.argument(ArgType.String, "output", "Output file")
|
||||
val debugMode by argParser.option(ArgType.Boolean, "debug", "d", "Debug mode")
|
||||
argParser.parse(arrayOf("2", "-d", "3", "out.txt"))
|
||||
assertEquals("out.txt", output)
|
||||
val (first, second) = addendums
|
||||
assertEquals(2, addendums.size)
|
||||
assertEquals(2, first)
|
||||
assertEquals(3, second)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSkippingExtraArguments() {
|
||||
val argParser = ArgParser("testParser", skipExtraArguments = true)
|
||||
val addendums by argParser.argument(ArgType.Int, "addendums", description = "Addendums").multiple(2)
|
||||
val output by argParser.argument(ArgType.String, "output", "Output file")
|
||||
val debugMode by argParser.option(ArgType.Boolean, "debug", "d", "Debug mode")
|
||||
argParser.parse(arrayOf("2", "-d", "3", "out.txt", "something", "else", "in", "string"))
|
||||
assertEquals("out.txt", output)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package kotlinx.cli
|
||||
|
||||
import kotlinx.cli.ArgParser
|
||||
import kotlinx.cli.ArgType
|
||||
import kotlin.test.*
|
||||
|
||||
class ErrorTests {
|
||||
@Test
|
||||
fun testExtraArguments() {
|
||||
val argParser = ArgParser("testParser")
|
||||
val addendums by argParser.argument(ArgType.Int, "addendums", description = "Addendums").multiple(2)
|
||||
val output by argParser.argument(ArgType.String, "output", "Output file")
|
||||
val debugMode by argParser.option(ArgType.Boolean, "debug", "d", "Debug mode")
|
||||
val exception = assertFailsWith<IllegalStateException> { argParser.parse(
|
||||
arrayOf("2", "-d", "3", "out.txt", "something", "else", "in", "string")) }
|
||||
assertTrue("Too many arguments! Couldn't process argument something" in exception.message!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUnknownOption() {
|
||||
val argParser = ArgParser("testParser")
|
||||
val output by argParser.option(ArgType.String, "output", "o", "Output file")
|
||||
val input by argParser.option(ArgType.String, "input", "i", "Input file")
|
||||
val exception = assertFailsWith<IllegalStateException> {
|
||||
argParser.parse(arrayOf("-o", "out.txt", "-d", "-i", "input.txt"))
|
||||
}
|
||||
assertTrue("Unknown option -d" in exception.message!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testWrongFormat() {
|
||||
val argParser = ArgParser("testParser")
|
||||
val number by argParser.option(ArgType.Int, "number", description = "Integer number")
|
||||
val exception = assertFailsWith<IllegalStateException> {
|
||||
argParser.parse(arrayOf("--number", "out.txt"))
|
||||
}
|
||||
assertTrue("Option number is expected to be integer number. out.txt is provided." in exception.message!!)
|
||||
}
|
||||
|
||||
@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 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!!)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
@file:OptIn(ExperimentalCli::class)
|
||||
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 {
|
||||
@Test
|
||||
fun testHelpMessage() {
|
||||
val argParser = ArgParser("test")
|
||||
val mainReport by argParser.argument(ArgType.String, description = "Main report for analysis")
|
||||
val compareToReport by argParser.argument(ArgType.String, description = "Report to compare to").optional()
|
||||
|
||||
val output by argParser.option(ArgType.String, shortName = "o", description = "Output file")
|
||||
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")),
|
||||
shortName = "r", description = "Renders for showing information").multiple().default(listOf("text"))
|
||||
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()
|
||||
@Suppress("CanBeVal") // can't be val in order to build expectedOutput only in run time.
|
||||
var epsDefault = 1.0
|
||||
val expectedOutput = """
|
||||
Usage: test options_list
|
||||
Arguments:
|
||||
mainReport -> Main report for analysis { String }
|
||||
compareToReport -> Report to compare to (optional) { String }
|
||||
Options:
|
||||
--output, -o -> Output file { String }
|
||||
--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] }
|
||||
--user, -u -> User access information for authorization { String }
|
||||
--help, -h -> Usage info
|
||||
""".trimIndent()
|
||||
assertEquals(expectedOutput, helpOutput)
|
||||
}
|
||||
|
||||
@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 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 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 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 user by option(ArgType.String, shortName = "u", description = "User access information for authorization")
|
||||
val mainReport by argument(ArgType.String, description = "Main report for analysis")
|
||||
|
||||
override fun execute() {
|
||||
println("Do some important things!")
|
||||
}
|
||||
}
|
||||
val action = Summary()
|
||||
// Parse args.
|
||||
val argParser = ArgParser("test")
|
||||
argParser.subcommands(action)
|
||||
argParser.parse(arrayOf("summary", "out.txt"))
|
||||
val helpOutput = action.makeUsage().trimIndent()
|
||||
val expectedOutput = """
|
||||
Usage: test summary options_list
|
||||
Arguments:
|
||||
mainReport -> Main report for analysis { String }
|
||||
Options:
|
||||
--exec [geomean] -> Execution time way of calculation { Value should be one of [samples, geomean] }
|
||||
--exec-samples -> Samples used for execution time metric (value 'all' allows use all samples) { String }
|
||||
--exec-normalize -> File with golden results which should be used for normalization { String }
|
||||
--compile [geomean] -> Compile time way of calculation { Value should be one of [samples, geomean] }
|
||||
--compile-samples -> Samples used for compile time metric (value 'all' allows use all samples) { String }
|
||||
--compile-normalize -> File with golden results which should be used for normalization { String }
|
||||
--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 }
|
||||
--user, -u -> User access information for authorization { String }
|
||||
--help, -h -> Usage info
|
||||
""".trimIndent()
|
||||
assertEquals(expectedOutput, helpOutput)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHelpMessageWithSubcommands() {
|
||||
abstract class CommonOptions(name: String, actionDescription: String): Subcommand(name, actionDescription) {
|
||||
val numbers by argument(ArgType.Int, "numbers", description = "Numbers").vararg()
|
||||
}
|
||||
class Summary: CommonOptions("summary", "Calculate summary") {
|
||||
val invert by option(ArgType.Boolean, "invert", "i", "Invert results")
|
||||
var result: Int = 0
|
||||
|
||||
override fun execute() {
|
||||
result = numbers.sum()
|
||||
result = invert?.let { -1 * result } ?: result
|
||||
}
|
||||
}
|
||||
|
||||
class Subtraction : CommonOptions("sub", "Calculate subtraction") {
|
||||
var result: Int = 0
|
||||
|
||||
override fun execute() {
|
||||
result = numbers.map { -it }.sum()
|
||||
}
|
||||
}
|
||||
|
||||
val summaryAction = Summary()
|
||||
val subtractionAction = Subtraction()
|
||||
val argParser = ArgParser("testParser")
|
||||
argParser.subcommands(summaryAction, subtractionAction)
|
||||
argParser.parse(emptyArray())
|
||||
val helpOutput = argParser.makeUsage().trimIndent()
|
||||
println(helpOutput)
|
||||
val expectedOutput = """
|
||||
Usage: testParser options_list
|
||||
Subcommands:
|
||||
summary - Calculate summary
|
||||
sub - Calculate subtraction
|
||||
|
||||
Options:
|
||||
--help, -h -> Usage info
|
||||
""".trimIndent()
|
||||
assertEquals(expectedOutput, helpOutput)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package kotlinx.cli
|
||||
|
||||
import kotlin.test.*
|
||||
|
||||
class OptionsTests {
|
||||
@Test
|
||||
fun testShortForm() {
|
||||
val argParser = ArgParser("testParser")
|
||||
val output by argParser.option(ArgType.String, "output", "o", "Output file")
|
||||
val input by argParser.option(ArgType.String, "input", "i", "Input file")
|
||||
argParser.parse(arrayOf("-o", "out.txt", "-i", "input.txt"))
|
||||
assertEquals("out.txt", output)
|
||||
assertEquals("input.txt", input)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFullForm() {
|
||||
val argParser = ArgParser("testParser")
|
||||
val output by argParser.option(ArgType.String, shortName = "o", description = "Output file")
|
||||
val input by argParser.option(ArgType.String, shortName = "i", description = "Input file")
|
||||
argParser.parse(arrayOf("--output", "out.txt", "--input", "input.txt"))
|
||||
assertEquals("out.txt", output)
|
||||
assertEquals("input.txt", input)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testJavaPrefix() {
|
||||
val argParser = ArgParser("testParser", prefixStyle = ArgParser.OptionPrefixStyle.JVM)
|
||||
val output by argParser.option(ArgType.String, "output", "o", "Output file")
|
||||
val input by argParser.option(ArgType.String, "input", "i", "Input file")
|
||||
argParser.parse(arrayOf("-output", "out.txt", "-i", "input.txt"))
|
||||
assertEquals("out.txt", output)
|
||||
assertEquals("input.txt", input)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGNUPrefix() {
|
||||
val argParser = ArgParser("testParser", prefixStyle = ArgParser.OptionPrefixStyle.GNU)
|
||||
val output by argParser.option(ArgType.String, "output", "o", "Output file")
|
||||
val input by argParser.option(ArgType.String, "input", "i", "Input file")
|
||||
val verbose by argParser.option(ArgType.Boolean, "verbose", "v", "Verbose print")
|
||||
val shortForm by argParser.option(ArgType.Boolean, "short", "s", "Short output form")
|
||||
val text by argParser.option(ArgType.Boolean, "text", "t", "Use text format")
|
||||
argParser.parse(arrayOf("-oout.txt", "--input=input.txt", "-vst"))
|
||||
assertEquals("out.txt", output)
|
||||
assertEquals("input.txt", input)
|
||||
assertEquals(verbose, true)
|
||||
assertEquals(shortForm, true)
|
||||
assertEquals(text, true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGNUArguments() {
|
||||
val argParser = ArgParser("testParser", prefixStyle = ArgParser.OptionPrefixStyle.GNU)
|
||||
val output by argParser.argument(ArgType.String, "output", "Output file")
|
||||
val input by argParser.argument(ArgType.String, "input", "Input file")
|
||||
val verbose by argParser.option(ArgType.Boolean, "verbose", "v", "Verbose print")
|
||||
val shortForm by argParser.option(ArgType.Boolean, "short", "s", "Short output form").default(false)
|
||||
val text by argParser.option(ArgType.Boolean, "text", "t", "Use text format").default(false)
|
||||
argParser.parse(arrayOf("--verbose", "--", "out.txt", "--input.txt"))
|
||||
assertEquals("out.txt", output)
|
||||
assertEquals("--input.txt", input)
|
||||
assertEquals(verbose, true)
|
||||
assertEquals(shortForm, false)
|
||||
assertEquals(text, false)
|
||||
}
|
||||
|
||||
@Test
|
||||
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"))
|
||||
assertEquals(true, useShortForm)
|
||||
assertEquals(2, renders.size)
|
||||
val (firstRender, secondRender) = renders
|
||||
assertEquals("text", firstRender)
|
||||
assertEquals("json", secondRender)
|
||||
}
|
||||
|
||||
@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 output by argParser.option(ArgType.String, "output", "o", "Output file")
|
||||
argParser.parse(arrayOf("-o", "out.txt"))
|
||||
assertEquals(false, useShortForm)
|
||||
assertEquals("text", renders[0])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testResetOptionsValues() {
|
||||
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"))
|
||||
var renders by rendersOption
|
||||
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()
|
||||
assertEquals(true, useShortForm)
|
||||
assertEquals(null, output)
|
||||
assertEquals(0, renders.size)
|
||||
assertEquals(ArgParser.ValueOrigin.REDEFINED, outputOption.valueOrigin)
|
||||
assertEquals(ArgParser.ValueOrigin.REDEFINED, useShortFormOption.valueOrigin)
|
||||
assertEquals(ArgParser.ValueOrigin.REDEFINED, rendersOption.valueOrigin)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
@file:OptIn(ExperimentalCli::class)
|
||||
package kotlinx.cli
|
||||
|
||||
import kotlinx.cli.ArgParser
|
||||
import kotlinx.cli.ArgType
|
||||
import kotlinx.cli.ExperimentalCli
|
||||
import kotlinx.cli.Subcommand
|
||||
import kotlin.test.*
|
||||
|
||||
class SubcommandsTests {
|
||||
@Test
|
||||
fun testSubcommand() {
|
||||
val argParser = ArgParser("testParser")
|
||||
val output by argParser.option(ArgType.String, "output", "o", "Output file")
|
||||
class Summary: Subcommand("summary", "Calculate summary") {
|
||||
val invert by option(ArgType.Boolean, "invert", "i", "Invert results")
|
||||
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
|
||||
}
|
||||
}
|
||||
val action = Summary()
|
||||
argParser.subcommands(action)
|
||||
argParser.parse(arrayOf("-o", "out.txt", "summary", "-i", "2", "3", "5"))
|
||||
assertEquals("out.txt", output)
|
||||
assertEquals(-10, action.result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCommonOptions() {
|
||||
abstract class CommonOptions(name: String, actionDescription: String): Subcommand(name, actionDescription) {
|
||||
val numbers by argument(ArgType.Int, "numbers", description = "Numbers").vararg()
|
||||
}
|
||||
class Summary: CommonOptions("summary", "Calculate summary") {
|
||||
val invert by option(ArgType.Boolean, "invert", "i", "Invert results")
|
||||
var result: Int = 0
|
||||
|
||||
override fun execute() {
|
||||
result = numbers.sum()
|
||||
result = invert?.let { -1 * result } ?: result
|
||||
}
|
||||
}
|
||||
|
||||
class Subtraction : CommonOptions("sub", "Calculate subtraction") {
|
||||
var result: Int = 0
|
||||
|
||||
override fun execute() {
|
||||
result = numbers.map { -it }.sum()
|
||||
}
|
||||
}
|
||||
|
||||
val summaryAction = Summary()
|
||||
val subtractionAction = Subtraction()
|
||||
val argParser = ArgParser("testParser")
|
||||
argParser.subcommands(summaryAction, subtractionAction)
|
||||
argParser.parse(arrayOf("summary", "2", "3", "5"))
|
||||
assertEquals(10, summaryAction.result)
|
||||
|
||||
val argParserSubtraction = ArgParser("testParser")
|
||||
argParserSubtraction.subcommands(summaryAction, subtractionAction)
|
||||
argParserSubtraction.parse(arrayOf("sub", "8", "-2", "3"))
|
||||
assertEquals(-9, subtractionAction.result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRecursiveSubcommands() {
|
||||
val argParser = ArgParser("testParser")
|
||||
|
||||
class Summary: Subcommand("summary", "Calculate summary") {
|
||||
val addendums by argument(ArgType.Int, "addendums", description = "Addendums").vararg()
|
||||
var result: Int = 0
|
||||
|
||||
override fun execute() {
|
||||
result = addendums.sum()
|
||||
}
|
||||
}
|
||||
|
||||
class Calculation: Subcommand("calc", "Execute calculation") {
|
||||
init {
|
||||
subcommands(Summary())
|
||||
}
|
||||
val invert by option(ArgType.Boolean, "invert", "i", "Invert results")
|
||||
var result: Int = 0
|
||||
|
||||
override fun execute() {
|
||||
result = (subcommands["summary"] as Summary).result
|
||||
result = if (invert!!) -1 * result else result
|
||||
}
|
||||
}
|
||||
|
||||
val action = Calculation()
|
||||
argParser.subcommands(action)
|
||||
argParser.parse(arrayOf("calc", "-i", "summary", "2", "3", "5"))
|
||||
assertEquals(-10, action.result)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user