-
Notifications
You must be signed in to change notification settings - Fork 128
Description
Summary
When mutuallyExclusiveOptions includes flags with convert functions, those converters are called prematurely for flags that were not used. I expected conversion to only happen for the mutually exclusive option that was actually used, if any.
Detailed Scenario
I'm using Clikt 5.0.3 with Kotlin 2.3.0. My CLI tool fetches a piece of data and does one of three things with it:
| Flag | Action |
|---|---|
--save=<path> |
Save the data to the given file <path> |
--show |
Show the data |
--print |
Print the data |
Notice that one of these actions requires an option with a <path> argument, while the other two are zero-argument flags.
I've modeled the actions as subclasses of a sealed Receiver class. Assume that the actual receive implementations are more complex than what's shown here.
sealed class Receiver {
abstract fun receive()
init {
println("Initializing $this.")
}
data class SaveAs(var path: Path) : Receiver() {
override fun receive() = println("Save as $path.")
}
class Show : Receiver() {
override fun receive() = println("Show.")
}
class Print : Receiver() {
override fun receive() = println("Print.")
}
}Since exactly one receiver is needed, I've modeled the command line using mutuallyExclusiveOptions, with convert lambdas to create the appropriate Receiver subclass instances:
class Main : CliktCommand() {
val receiver: Receiver by
mutuallyExclusiveOptions(
option("--save").convert { Receiver.SaveAs(Path.of(it)) },
option("--show").flag().convert { Receiver.Show() },
option("--print").flag().convert { Receiver.Print() },
)
.single()
.required()
override fun run() {
print("Using $receiver.")
}
}Observed Behavior Versus Expectation
Given the above, I find that Clikt calls the convert lambdas for each of the two flags, passing false as the value to be converted, even when the actual receiver should be SaveAs. In fact, even if the CLI has no options at all, the flag converters are still called before printing a usage message:
Initializing Receiver$Show@97e1986.
Initializing Receiver$Print@69d9c55.
Usage: main [<options>]
Error: must provide one of --save, --show, --print
My expectation was that a Show instance would only be constructed if --show were used, and likewise that a Print instance would only be constructed if --print were used. Prematurely creating these extra instances is a problem for my actual application, since the actual constructors for these Receiver subclasses do nontrivial work. I really want to create only the one Receiver subclass instance that is actually needed.