Skip to content

Commit

Permalink
Relax type constraints on restrictTo (#193)
Browse files Browse the repository at this point in the history
  • Loading branch information
ajalt committed Jun 11, 2020
1 parent 5ee90ff commit 3af97bb
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 21 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -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))
Expand Down
Expand Up @@ -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 <T> checkRange(it: T, min: T? = null, max: T? = null,
clamp: Boolean, fail: (String) -> Unit): T
where T : Number, T : Comparable<T> {
private inline fun <T : Comparable<T>> 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
Expand All @@ -28,15 +27,18 @@ private inline fun <T> 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 <T> ProcessedArgument<T, T>.restrictTo(min: T? = null, max: T? = null, clamp: Boolean = false)
: ProcessedArgument<T, T> where T : Number, T : Comparable<T> {
return copy({ checkRange(transformValue(it), min, max, clamp) { fail(it) } }, transformAll, transformValidator)
fun <T : Comparable<T>> ProcessedArgument<T, T>.restrictTo(min: T? = null, max: T? = null, clamp: Boolean = false)
: ProcessedArgument<T, T> {
return copy({ checkRange(transformValue(it), min, max, clamp) { m -> fail(m) } }, transformAll, transformValidator)
}

/**
Expand All @@ -45,14 +47,18 @@ fun <T> ProcessedArgument<T, T>.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 <T> ProcessedArgument<T, T>.restrictTo(range: ClosedRange<T>, clamp: Boolean = false)
where T : Number, T : Comparable<T> = restrictTo(range.start, range.endInclusive, clamp)
fun <T : Comparable<T>> ProcessedArgument<T, T>.restrictTo(range: ClosedRange<T>, clamp: Boolean = false): ProcessedArgument<T, T> {
return restrictTo(range.start, range.endInclusive, clamp)
}

// Options

Expand All @@ -62,15 +68,17 @@ fun <T> ProcessedArgument<T, T>.restrictTo(range: ClosedRange<T>, 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 <T> OptionWithValues<T?, T, T>.restrictTo(min: T? = null, max: T? = null, clamp: Boolean = false)
: OptionWithValues<T?, T, T> where T : Number, T : Comparable<T> {
return copy({ checkRange(transformValue(it), min, max, clamp) { fail(it) } }, transformEach, transformAll, transformValidator)
fun <T : Comparable<T>> OptionWithValues<T?, T, T>.restrictTo(min: T? = null, max: T? = null, clamp: Boolean = false): OptionWithValues<T?, T, T> {
return copy({ checkRange(transformValue(it), min, max, clamp) { m -> fail(m) } }, transformEach, transformAll, transformValidator)
}


Expand All @@ -80,11 +88,15 @@ fun <T> OptionWithValues<T?, T, T>.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 <T> OptionWithValues<T?, T, T>.restrictTo(range: ClosedRange<T>, clamp: Boolean = false)
where T : Number, T : Comparable<T> = restrictTo(range.start, range.endInclusive, clamp)
fun <T : Comparable<T>> OptionWithValues<T?, T, T>.restrictTo(range: ClosedRange<T>, clamp: Boolean = false): OptionWithValues<T?, T, T> {
return restrictTo(range.start, range.endInclusive, clamp)
}
Expand Up @@ -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
Expand Down Expand Up @@ -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<BadParameterValue> { C().parse("--xx=a") }
.message shouldBe "Invalid value for \"--xx\": a is not in the valid range of b to d."
shouldThrow<BadParameterValue> { 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`() {
Expand Down

0 comments on commit 3af97bb

Please sign in to comment.