Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make mutuallyExclusiveOptions covariant #267

Merged
merged 1 commit into from
Jan 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Changelog

## Unreleased
- Make parameters of `mutuallyExclusiveOptions` covariant to allow validation without explicit type annotations. ([#265](https://github.com/ajalt/clikt/issues/265))

## 3.1.0
_2020-12-12_
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ class MutuallyExclusiveOptionTransformContext(val context: Context)
typealias MutuallyExclusiveOptionsTransform<OptT, OutT> = MutuallyExclusiveOptionTransformContext.(List<OptT>) -> OutT

class MutuallyExclusiveOptions<OptT : Any, OutT> internal constructor(
internal val options: List<OptionDelegate<OptT?>>,
override val groupName: String?,
override val groupHelp: String?,
internal val transformAll: MutuallyExclusiveOptionsTransform<OptT, OutT>
internal val options: List<OptionDelegate<out OptT?>>,
override val groupName: String?,
override val groupHelp: String?,
internal val transformAll: MutuallyExclusiveOptionsTransform<OptT, OutT>,
) : ParameterGroupDelegate<OutT> {
init {
require(options.size > 1) { "must provide at least two options to a mutually exclusive group" }
Expand Down Expand Up @@ -104,11 +104,11 @@ fun <OptT: Any, OutT> MutuallyExclusiveOptions<OptT, OutT>.help(name: String, he
*/
@Suppress("unused")
fun <T : Any> ParameterHolder.mutuallyExclusiveOptions(
option1: OptionDelegate<T?>,
option2: OptionDelegate<T?>,
vararg options: OptionDelegate<T?>,
name: String? = null,
help: String? = null
option1: OptionDelegate<out T?>,
option2: OptionDelegate<out T?>,
vararg options: OptionDelegate<out T?>,
name: String? = null,
help: String? = null,
): MutuallyExclusiveOptions<T, T?> {
return MutuallyExclusiveOptions(listOf(option1, option2) + options, name, help) { it.lastOrNull() }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -773,7 +773,7 @@ class OptionTest {
row("--x=1", listOf(1))
) { argv, expected ->
class C : TestCommand() {
val x by option().int().convert { listOf(it) }
val x by option(names=arrayOf()).int().convert { listOf(it) }
override fun run_() {
x shouldBe expected
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import com.github.ajalt.clikt.testing.skipDueToKT33294
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.data.blocking.forAll
import io.kotest.data.row
import io.kotest.matchers.should
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.startWith
import kotlin.js.JsName
import kotlin.test.Test
import kotlin.test.fail
Expand Down Expand Up @@ -111,21 +113,34 @@ class OptionGroupsTest {
@Test
@JsName("mutually_exclusive_group_single")
fun `mutually exclusive group single`() {
class C(val runAllowed: Boolean) : TestCommand() {
class C(called: Boolean) : TestCommand(called) {
val g by mutuallyExclusiveOptions(option("--x"), option("--y"), option("--z")).single()
override fun run_() {
if (!runAllowed) fail("run should not be called")
}
}

C(true).apply { parse("--x=1") }.g shouldBe "1"
C(true).apply { parse("--y=1 --y=2") }.g shouldBe "2"

shouldThrow<MutuallyExclusiveGroupException> { C(false).parse("--x=1 --y=2") }
.message shouldBe "option --x cannot be used with --y or --z"
.message shouldBe "option --x cannot be used with --y or --z"

shouldThrow<MutuallyExclusiveGroupException> { C(false).parse("--y=1 --z=2") }
.message shouldBe "option --x cannot be used with --y or --z"
.message shouldBe "option --x cannot be used with --y or --z"
}

@Test
@JsName("mutually_exclusive_group_validate")
fun `mutually exclusive group validate`() {
class C(called: Boolean) : TestCommand(called) {
val g by mutuallyExclusiveOptions(
option("--x").convert { Sealed.Sealed1 }.check { true },
option("--y").convert { Sealed.Sealed2 }.check { false },
)
}

C(true).apply { parse("--x=1") }.g shouldBe Sealed.Sealed1

shouldThrow<BadParameterValue> { C(false).parse("--y=1") }
.message should startWith("Invalid value for \"--y\"")
}

@Test
Expand Down Expand Up @@ -566,3 +581,8 @@ private class Group5 : OptionGroup() {
private class Group6 : OptionGroup() {
val opt2 by option()
}

private sealed class Sealed {
object Sealed1 : Sealed()
object Sealed2 : Sealed()
}