From 623fdf033038262f8c85952317161b4705388744 Mon Sep 17 00:00:00 2001 From: AJ Alt Date: Sat, 22 Feb 2020 15:48:54 -0800 Subject: [PATCH] Simplify creating eager options (#135) --- CHANGELOG.md | 5 ++ .../ajalt/clikt/core/ParameterHolder.kt | 9 +- .../com/github/ajalt/clikt/core/exceptions.kt | 2 +- .../kotlin/com/github/ajalt/clikt/mpp/MppH.kt | 4 +- .../ajalt/clikt/output/CliktHelpFormatter.kt | 2 +- .../com/github/ajalt/clikt/output/TermUi.kt | 4 +- .../clikt/parameters/arguments/Argument.kt | 2 +- .../clikt/parameters/groups/ChoiceGroup.kt | 1 - .../clikt/parameters/options/EagerOption.kt | 83 +++++++++++++++---- .../ajalt/clikt/parameters/options/Option.kt | 9 +- .../ajalt/clikt/core/CliktCommandTest.kt | 6 +- .../github/ajalt/clikt/core/ContextTest.kt | 4 +- .../clikt/output/CliktHelpFormatterTest.kt | 19 +++-- .../clikt/parameters/EagerOptionsTest.kt | 43 +++++++++- .../ajalt/clikt/parameters/OptionTest.kt | 2 +- .../ajalt/clikt/parameters/SubcommandTest.kt | 2 +- .../parameters/groups/OptionGroupsTest.kt | 2 +- .../clikt/parameters/types/ChoiceTest.kt | 2 +- .../clikt/parameters/types/DoubleTest.kt | 2 +- .../ajalt/clikt/parameters/types/FloatTest.kt | 2 +- .../ajalt/clikt/parameters/types/IntTest.kt | 2 +- .../ajalt/clikt/parameters/types/LongTest.kt | 2 +- .../ajalt/clikt/parameters/types/RangeTest.kt | 4 +- .../ajalt/clikt/completion/CompletionTest.kt | 2 +- .../clikt/parameters/EnvvarOptionsTest.kt | 2 +- .../clikt/parameters/PromptOptionsTest.kt | 2 +- .../ajalt/clikt/parameters/types/FileTest.kt | 2 +- .../github/ajalt/clikt/parsers/ParserTest.kt | 4 +- .../com/github/ajalt/clikt/mpp/MppImpl.kt | 5 +- docs/options.md | 22 +++-- .../ajalt/clikt/samples/ansicolors/main.kt | 1 + .../ajalt/clikt/samples/helpformat/main.kt | 7 +- .../ajalt/clikt/samples/validation/main.kt | 2 - 33 files changed, 183 insertions(+), 79 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc6f93eb7..343768c9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog ## [Unreleased] +### Added +- `eagerOption {}` function to more easily register eager options. +- Eager options can now be added to option groups in help out by passing a value for `groupName` when creating them. + ### Fixed - `file()` and `path()` conversions will now properly expand leading `~` in paths to the home directory for `mustExist`, `canBeFile`, and `canBeDir` checks. The property value is unchanged, and can still begin with a `~`. ([#131](https://github.com/ajalt/clikt/issues/79)) @@ -11,6 +15,7 @@ ### Added - Clikt is now available as a Kotlin Multiplatform Project, supporting JVM, NodeJS, and native Windows, Linux, and macOS. - `canBeSymlink` parameter to `file()` and `path()` conversions that can be used to disallow symlinks +- `CliktCommand.eagerOption` to simplify creating custom eager options ### Changed - The `CliktCommand.context` property has been deprecated in favor of the new name, `currentContext`, to avoid confusion with the `CliktCommand.context{}` method. diff --git a/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/core/ParameterHolder.kt b/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/core/ParameterHolder.kt index b6e0765aa..c29e8157e 100644 --- a/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/core/ParameterHolder.kt +++ b/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/core/ParameterHolder.kt @@ -17,13 +17,18 @@ interface ParameterHolder { fun registerOption(option: GroupableOption) } +interface StaticallyGroupedOption : Option { + /** The name of the group, or null if this option should not be grouped in the help output. */ + val groupName: String? +} + /** * An option that can be added to a [ParameterGroup] */ -interface GroupableOption : Option { +interface GroupableOption : StaticallyGroupedOption { /** The group that this option belongs to, or null. Set by the group. */ var parameterGroup: ParameterGroup? /** The name of the group, or null if this option should not be grouped in the help output. */ - var groupName: String? + override var groupName: String? } diff --git a/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/core/exceptions.kt b/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/core/exceptions.kt index 5ec1a3959..11a7524e7 100644 --- a/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/core/exceptions.kt +++ b/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/core/exceptions.kt @@ -40,7 +40,7 @@ open class PrintMessage(message: String) : CliktError(message) * @param forceUnixLineEndings if true, all line endings in the message should be `\n`, regardless * of the current operating system. */ -class PrintCompletionMessage(message: String, val forceUnixLineEndings: Boolean): PrintMessage(message) +class PrintCompletionMessage(message: String, val forceUnixLineEndings: Boolean) : PrintMessage(message) /** * An internal exception that signals a usage error. diff --git a/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/mpp/MppH.kt b/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/mpp/MppH.kt index f3a3bc351..ffb6ee54b 100644 --- a/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/mpp/MppH.kt +++ b/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/mpp/MppH.kt @@ -1,7 +1,5 @@ package com.github.ajalt.clikt.mpp -import com.github.ajalt.clikt.core.CliktCommand - internal val ANSI_CODE_RE = Regex("${"\u001B"}\\[[^m]*m") internal expect val String.graphemeLengthMpp: Int @@ -12,7 +10,7 @@ internal expect fun isWindowsMpp(): Boolean internal expect fun exitProcessMpp(status: Int): Nothing -internal expect fun isLetterOrDigit(c: Char) : Boolean +internal expect fun isLetterOrDigit(c: Char): Boolean internal expect fun readFileIfExists(filename: String): String? diff --git a/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/output/CliktHelpFormatter.kt b/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/output/CliktHelpFormatter.kt index bca9bfc7a..f03578c9f 100644 --- a/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/output/CliktHelpFormatter.kt +++ b/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/output/CliktHelpFormatter.kt @@ -208,7 +208,7 @@ open class CliktHelpFormatter( buildString { append(firstIndent).append(col1) // Pad the difference between this column's width and the table's first column width - repeat(firstWidth - col1.graphemeLength + colSpacing) {append(" ")} + repeat(firstWidth - col1.graphemeLength + colSpacing) { append(" ") } } } diff --git a/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/output/TermUi.kt b/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/output/TermUi.kt index 015a90b8a..c0e627062 100644 --- a/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/output/TermUi.kt +++ b/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/output/TermUi.kt @@ -116,7 +116,7 @@ object TermUi { val result = try { convert.invoke(value) } catch (err: UsageError) { - echo(err.helpMessage(), console=console) + echo(err.helpMessage(), console = console) continue } @@ -180,7 +180,7 @@ object TermUi { "n", "no" -> false "" -> default else -> { - echo("Error: invalid input", console=console) + echo("Error: invalid input", console = console) continue@l } } diff --git a/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/parameters/arguments/Argument.kt b/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/parameters/arguments/Argument.kt index 6e0011eaf..f8ba4cb1e 100644 --- a/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/parameters/arguments/Argument.kt +++ b/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/parameters/arguments/Argument.kt @@ -402,7 +402,7 @@ inline fun ProcessedArgument.wrapValue( fail(err.message ?: "") } } - return copy(conv, defaultAllProcessor(), defaultValidator()) + return copy(conv, defaultAllProcessor(), defaultValidator()) } /** diff --git a/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/parameters/groups/ChoiceGroup.kt b/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/parameters/groups/ChoiceGroup.kt index ef21f74c3..8dd38aacb 100644 --- a/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/parameters/groups/ChoiceGroup.kt +++ b/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/parameters/groups/ChoiceGroup.kt @@ -1,6 +1,5 @@ package com.github.ajalt.clikt.parameters.groups -import com.github.ajalt.clikt.completion.CompletionCandidates.Fixed import com.github.ajalt.clikt.core.BadParameterValue import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.core.Context diff --git a/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/parameters/options/EagerOption.kt b/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/parameters/options/EagerOption.kt index 8f823a40a..ce41a7ff4 100644 --- a/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/parameters/options/EagerOption.kt +++ b/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/parameters/options/EagerOption.kt @@ -1,18 +1,17 @@ package com.github.ajalt.clikt.parameters.options -import com.github.ajalt.clikt.core.CliktCommand -import com.github.ajalt.clikt.core.Context -import com.github.ajalt.clikt.core.PrintHelpMessage -import com.github.ajalt.clikt.core.PrintMessage +import com.github.ajalt.clikt.core.* import com.github.ajalt.clikt.parsers.FlagOptionParser import com.github.ajalt.clikt.parsers.OptionParser /** * An [Option] with no values that is [finalize]d before other types of options. * - * @param callback This callback is called when the option is encountered on the command line. If you want to - * print a message and halt execution normally, you should throw a [PrintMessage] exception. The callback it - * passed the current execution context as a parameter. + * @param callback This callback is called when the option is encountered on the command line. If + * you want to print a message and halt execution normally, you should throw a [PrintMessage] + * exception. If you want to exit normally without printing a message, you should throw + * [`Abort(error=false)`][Abort]. The callback is passed the current execution context as a + * parameter. */ class EagerOption( override val names: Set, @@ -20,10 +19,17 @@ class EagerOption( override val help: String, override val hidden: Boolean, override val helpTags: Map, - private val callback: OptionTransformContext.() -> Unit) : Option { + override val groupName: String?, + private val callback: OptionTransformContext.() -> Unit +) : StaticallyGroupedOption { constructor(vararg names: String, nvalues: Int = 0, help: String = "", hidden: Boolean = false, - helpTags: Map = emptyMap(), callback: OptionTransformContext.() -> Unit) - : this(names.toSet(), nvalues, help, hidden, helpTags, callback) + helpTags: Map = emptyMap(), groupName: String?=null, + callback: OptionTransformContext.() -> Unit) + : this(names.toSet(), nvalues, help, hidden, helpTags, groupName, callback) + + init { + require(names.isNotEmpty()) { "options must have at least one name" } + } override val secondaryNames: Set get() = emptySet() override val parser: OptionParser = FlagOptionParser @@ -34,14 +40,61 @@ class EagerOption( } } -internal fun helpOption(names: Set, message: String) = EagerOption(names, 0, message, false, emptyMap(), - callback = { throw PrintHelpMessage(context.command) }) +internal fun helpOption(names: Set, message: String): EagerOption { + return EagerOption(names, 0, message, false, emptyMap(), null) { throw PrintHelpMessage(context.command) } +} + +/** + * Add an eager option to this command that, when invoked, runs [action]. + * + * @param name The names that can be used to invoke this option. They must start with a punctuation character. + * @param help The description of this option, usually a single line. + * @param hidden Hide this option from help outputs. + * @param helpTags Extra information about this option to pass to the help formatter + * @param groupName All options with that share a group name will be grouped together in help output. + * @param action This callback is called when the option is encountered on the command line. If + * you want to print a message and halt execution normally, you should throw a [PrintMessage] + * exception. If you want to exit normally without printing a message, you should throw + * [`Abort(error=false)`][Abort]. The callback is passed the current execution context as a + * parameter. + */ +fun T.eagerOption( + name: String, + vararg additionalNames: String, + help: String = "", + hidden: Boolean = false, + helpTags: Map = emptyMap(), + groupName: String? = null, + action: OptionTransformContext.() -> Unit +): T = eagerOption(listOf(name) + additionalNames, help, hidden, helpTags, groupName, action) + +/** + * Add an eager option to this command that, when invoked, runs [action]. + * + * @param names The names that can be used to invoke this option. They must start with a punctuation character. + * @param help The description of this option, usually a single line. + * @param hidden Hide this option from help outputs. + * @param helpTags Extra information about this option to pass to the help formatter + * @param groupName All options with that share a group name will be grouped together in help output. + * @param action This callback is called when the option is encountered on the command line. If + * you want to print a message and halt execution normally, you should throw a [PrintMessage] + * exception. If you want to exit normally without printing a message, you should throw + * [`Abort(error=false)`][Abort]. The callback is passed the current execution context as a + * parameter. + */ +fun T.eagerOption( + names: Collection, + help: String = "", + hidden: Boolean = false, + helpTags: Map = emptyMap(), + groupName: String? = null, + action: OptionTransformContext.() -> Unit +): T = apply { registerOption(EagerOption(names.toSet(), 0, help, hidden, helpTags, groupName, action)) } /** Add an eager option to this command that, when invoked, prints a version message and exits. */ inline fun T.versionOption( version: String, help: String = "Show the version and exit", names: Set = setOf("--version"), - crossinline message: (String) -> String = { "$commandName version $it" }): T = apply { - registerOption(EagerOption(names, 0, help, false, emptyMap()) { throw PrintMessage(message(version)) }) -} + crossinline message: (String) -> String = { "$commandName version $it" } +): T = eagerOption(names, help) { throw PrintMessage(message(version)) } diff --git a/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/parameters/options/Option.kt b/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/parameters/options/Option.kt index acc620ca6..9b0c45e07 100644 --- a/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/parameters/options/Option.kt +++ b/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/parameters/options/Option.kt @@ -1,10 +1,7 @@ package com.github.ajalt.clikt.parameters.options import com.github.ajalt.clikt.completion.CompletionCandidates -import com.github.ajalt.clikt.core.CliktError -import com.github.ajalt.clikt.core.Context -import com.github.ajalt.clikt.core.GroupableOption -import com.github.ajalt.clikt.core.ParameterHolder +import com.github.ajalt.clikt.core.* import com.github.ajalt.clikt.mpp.isLetterOrDigit import com.github.ajalt.clikt.output.HelpFormatter import com.github.ajalt.clikt.parsers.OptionParser @@ -49,7 +46,9 @@ interface Option { get() = when { hidden -> null else -> HelpFormatter.ParameterHelp.Option(names, secondaryNames, metavar, help, nvalues, helpTags, - groupName = if (this is GroupableOption) groupName ?: parameterGroup?.groupName else null) + groupName = (this as? StaticallyGroupedOption)?.groupName + ?: (this as? GroupableOption)?.parameterGroup?.groupName + ) } /** diff --git a/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/core/CliktCommandTest.kt b/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/core/CliktCommandTest.kt index 41ee92a0d..2e873e4c4 100644 --- a/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/core/CliktCommandTest.kt +++ b/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/core/CliktCommandTest.kt @@ -10,13 +10,13 @@ import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.options.required import com.github.ajalt.clikt.parameters.types.int import com.github.ajalt.clikt.testing.TestCommand -import io.kotest.matchers.shouldBe -import io.kotest.matchers.shouldNotBe import io.kotest.assertions.throwables.shouldThrow import io.kotest.data.forall +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import io.kotest.tables.row import kotlin.js.JsName import kotlin.test.Test -import io.kotest.tables.* @Suppress("unused") class CliktCommandTest { diff --git a/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/core/ContextTest.kt b/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/core/ContextTest.kt index 71efcd4f7..9c6f1375a 100644 --- a/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/core/ContextTest.kt +++ b/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/core/ContextTest.kt @@ -1,10 +1,10 @@ package com.github.ajalt.clikt.core import com.github.ajalt.clikt.testing.TestCommand -import io.kotest.matchers.should -import io.kotest.matchers.shouldBe import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.beInstanceOf +import io.kotest.matchers.should +import io.kotest.matchers.shouldBe import io.kotest.matchers.types.shouldBeSameInstanceAs import kotlin.js.JsName import kotlin.test.Test diff --git a/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/output/CliktHelpFormatterTest.kt b/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/output/CliktHelpFormatterTest.kt index 4215b4440..d46b529cf 100644 --- a/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/output/CliktHelpFormatterTest.kt +++ b/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/output/CliktHelpFormatterTest.kt @@ -6,11 +6,7 @@ import com.github.ajalt.clikt.core.subcommands import com.github.ajalt.clikt.output.HelpFormatter.ParameterHelp import com.github.ajalt.clikt.parameters.arguments.argument import com.github.ajalt.clikt.parameters.arguments.multiple -import com.github.ajalt.clikt.parameters.groups.OptionGroup -import com.github.ajalt.clikt.parameters.groups.cooccurring -import com.github.ajalt.clikt.parameters.groups.groupChoice -import com.github.ajalt.clikt.parameters.groups.groupSwitch -import com.github.ajalt.clikt.parameters.groups.mutuallyExclusiveOptions +import com.github.ajalt.clikt.parameters.groups.* import com.github.ajalt.clikt.parameters.options.* import com.github.ajalt.clikt.parameters.types.int import com.github.ajalt.clikt.testing.TestCommand @@ -429,11 +425,13 @@ class CliktHelpFormatterTest { class G1 : OptionGroup("G1") { val opt1 by option() } + class G2 : OptionGroup("G2") { val opt2 by option() } + class C : TestCommand() { - val opt by option(help="select group").groupChoice("g1" to G1(), "g2" to G2()) + val opt by option(help = "select group").groupChoice("g1" to G1(), "g2" to G2()) } val c = C() @@ -460,11 +458,13 @@ class CliktHelpFormatterTest { class G1 : OptionGroup("G1") { val opt1 by option() } + class G2 : OptionGroup("G2") { val opt2 by option() } + class C : TestCommand() { - val opt by option(help="select group").groupSwitch("--g1" to G1(), "--g2" to G2()) + val opt by option(help = "select group").groupSwitch("--g1" to G1(), "--g2" to G2()) } val c = C() @@ -533,6 +533,9 @@ class CliktHelpFormatterTest { requiredOptionMarker = "*" ) } + + eagerOption("--eager", "-e", help = "this is an eager option with a group", groupName = "My Group") {} + eagerOption("--eager2", "-E", help = "this is an eager option") {} } } @@ -562,6 +565,7 @@ class CliktHelpFormatterTest { | |* --group-foo TEXT foo for group (required) | -g, --group-bar TEXT bar for group + | -e, --eager this is an eager option with a group | |Another group: |* --group-baz TEXT this group doesn't have help (required) @@ -582,6 +586,7 @@ class CliktHelpFormatterTest { |* --foo INT foo option help (required) | -b, --bar META bar option help (default: optdef) | --baz / --no-baz baz option help + | -E, --eager2 this is an eager option | --version Show the version and exit | -h, --help Show this message and exit | diff --git a/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/EagerOptionsTest.kt b/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/EagerOptionsTest.kt index 0c45890dd..f88352491 100644 --- a/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/EagerOptionsTest.kt +++ b/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/EagerOptionsTest.kt @@ -1,15 +1,56 @@ package com.github.ajalt.clikt.parameters +import com.github.ajalt.clikt.core.Abort import com.github.ajalt.clikt.core.PrintHelpMessage import com.github.ajalt.clikt.core.PrintMessage +import com.github.ajalt.clikt.parameters.options.eagerOption import com.github.ajalt.clikt.parameters.options.versionOption import com.github.ajalt.clikt.testing.TestCommand -import io.kotest.matchers.shouldBe import io.kotest.assertions.throwables.shouldThrow +import io.kotest.data.forall +import io.kotest.matchers.shouldBe +import io.kotest.tables.row import kotlin.js.JsName import kotlin.test.Test +@Suppress("BooleanLiteralArgument") class EagerOptionsTest { + @Test + @JsName("custom_eager_option") + fun `custom eager option`() = forall( + row("", false, false), + row("--option", true, false), + row("-p", false, true), + row("-op", true, true) + ) { argv, eO, eP -> + var calledO = false + var calledP = false + + class C : TestCommand() { + init { + eagerOption("-o", "--option") { calledO = true } + eagerOption(listOf("-p")) { calledP = true } + } + } + + with(C()) { + parse(argv) + calledO shouldBe eO + calledP shouldBe eP + } + } + + @Test + @JsName("custom_eager_option_throwing_abort") + fun `custom eager option throwing abort`() { + class C : TestCommand(called = false) + + shouldThrow { + C().eagerOption("--foo") { throw Abort(error = false) } + .parse("--foo") + }.error shouldBe false + } + @Test @JsName("version_default") fun `version default`() { diff --git a/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/OptionTest.kt b/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/OptionTest.kt index 939568e20..f285067ed 100644 --- a/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/OptionTest.kt +++ b/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/OptionTest.kt @@ -6,9 +6,9 @@ import com.github.ajalt.clikt.parameters.types.int import com.github.ajalt.clikt.testing.TestCommand import com.github.ajalt.clikt.testing.skipDueToKT33294 import io.kotest.assertions.fail +import io.kotest.assertions.throwables.shouldThrow import io.kotest.data.forall import io.kotest.matchers.shouldBe -import io.kotest.assertions.throwables.shouldThrow import io.kotest.tables.row import kotlin.js.JsName import kotlin.test.Test diff --git a/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/SubcommandTest.kt b/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/SubcommandTest.kt index 94ccf467f..c1b764c04 100644 --- a/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/SubcommandTest.kt +++ b/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/SubcommandTest.kt @@ -8,9 +8,9 @@ import com.github.ajalt.clikt.parameters.arguments.multiple import com.github.ajalt.clikt.parameters.arguments.pair import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.testing.TestCommand +import io.kotest.assertions.throwables.shouldThrow import io.kotest.data.forall import io.kotest.matchers.shouldBe -import io.kotest.assertions.throwables.shouldThrow import io.kotest.tables.row import kotlin.js.JsName import kotlin.test.Test diff --git a/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/groups/OptionGroupsTest.kt b/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/groups/OptionGroupsTest.kt index 163b22609..d8ef2fbf1 100644 --- a/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/groups/OptionGroupsTest.kt +++ b/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/groups/OptionGroupsTest.kt @@ -14,9 +14,9 @@ import com.github.ajalt.clikt.parameters.types.int import com.github.ajalt.clikt.testing.TestCommand import com.github.ajalt.clikt.testing.skipDueToKT33294 import io.kotest.assertions.fail +import io.kotest.assertions.throwables.shouldThrow import io.kotest.data.forall import io.kotest.matchers.shouldBe -import io.kotest.assertions.throwables.shouldThrow import io.kotest.tables.row import kotlin.js.JsName import kotlin.test.Test diff --git a/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/types/ChoiceTest.kt b/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/types/ChoiceTest.kt index bbdc9d675..fead78a2f 100644 --- a/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/types/ChoiceTest.kt +++ b/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/types/ChoiceTest.kt @@ -271,7 +271,7 @@ class ChoiceTypeTest { row("aZ Bz", listOf(TestEnum.A, TestEnum.B)) ) { argv, ex -> class C : TestCommand() { - val x by argument().enum { it.name + "z"}.multiple() + val x by argument().enum { it.name + "z" }.multiple() override fun run_() { x shouldBe ex } diff --git a/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/types/DoubleTest.kt b/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/types/DoubleTest.kt index c2b8412f4..70d98c338 100644 --- a/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/types/DoubleTest.kt +++ b/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/types/DoubleTest.kt @@ -7,9 +7,9 @@ import com.github.ajalt.clikt.parameters.arguments.optional import com.github.ajalt.clikt.parameters.options.default import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.testing.TestCommand +import io.kotest.assertions.throwables.shouldThrow import io.kotest.data.forall import io.kotest.matchers.shouldBe -import io.kotest.assertions.throwables.shouldThrow import io.kotest.tables.row import kotlin.js.JsName import kotlin.test.Test diff --git a/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/types/FloatTest.kt b/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/types/FloatTest.kt index aa7d741b5..9ec02584c 100644 --- a/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/types/FloatTest.kt +++ b/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/types/FloatTest.kt @@ -7,9 +7,9 @@ import com.github.ajalt.clikt.parameters.arguments.optional import com.github.ajalt.clikt.parameters.options.default import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.testing.TestCommand +import io.kotest.assertions.throwables.shouldThrow import io.kotest.data.forall import io.kotest.matchers.shouldBe -import io.kotest.assertions.throwables.shouldThrow import io.kotest.tables.row import kotlin.js.JsName import kotlin.test.Test diff --git a/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/types/IntTest.kt b/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/types/IntTest.kt index a28eff832..bf84f5dd2 100644 --- a/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/types/IntTest.kt +++ b/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/types/IntTest.kt @@ -7,9 +7,9 @@ import com.github.ajalt.clikt.parameters.arguments.optional import com.github.ajalt.clikt.parameters.options.default import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.testing.TestCommand +import io.kotest.assertions.throwables.shouldThrow import io.kotest.data.forall import io.kotest.matchers.shouldBe -import io.kotest.assertions.throwables.shouldThrow import io.kotest.tables.row import kotlin.js.JsName import kotlin.test.Test diff --git a/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/types/LongTest.kt b/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/types/LongTest.kt index d2a860c32..8a8eebed9 100644 --- a/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/types/LongTest.kt +++ b/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/types/LongTest.kt @@ -7,9 +7,9 @@ import com.github.ajalt.clikt.parameters.arguments.optional import com.github.ajalt.clikt.parameters.options.default import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.testing.TestCommand +import io.kotest.assertions.throwables.shouldThrow import io.kotest.data.forall import io.kotest.matchers.shouldBe -import io.kotest.assertions.throwables.shouldThrow import io.kotest.tables.row import kotlin.js.JsName import kotlin.test.Test diff --git a/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/types/RangeTest.kt b/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/types/RangeTest.kt index 22a6c6c7e..53112adf9 100644 --- a/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/types/RangeTest.kt +++ b/clikt/src/commonTest/kotlin/com/github/ajalt/clikt/parameters/types/RangeTest.kt @@ -8,11 +8,11 @@ import com.github.ajalt.clikt.parameters.options.multiple import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.options.pair import com.github.ajalt.clikt.testing.TestCommand +import io.kotest.assertions.throwables.shouldThrow import io.kotest.data.forall +import io.kotest.matchers.collections.beEmpty import io.kotest.matchers.should import io.kotest.matchers.shouldBe -import io.kotest.assertions.throwables.shouldThrow -import io.kotest.matchers.collections.beEmpty import io.kotest.tables.row import kotlin.js.JsName import kotlin.test.Test diff --git a/clikt/src/jvmTest/kotlin/com/github/ajalt/clikt/completion/CompletionTest.kt b/clikt/src/jvmTest/kotlin/com/github/ajalt/clikt/completion/CompletionTest.kt index aeb3c4fb9..36ffd4eed 100644 --- a/clikt/src/jvmTest/kotlin/com/github/ajalt/clikt/completion/CompletionTest.kt +++ b/clikt/src/jvmTest/kotlin/com/github/ajalt/clikt/completion/CompletionTest.kt @@ -9,8 +9,8 @@ import com.github.ajalt.clikt.testing.TestCommand import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe import org.junit.Rule -import kotlin.test.Test import org.junit.contrib.java.lang.system.EnvironmentVariables +import kotlin.test.Test @UseExperimental(ExperimentalCompletionCandidates::class) @Suppress("unused") diff --git a/clikt/src/jvmTest/kotlin/com/github/ajalt/clikt/parameters/EnvvarOptionsTest.kt b/clikt/src/jvmTest/kotlin/com/github/ajalt/clikt/parameters/EnvvarOptionsTest.kt index daa08aaeb..77106fe0c 100644 --- a/clikt/src/jvmTest/kotlin/com/github/ajalt/clikt/parameters/EnvvarOptionsTest.kt +++ b/clikt/src/jvmTest/kotlin/com/github/ajalt/clikt/parameters/EnvvarOptionsTest.kt @@ -10,10 +10,10 @@ import io.kotest.data.forall import io.kotest.matchers.shouldBe import io.kotest.tables.row import org.junit.Rule -import kotlin.test.Test import org.junit.contrib.java.lang.system.EnvironmentVariables import org.junit.contrib.java.lang.system.RestoreSystemProperties import java.io.File +import kotlin.test.Test class EnvvarOptionsTest { diff --git a/clikt/src/jvmTest/kotlin/com/github/ajalt/clikt/parameters/PromptOptionsTest.kt b/clikt/src/jvmTest/kotlin/com/github/ajalt/clikt/parameters/PromptOptionsTest.kt index 2bf19efec..01ad16858 100644 --- a/clikt/src/jvmTest/kotlin/com/github/ajalt/clikt/parameters/PromptOptionsTest.kt +++ b/clikt/src/jvmTest/kotlin/com/github/ajalt/clikt/parameters/PromptOptionsTest.kt @@ -13,9 +13,9 @@ import io.kotest.matchers.collections.containExactly import io.kotest.matchers.should import io.kotest.matchers.shouldBe import org.junit.Rule -import kotlin.test.Test import org.junit.contrib.java.lang.system.SystemOutRule import org.junit.contrib.java.lang.system.TextFromStandardInputStream +import kotlin.test.Test class PromptOptionsTest { @Rule diff --git a/clikt/src/jvmTest/kotlin/com/github/ajalt/clikt/parameters/types/FileTest.kt b/clikt/src/jvmTest/kotlin/com/github/ajalt/clikt/parameters/types/FileTest.kt index 71dc24740..a705aad95 100644 --- a/clikt/src/jvmTest/kotlin/com/github/ajalt/clikt/parameters/types/FileTest.kt +++ b/clikt/src/jvmTest/kotlin/com/github/ajalt/clikt/parameters/types/FileTest.kt @@ -4,8 +4,8 @@ import com.github.ajalt.clikt.parameters.arguments.argument import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.testing.TestCommand import io.kotest.matchers.types.shouldBeInstanceOf -import kotlin.test.Test import java.io.File +import kotlin.test.Test class FileTypeTest { @Test diff --git a/clikt/src/jvmTest/kotlin/com/github/ajalt/clikt/parsers/ParserTest.kt b/clikt/src/jvmTest/kotlin/com/github/ajalt/clikt/parsers/ParserTest.kt index 8870d5833..2fb262c1e 100644 --- a/clikt/src/jvmTest/kotlin/com/github/ajalt/clikt/parsers/ParserTest.kt +++ b/clikt/src/jvmTest/kotlin/com/github/ajalt/clikt/parsers/ParserTest.kt @@ -6,11 +6,11 @@ import com.github.ajalt.clikt.parameters.arguments.argument import com.github.ajalt.clikt.parameters.arguments.multiple import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.testing.TestCommand -import io.kotest.matchers.shouldBe import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe import org.junit.Rule -import kotlin.test.Test import org.junit.rules.TemporaryFolder +import kotlin.test.Test class ParserTest { diff --git a/clikt/src/nativeMain/kotlin/com/github/ajalt/clikt/mpp/MppImpl.kt b/clikt/src/nativeMain/kotlin/com/github/ajalt/clikt/mpp/MppImpl.kt index e18bc64e0..04663c437 100644 --- a/clikt/src/nativeMain/kotlin/com/github/ajalt/clikt/mpp/MppImpl.kt +++ b/clikt/src/nativeMain/kotlin/com/github/ajalt/clikt/mpp/MppImpl.kt @@ -1,4 +1,5 @@ package com.github.ajalt.clikt.mpp + import kotlinx.cinterop.ByteVar import kotlinx.cinterop.allocArray import kotlinx.cinterop.memScoped @@ -13,7 +14,7 @@ private val LETTER_OR_DIGIT_RE = Regex("""[a-zA-Z0-9]""") internal actual val String.graphemeLengthMpp: Int get() = replace(ANSI_CODE_RE, "").length -internal actual fun isLetterOrDigit(c: Char) : Boolean = LETTER_OR_DIGIT_RE.matches(c.toString()) +internal actual fun isLetterOrDigit(c: Char): Boolean = LETTER_OR_DIGIT_RE.matches(c.toString()) internal actual fun readEnvvar(key: String): String? = getenv(key)?.toKString() @@ -29,7 +30,7 @@ internal actual fun readFileIfExists(filename: String): String? { val bufferLength = 64 * 1024 val buffer = allocArray(bufferLength) - while(true){ + while (true) { val chunk = fgets(buffer, bufferLength, file)?.toKString() if (chunk == null || chunk.isEmpty()) break chunks.append(chunk) diff --git a/docs/options.md b/docs/options.md index e8ccd1492..9130e2c1e 100644 --- a/docs/options.md +++ b/docs/options.md @@ -640,24 +640,21 @@ $ ./cli --version cli version 1.0 ``` -If you want to define your own option with a similar behavior, you can do so by creating an instance -of [`EagerOption`][EagerOption] and -passing it to -[`CliktCommand.registerOption`][CliktCommand.registerOption]. -`EagerOption`s have a `callback` that is called when the option is encountered on the command line. -To print a message and halt execution normally from the callback, you can throw a -[`PrintMessage`][PrintMessage] exception, and -[`CliktCommand.main`][CliktCommand.main] will take care -of printing the message. +If you want to define your own option with a similar behavior, you can do so by calling +[`eagerOption`][eagerOption]. The takes an `action` that is called when the option is encountered on +the command line. To print a message and halt execution normally from the callback, you can throw a +[`PrintMessage`][PrintMessage] exception, and [`CliktCommand.main`][CliktCommand.main] will take +care of printing the message. If you want to exit normally without printing a message, you can throw +[`Abort(error=false)`][Abort] instead. You can define your own version option like this: ```kotlin class Cli : NoOpCliktCommand() { init { - registerOption(EagerOption("--version") { + eagerOption("--version") { throw PrintMessage("$commandName version 1.0") - }) + } } } ``` @@ -904,9 +901,10 @@ val opt: Pair by option("-o", "--opt") [co-occurring-option-groups]: #co-occurring-option-groups [prompt]: api/clikt/com.github.ajalt.clikt.parameters.options/prompt.md [versionOption]: api/clikt/com.github.ajalt.clikt.parameters.options/version-option.md -[EagerOption]: api/clikt/com.github.ajalt.clikt.parameters.options/-eager-option/index.md +[eagerOption]: api/clikt/com.github.ajalt.clikt.parameters.options/eager-option.md [CliktCommand.registerOption]: api/clikt/com.github.ajalt.clikt.core/-clikt-command/register-option.md [PrintMessage]: api/clikt/com.github.ajalt.clikt.core/-print-message/index.md +[Abort]: api/clikt/com.github.ajalt.clikt.core/-abort/index.md [CliktCommand.main]: api/clikt/com.github.ajalt.clikt.core/-clikt-command/main.md [deprecated]: api/clikt/com.github.ajalt.clikt.parameters.options/deprecated.md [context]: api/clikt/com.github.ajalt.clikt.core/context.md diff --git a/samples/ansicolors/src/main/kotlin/com/github/ajalt/clikt/samples/ansicolors/main.kt b/samples/ansicolors/src/main/kotlin/com/github/ajalt/clikt/samples/ansicolors/main.kt index ee80ca196..8a52c57f2 100644 --- a/samples/ansicolors/src/main/kotlin/com/github/ajalt/clikt/samples/ansicolors/main.kt +++ b/samples/ansicolors/src/main/kotlin/com/github/ajalt/clikt/samples/ansicolors/main.kt @@ -7,6 +7,7 @@ import com.github.ajalt.clikt.output.CliktHelpFormatter import com.github.ajalt.clikt.output.HelpFormatter import com.github.ajalt.clikt.parameters.arguments.argument import com.github.ajalt.clikt.parameters.arguments.multiple +import com.github.ajalt.clikt.parameters.groups.OptionGroup import com.github.ajalt.clikt.parameters.options.flag import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.mordant.TermColors diff --git a/samples/helpformat/src/main/kotlin/com/github/ajalt/clikt/samples/helpformat/main.kt b/samples/helpformat/src/main/kotlin/com/github/ajalt/clikt/samples/helpformat/main.kt index 1093adddf..e7d709988 100644 --- a/samples/helpformat/src/main/kotlin/com/github/ajalt/clikt/samples/helpformat/main.kt +++ b/samples/helpformat/src/main/kotlin/com/github/ajalt/clikt/samples/helpformat/main.kt @@ -2,21 +2,21 @@ package com.github.ajalt.clikt.samples.helpformat import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.core.context -import com.github.ajalt.clikt.output.HelpFormatter import com.github.ajalt.clikt.output.CliktHelpFormatter +import com.github.ajalt.clikt.output.HelpFormatter import com.github.ajalt.clikt.parameters.arguments.argument import com.github.ajalt.clikt.parameters.arguments.multiple import com.github.ajalt.clikt.parameters.options.flag import com.github.ajalt.clikt.parameters.options.option -class ArgparseHelpFormatter: CliktHelpFormatter( +class ArgparseHelpFormatter : CliktHelpFormatter( usageTitle = "usage:", optionsTitle = "optional arguments:", argumentsTitle = "positional arguments:") { override fun formatHelp(prolog: String, epilog: String, parameters: List, - programName: String)= buildString { + programName: String) = buildString { // argparse prints arguments before options addUsage(parameters, programName) addProlog(prolog) @@ -31,6 +31,7 @@ class Echo : CliktCommand(help = "Echo the STRING(s) to standard output") { init { context { helpFormatter = ArgparseHelpFormatter() } } + val suppressNewline by option("-n", help = "do not output the trailing newline").flag() val strings by argument(help = "the strings to echo").multiple() diff --git a/samples/validation/src/main/kotlin/com/github/ajalt/clikt/samples/validation/main.kt b/samples/validation/src/main/kotlin/com/github/ajalt/clikt/samples/validation/main.kt index d6abcfd47..ac84ecd45 100644 --- a/samples/validation/src/main/kotlin/com/github/ajalt/clikt/samples/validation/main.kt +++ b/samples/validation/src/main/kotlin/com/github/ajalt/clikt/samples/validation/main.kt @@ -3,9 +3,7 @@ package com.github.ajalt.clikt.samples.validation import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.parameters.arguments.argument import com.github.ajalt.clikt.parameters.arguments.convert -import com.github.ajalt.clikt.parameters.options.default import com.github.ajalt.clikt.parameters.options.option -import com.github.ajalt.clikt.parameters.options.required import com.github.ajalt.clikt.parameters.options.transformAll import com.github.ajalt.clikt.parameters.options.transformValues import com.github.ajalt.clikt.parameters.options.validate