Giter VIP home page Giter VIP logo

Comments (5)

ajalt avatar ajalt commented on May 23, 2024 1

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.

ajalt avatar ajalt commented on May 23, 2024

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.

artem-zinnatullin avatar artem-zinnatullin commented on May 23, 2024

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 to statsd
  • --inmemory-* params are only valid if --cache-system is set to inmemory

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.

ajalt avatar ajalt commented on May 23, 2024

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.

artem-zinnatullin avatar artem-zinnatullin commented on May 23, 2024

Understandable 👍

from clikt.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.