diff --git a/CHANGELOG.md b/CHANGELOG.md index 74bfcbca7..887461388 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,13 @@ # Changelog ## [Unreleased] -### Changed -- Shell completion code is now printed by throwing a `PrintCompletionMessage` (a subclass of `PrintMessage`) rather than calling `echo` directly. - ### Added +- `option().groupSwitch()`, which works like `groupChoice()`, but uses a `switch()` option rather than a `choice()` option. - `UsageError` now has a `statusCode` parameter (which defaults to 1). If you're using `ClicktCommand.main`, the value of `statusCode` will be passed to `exitProcess`. +### Changed +- Shell completion code is now printed by throwing a `PrintCompletionMessage` (a subclass of `PrintMessage`) rather than calling `echo` directly. + ## [2.2.0] - 2019-09-25 ### Added - Added [`enum()` conversion](https://ajalt.github.io/clikt/api/clikt/com.github.ajalt.clikt.parameters.types/enum/) for options and arguments. ([#84](https://github.com/ajalt/clikt/issues/84)) diff --git a/clikt/src/main/kotlin/com/github/ajalt/clikt/parameters/groups/ChoiceGroup.kt b/clikt/src/main/kotlin/com/github/ajalt/clikt/parameters/groups/ChoiceGroup.kt index d67d9b6e6..f67ccd369 100644 --- a/clikt/src/main/kotlin/com/github/ajalt/clikt/parameters/groups/ChoiceGroup.kt +++ b/clikt/src/main/kotlin/com/github/ajalt/clikt/parameters/groups/ChoiceGroup.kt @@ -7,13 +7,15 @@ import com.github.ajalt.clikt.core.Context import com.github.ajalt.clikt.core.MissingParameter import com.github.ajalt.clikt.parameters.internal.NullableLateinit import com.github.ajalt.clikt.parameters.options.Option +import com.github.ajalt.clikt.parameters.options.OptionDelegate import com.github.ajalt.clikt.parameters.options.RawOption +import com.github.ajalt.clikt.parameters.options.switch import com.github.ajalt.clikt.parsers.OptionParser import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty class ChoiceGroup( - internal val option: RawOption, + internal val option: OptionDelegate, internal val groups: Map, internal val transform: (GroupT?) -> OutT ) : ParameterGroupDelegate { @@ -103,3 +105,28 @@ fun ChoiceGroup.required(): ChoiceGroup { return ChoiceGroup(option, groups) { it ?: throw MissingParameter(option) } } +/** + * Convert the option into a set of flags that each map to an option group. + * + * ### Example: + * + * ```kotlin + * option().switch(mapOf("--foo" to FooOptionGroup(), "--bar" to BarOptionGroup())) + * ``` + */ +fun RawOption.groupSwitch(choices: Map): ChoiceGroup { + return ChoiceGroup(switch(choices.mapValues { it.key }), choices) { it } +} + +/** + * Convert the option into a set of flags that each map to an option group. + * + * ### Example: + * + * ```kotlin + * option().switch("--foo" to FooOptionGroup(), "--bar" to BarOptionGroup()) + * ``` + */ +fun RawOption.groupSwitch(vararg choices: Pair): ChoiceGroup { + return groupSwitch(choices.toMap()) +} diff --git a/clikt/src/main/kotlin/com/github/ajalt/clikt/parameters/options/FlagOption.kt b/clikt/src/main/kotlin/com/github/ajalt/clikt/parameters/options/FlagOption.kt index 357683210..2404a519c 100644 --- a/clikt/src/main/kotlin/com/github/ajalt/clikt/parameters/options/FlagOption.kt +++ b/clikt/src/main/kotlin/com/github/ajalt/clikt/parameters/options/FlagOption.kt @@ -118,7 +118,15 @@ fun RawOption.counted(): FlagOption { validator = {}) } -/** Turn an option into a set of flags that each map to a value. */ +/** + * Turn an option into a set of flags that each map to a value. + * + * ### Example: + * + * ```kotlin + * option().switch(mapOf("--foo" to Foo(), "--bar" to Bar())) + * ``` + */ fun RawOption.switch(choices: Map): FlagOption { require(choices.isNotEmpty()) { "Must specify at least one choice" } return FlagOption(choices.keys, emptySet(), help, hidden, helpTags, null, @@ -129,7 +137,15 @@ fun RawOption.switch(choices: Map): FlagOption { validator = {}) } -/** Turn an option into a set of flags that each map to a value. */ +/** + * Turn an option into a set of flags that each map to a value. + * + * ### Example: + * + * ```kotlin + * option().switch("--foo" to Foo(), "--bar" to Bar()) + * ``` + */ fun RawOption.switch(vararg choices: Pair): FlagOption = switch(mapOf(*choices)) /** Set a default value for a option. */ diff --git a/clikt/src/test/kotlin/com/github/ajalt/clikt/parameters/groups/OptionGroupsTest.kt b/clikt/src/test/kotlin/com/github/ajalt/clikt/parameters/groups/OptionGroupsTest.kt index 065fc3244..70e7d6dba 100644 --- a/clikt/src/test/kotlin/com/github/ajalt/clikt/parameters/groups/OptionGroupsTest.kt +++ b/clikt/src/test/kotlin/com/github/ajalt/clikt/parameters/groups/OptionGroupsTest.kt @@ -287,6 +287,49 @@ class OptionGroupsTest { .message shouldBe "Invalid value for \"--g\": invalid choice: 3. (choose from 1, 2)" } + @Test + fun `switch group`() { + class Group1 : OptionGroup() { + val g11 by option().int().required() + val g12 by option().int() + } + + class Group2 : OptionGroup() { + val g21 by option().int().required() + val g22 by option().int() + } + + class C : TestCommand() { + val g by option().groupSwitch("--a" to Group1(), "--b" to Group2()) + } + forall( + row("", 0, null, null), + row("--g11=1 --g21=1", 0, null, null), + row("--a --g11=2", 1, 2, null), + row("--a --g11=2 --g12=3", 1, 2, 3), + row("--a --g11=2 --g12=3", 1, 2, 3), + row("--b --g21=2 --g22=3", 2, 2, 3), + row("--b --g11=2 --g12=3 --g21=2 --g22=3", 2, 2, 3) + ) { argv, eg, eg1, eg2 -> + with(C()) { + parse(argv) + when (eg) { + 0 -> { + g shouldBe null + } + 1 -> { + (g as Group1).g11 shouldBe eg1 + (g as Group1).g12 shouldBe eg2 + } + 2 -> { + (g as Group2).g21 shouldBe eg1 + (g as Group2).g22 shouldBe eg2 + } + } + } + } + } + @Test fun `plain option group validation`() = forall( row("", null, true), diff --git a/docs/options.md b/docs/options.md index 710fc288b..0e4b635cb 100644 --- a/docs/options.md +++ b/docs/options.md @@ -483,15 +483,16 @@ Error: Missing option "--name". Like [other option groups][grouping-options-in-help], you can specify a `name` and `help` text for the group if you want to set the group apart in the help output. -## Choice Options With Groups +## Choice and Switch Options With Groups If you have different groups of options that only make sense when another option has a certain value, -you can use [`groupChoice`][groupChoice]. +you can use [`groupChoice`][groupChoice] and [`groupSwitch`][groupSwitch]. -These options are similar to [`choice` options][choice-options], but instead of mapping a value to +`groupChoice` options are similar to [`choice` options][choice-options], but instead of mapping a value to a single new type, they map a value to a [co-occurring `OptionGroup`][co-occurring-option-groups]. Options for groups other than the selected one are ignored, and only the selected group's `required` -constraints are enforced. +constraints are enforced. In the same way, `groupSwitch` options are similar to [`switch` +options][choice-options]. ```kotlin tab="Example" sealed class LoadConfig(name: String): OptionGroup(name) @@ -896,7 +897,8 @@ val opt: Pair by option("-o", "--opt") [cooccurring]: api/clikt/com.github.ajalt.clikt.parameters.groups/cooccurring.md [required]: api/clikt/com.github.ajalt.clikt.parameters.options/required.md [groupChoice]: api/clikt/com.github.ajalt.clikt.parameters.groups/group-choice.md -[choice-options]: #choice-options +[groupChoice]: api/clikt/com.github.ajalt.clikt.parameters.groups/group-switch.md +[switch-options]: #feature-switch-flags [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 diff --git a/mkdocs.yml b/mkdocs.yml index b8d30af5e..2a5d0fa03 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -64,7 +64,7 @@ nav: - 'Choice Options': options/#choice-options - 'Mutually Exclusive Option Groups': options/#mutually-exclusive-option-groups - 'Co-Occurring Option Groups': options/#co-occurring-option-groups - - 'Choice Options With Groups': options/#choice-options-with-groups + - 'Choice and Switch Options With Groups': options/#choice-and-switch-options-with-groups - 'Prompting For Input': options/#prompting-for-input - 'Password Prompts': options/#password-prompts - 'Eager Options': options/#eager-options