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:
Stanislav Erokhin
2020-10-27 21:00:28 +03:00
parent 91e4162dad
commit f624800b84
2830 changed files with 0 additions and 0 deletions
@@ -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!")
}
@@ -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)
}
@@ -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)
@@ -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
@@ -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()
}
}
@@ -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)
}
}