Comments (5)
So I decided to solve this with two new features. I added option groups (mutually exclusive, co-occurring, and switched) which should handle the most common use cases. Also, now lambdas passed to option().validate()
and argument().validate()
are called after all parameter values have been set, so they can now refer to other options and arguments.
If you still have use cases that you think are generally applicable, but aren't covered by these changes, please feel free to open a new issue.
from clikt.
Not in the property declaration, but you can do the validation yourself in run
. Check out the validation sample to see an example of that.
The reason you can't have one property read the value of another is that the property values are stored one at a time, and you might not have stored the property you're trying to read yet, which would result in a crash.
However, any time you think you want dependent options, what you probably should use is subcommands. For example, instead of
$ ./program --action upgrade --id foo
You would use
$ ./program upgrade --id foo
Then you can customize as much as you want. This is pattern is also much easier to communicate to your users via the help output.
from clikt.
Oh, I've found myself in need for dependent options too
Currently both custom validation and subcommands seem to have significant downsides :(
Custom validation
Can't use benefits of Clikt API like required()
:
- Requires custom checks for presence of required values
- Requires custom error messaging if required value wasn't passed
- Requires
!!
for required values as they need to be represented as nullable for Clikt
Subcommands
It's almost there, except that it seems that Clikt only supports one subcommand at a time (which I guess is intentional):
- Doesn't allow multiple dependent options
For context, here is my use case (similified)
Ideally something like this:
./program \
--metrics-reporting statsd \
--statsd-hostname somehost \
--statsd-port 8125 \
--cache-system inmemory \
--inmemory-cache-max-items-count 1024
As you can see:
- There is no subcommand because, well, my program only does one thing
--statsd-*
params are only valid if--metrics-reporting
is set tostatsd
--inmemory-*
params are only valid if--cache-system
is set toinmemory
Theoretical subcommands support
I think it could have worked with subcommands if multiple subcommands were allowed, like this:
./program \
report-metrics-to-statsd
--statsd-hostname somehost \
--statsd-port 8125 \
use-inmemory-cache
--inmemory-cache-max-items-count 1024
I also have couple ideas on typesafe dependent (or mutually exclusive) options implementations, but need more time to figure out realistic api for that. Maybe you have something else on mind
from clikt.
I'd love to add dependent options, but I'm pretty sure there's no good way around the fact that an option you want to depend on might not be set yet.
The POSIX approach would be to leave all of those options optional, and ignore the ones that aren't relevant for the current configuration. Required options are already not very POSIX-standard, but they're useful in practice.
I'm definitely not going to add sibling subcommands, since that would lead to ambiguous parsing. But if you really want a CLI that works that way, you can in fact implement it by making all subcommands children of each other.
class Program : CliktCommand() {
val config by findObject { HashMap<String, Any>() }
override fun run() {
config // populate root context obj
}
}
class Statsd : CliktCommand(name = "report-metrics-to-statsd") {
val hostname: String by option().required()
val port: Int by option().int().required()
val config by requireObject<HashMap<String, Any>>()
override fun run() {
config["hostname"] = hostname
config["port"] = port
}
}
class Cache : CliktCommand(name="use-inmemory-cache") {
val maxItems: Int by option().int().default(1024)
val config by requireObject<HashMap<String, Any>>()
override fun run() {
config["max-items"] = maxItems
}
}
fun main(args: Array<String>) {
val program = Program().subcommands(
Statsd().subcommands(Cache()),
Cache().subcommands(Statsd())
)
program.main(args)
println(program.config)
}
$ ./program report-metrics-to-statsd --hostname=localhost --port=8215 use-inmemory-cache --max-items 512
{hostname=localhost, port=8215, max-items=512}
This gets especially hairy if you add more subcommands, but I don't recommend the sibling design anyway. It effectively makes options positional, and I suspect it will lead to pain when someone puts an option in the wrong position.
If you have a lot of these nested-dictionary options, you might consider parsing a config file like toml or yaml instead of doing everything on the command line.
from clikt.
Understandable 👍
from clikt.
Related Issues (20)
- clikt and kscript standalone main have the same JVM signature (main([Ljava/lang/String;)V): HOT 2
- Pasting multiple lines to a prompt is broken HOT 2
- Can't use option inside option group in defaultLazy HOT 2
- Kotlin debug doesn't stop at Clikt subcommand HOT 1
- Add support for TextStyle in the Text Widget. HOT 1
- [Question] Callback to execute some code when the help is shown HOT 4
- Feature request: Command/option/flag information exposed via hook HOT 2
- Init inside `Object` in Kotlin Script HOT 1
- Consider specifying limits on `counted`
- Option to echo "raw" String HOT 4
- Feature request: generation of man pages or AsciiDoc HOT 1
- Feature request: choosing from options using arrow keys or fuzzy find HOT 1
- [QUESTION] Validate input arguments without running the command HOT 1
- Support path arguments for native HOT 3
- Other ways to construct a CliktCommand object HOT 4
- Disable prompt() globally HOT 3
- Cooccuring option group with required options is passed as nullable HOT 3
- Suspending command support HOT 16
- Question: by option().multiple().groupChoice() ? HOT 5
- Question: one single argument for multiple repeating subcommands HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from clikt.