Skip to content

Commit

Permalink
Add option().groupSwitch() (#95)
Browse files Browse the repository at this point in the history
  • Loading branch information
ajalt authored Nov 1, 2019
1 parent e291285 commit 5f3bc57
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 12 deletions.
7 changes: 4 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<GroupT : OptionGroup, OutT>(
internal val option: RawOption,
internal val option: OptionDelegate<String?>,
internal val groups: Map<String, GroupT>,
internal val transform: (GroupT?) -> OutT
) : ParameterGroupDelegate<OutT> {
Expand Down Expand Up @@ -103,3 +105,28 @@ fun <T : OptionGroup> ChoiceGroup<T, T?>.required(): ChoiceGroup<T, T> {
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 <T : OptionGroup> RawOption.groupSwitch(choices: Map<String, T>): ChoiceGroup<T, T?> {
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 <T : OptionGroup> RawOption.groupSwitch(vararg choices: Pair<String, T>): ChoiceGroup<T, T?> {
return groupSwitch(choices.toMap())
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,15 @@ fun RawOption.counted(): FlagOption<Int> {
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 <T : Any> RawOption.switch(choices: Map<String, T>): FlagOption<T?> {
require(choices.isNotEmpty()) { "Must specify at least one choice" }
return FlagOption(choices.keys, emptySet(), help, hidden, helpTags, null,
Expand All @@ -129,7 +137,15 @@ fun <T : Any> RawOption.switch(choices: Map<String, T>): FlagOption<T?> {
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 <T : Any> RawOption.switch(vararg choices: Pair<String, T>): FlagOption<T?> = switch(mapOf(*choices))

/** Set a default value for a option. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
12 changes: 7 additions & 5 deletions docs/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -896,7 +897,8 @@ val opt: Pair<Int, Int> 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
Expand Down
2 changes: 1 addition & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 5f3bc57

Please sign in to comment.