From 3af97bb0b15ee8c600fa3cbb353880341d99b897 Mon Sep 17 00:00:00 2001 From: AJ Alt Date: Thu, 11 Jun 2020 09:49:59 -0700 Subject: [PATCH] Relax type constraints on restrictTo (#193) --- CHANGELOG.md | 1 + .../ajalt/clikt/parameters/types/range.kt | 46 ++++++++++++------- .../ajalt/clikt/parameters/types/RangeTest.kt | 30 ++++++++++-- 3 files changed, 56 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de8c3108c..40786b6e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Changed - When `printHelpOnEmptyArgs` is `true` and no arguments are present, or when `invokeWithoutSubcommand` is `false` and no subcommand is present, `CliktCommand.main` will now exit with status code 1 rather than 0. +- `restrictTo` now works with any `Comparable` value, not just `Number`. ### Fixed - Fixed option values being reset when calling multiple subcommands with `allowMultipleSubcommands=true` ([#190](https://github.com/ajalt/clikt/issues/190)) diff --git a/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/parameters/types/range.kt b/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/parameters/types/range.kt index 89884c3a9..79205dac6 100644 --- a/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/parameters/types/range.kt +++ b/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/parameters/types/range.kt @@ -3,9 +3,8 @@ package com.github.ajalt.clikt.parameters.types import com.github.ajalt.clikt.parameters.arguments.ProcessedArgument import com.github.ajalt.clikt.parameters.options.OptionWithValues -private inline fun checkRange(it: T, min: T? = null, max: T? = null, - clamp: Boolean, fail: (String) -> Unit): T - where T : Number, T : Comparable { +private inline fun > checkRange(it: T, min: T? = null, max: T? = null, + clamp: Boolean, fail: (String) -> Unit): T { require(min == null || max == null || min < max) { "min must be less than max" } if (clamp) { if (min != null && it < min) return min @@ -28,15 +27,18 @@ private inline fun checkRange(it: T, min: T? = null, max: T? = null, * By default, conversion fails if the value is outside the range, but if [clamp] is true, the value will be * silently clamped to fit in the range. * + * This must be called before transforms like `pair`, `default`, or `multiple`, since it checks each + * individual value. + * * ### Example: * * ``` - * argument().int().restrictTo(max=10, clamp=true) + * argument().int().restrictTo(max=10, clamp=true).default(10) * ``` */ -fun ProcessedArgument.restrictTo(min: T? = null, max: T? = null, clamp: Boolean = false) - : ProcessedArgument where T : Number, T : Comparable { - return copy({ checkRange(transformValue(it), min, max, clamp) { fail(it) } }, transformAll, transformValidator) +fun > ProcessedArgument.restrictTo(min: T? = null, max: T? = null, clamp: Boolean = false) + : ProcessedArgument { + return copy({ checkRange(transformValue(it), min, max, clamp) { m -> fail(m) } }, transformAll, transformValidator) } /** @@ -45,14 +47,18 @@ fun ProcessedArgument.restrictTo(min: T? = null, max: T? = null, clamp * By default, conversion fails if the value is outside the range, but if [clamp] is true, the value will be * silently clamped to fit in the range. * + * This must be called before transforms like `pair`, `default`, or `multiple`, since it checks each + * individual value. + * * ### Example: * * ``` - * argument().int().restrictTo(1..10, clamp=true) + * argument().int().restrictTo(1..10, clamp=true).default(10) * ``` */ -fun ProcessedArgument.restrictTo(range: ClosedRange, clamp: Boolean = false) - where T : Number, T : Comparable = restrictTo(range.start, range.endInclusive, clamp) +fun > ProcessedArgument.restrictTo(range: ClosedRange, clamp: Boolean = false): ProcessedArgument { + return restrictTo(range.start, range.endInclusive, clamp) +} // Options @@ -62,15 +68,17 @@ fun ProcessedArgument.restrictTo(range: ClosedRange, clamp: Boolean * By default, conversion fails if the value is outside the range, but if [clamp] is true, the value will be * silently clamped to fit in the range. * + * This must be called before transforms like `pair`, `default`, or `multiple`, since it checks each + * individual value. + * * ### Example: * * ``` - * option().int().restrictTo(max=10, clamp=true) + * option().int().restrictTo(max=10, clamp=true).default(10) * ``` */ -fun OptionWithValues.restrictTo(min: T? = null, max: T? = null, clamp: Boolean = false) - : OptionWithValues where T : Number, T : Comparable { - return copy({ checkRange(transformValue(it), min, max, clamp) { fail(it) } }, transformEach, transformAll, transformValidator) +fun > OptionWithValues.restrictTo(min: T? = null, max: T? = null, clamp: Boolean = false): OptionWithValues { + return copy({ checkRange(transformValue(it), min, max, clamp) { m -> fail(m) } }, transformEach, transformAll, transformValidator) } @@ -80,11 +88,15 @@ fun OptionWithValues.restrictTo(min: T? = null, max: T? = null, cl * By default, conversion fails if the value is outside the range, but if [clamp] is true, the value will be * silently clamped to fit in the range. * + * This must be called before transforms like `pair`, `default`, or `multiple`, since it checks each + * individual value. + * * ### Example: * * ``` - * option().int().restrictTo(1..10, clamp=true) + * option().int().restrictTo(1..10, clamp=true).default(10) * ``` */ -fun OptionWithValues.restrictTo(range: ClosedRange, clamp: Boolean = false) - where T : Number, T : Comparable = restrictTo(range.start, range.endInclusive, clamp) +fun > OptionWithValues.restrictTo(range: ClosedRange, clamp: Boolean = false): OptionWithValues { + return restrictTo(range.start, range.endInclusive, clamp) +} 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 53112adf9..83d6b0370 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 @@ -3,10 +3,7 @@ package com.github.ajalt.clikt.parameters.types import com.github.ajalt.clikt.core.BadParameterValue import com.github.ajalt.clikt.parameters.arguments.argument import com.github.ajalt.clikt.parameters.arguments.optional -import com.github.ajalt.clikt.parameters.options.default -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.parameters.options.* import com.github.ajalt.clikt.testing.TestCommand import io.kotest.assertions.throwables.shouldThrow import io.kotest.data.forall @@ -181,6 +178,31 @@ class RangeTest { .message shouldBe "Invalid value for \"-y\": 10 is not in the valid range of 3 to 4." } + @Test + @JsName("restrictTo_option_char") + fun `restrictTo option char`() { + class C : TestCommand() { + val x by option("-x", "--xx").convert { it[0] }.restrictTo('b'..'d') + } + + C().apply { + parse("") + x shouldBe null + } + C().apply { + parse("-xb") + x shouldBe 'b' + } + C().apply { + parse("-xd") + x shouldBe 'd' + } + shouldThrow { C().parse("--xx=a") } + .message shouldBe "Invalid value for \"--xx\": a is not in the valid range of b to d." + shouldThrow { C().parse("-xe") } + .message shouldBe "Invalid value for \"-x\": e is not in the valid range of b to d." + } + @Test @JsName("restrictTo_argument") fun `restrictTo argument`() {