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

Add limit parameter to counted options #484

Merged
merged 3 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## Unreleased
### Added
- Added `limit` parameter to `option().counted()` to limit the number of times the option can be used. You can either clamp the value to the limit, or throw an error if the limit is exceeded. ([#483](https://github.com/ajalt/clikt/issues/483))

## 4.2.2
### Changed
- Options and arguments can now reference option groups in their `defaultLazy` and other finalization blocks. They can also freely reference each other, including though chains of references. ([#473](https://github.com/ajalt/clikt/issues/473))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,12 @@ interface Localization {
fun rangeExceededBoth(value: String, min: String, max: String) =
"$value is not in the valid range of $min to $max."

/**
* A counted option was given more times than its limit
*/
fun countedOptionExceededLimit(count: Int, limit: Int): String =
"option was given $count times, but only $limit times are allowed"

/**
* Invalid value for `choice` parameter
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.github.ajalt.clikt.core.BadParameterValue
import com.github.ajalt.clikt.parameters.types.boolean
import com.github.ajalt.clikt.parameters.types.int
import com.github.ajalt.mordant.terminal.YesNoPrompt
import kotlin.jvm.JvmOverloads

/** A block that converts a flag value from one type to another */
typealias FlagConverter<InT, OutT> = OptionTransformContext.(InT) -> OutT
Expand Down Expand Up @@ -79,8 +80,16 @@ inline fun <OutT> OptionWithValues<Boolean, Boolean, Boolean>.convert(
/**
* Turn an option into a flag that counts the number of times it occurs on the command line.
*/
fun RawOption.counted(): OptionWithValues<Int, Int, Int> {
return int().transformValues(0..0) { it.lastOrNull() ?: 1 }.transformAll { it.sum() }
@JvmOverloads // TODO(5.0): remove this annotation
fun RawOption.counted(limit: Int = 0, clamp: Boolean = true): OptionWithValues<Int, Int, Int> {
ajalt marked this conversation as resolved.
Show resolved Hide resolved
return int().transformValues(0..0) { it.lastOrNull() ?: 1 }.transformAll {
ajalt marked this conversation as resolved.
Show resolved Hide resolved
val s = it.sum()
when {
limit > 0 && clamp -> s.coerceAtMost(limit)
limit in 1..<s -> fail(context.localization.countedOptionExceededLimit(s, limit))
else -> s
}
ajalt marked this conversation as resolved.
Show resolved Hide resolved
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -361,10 +361,11 @@ class OptionTest {
row("-xyx", 2, true, null),
row("-xyxzxyz", 2, true, "xyz"),
row("-xyzxyz", 1, true, "xyz"),
row("-xzfoo", 1, false, "foo")
row("-xzfoo", 1, false, "foo"),
row("-xxxxxx", 4, false, null),
) { argv, ex, ey, ez ->
class C : TestCommand() {
val x by option("-x", "--xx").counted()
val x by option("-x", "--xx").counted(limit = 4)
val y by option("-y", "--yy").flag()
val z by option("-z", "--zz")
override fun run_() {
Expand All @@ -377,6 +378,20 @@ class OptionTest {
C().parse(argv)
}

@Test
@JsName("counted_option_clamp_false")
fun `counted option clamp=false`() {
class C(called: Boolean) : TestCommand(called) {
val x by option("-x").counted(limit = 2, clamp = false)
}

C(true).parse("").x shouldBe 0
C(true).parse("-xx").x shouldBe 2

shouldThrow<UsageError> { C(false).parse("-xxxx") }
.formattedMessage shouldBe "invalid value for -x: option was given 4 times, but only 2 times are allowed"
}

@Test
@JsName("default_option")
fun `default option`() = forAll(
Expand Down
6 changes: 5 additions & 1 deletion docs/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -445,10 +445,13 @@ line:
You might want a flag option that counts the number of times it occurs on the command line. You can
use [`counted`][counted] for this.

You can specify a `limit` for the number of times the [`counted`][counted] option can be given,
and either `clamp` the value or show an error if the limit is exceeded.

=== "Example"
```kotlin
class Log : CliktCommand() {
val verbosity by option("-v").counted()
val verbosity by option("-v").counted(limit=3, clamp=true)
override fun run() {
echo("Verbosity level: $verbosity")
}
Expand All @@ -461,6 +464,7 @@ use [`counted`][counted] for this.
Verbosity level: 3
```


## Feature Switch Flags

Another way to use flags is to assign a value to each option name. You can do this with
Expand Down
Loading