ajalt / clikt

Multiplatform command line interface parsing for Kotlin
https://ajalt.github.io/clikt/
Apache License 2.0
2.57k stars 124 forks source link

Rewrite parser to support custom run signatures #508

Closed ajalt closed 7 months ago

ajalt commented 7 months ago

This PR completely rewrites all parsing, running, and finalization code to separate those three phases.

Previously, all three were interleaved in a single pass. Clikt has evolved a lot of features over the years, and many of them interact with each other. Especially allowMultipleSubcommands, conversions like defaultLazy referencing other parameters, eager options, and MultiUsageError contribute to making the execution logic more complicated.

But that's all disentangled now, and the phases are made public in the new CommandLineProcessor static functions, so you can do this:

// Returns a description of the parsed command line, but doesn't modify the command object,
// run the command, or throw any exceptions.
val parseResult = CommandLineParser.parse(myCommand, argv)
// Now we can run the commands ourselves. `flatten` (optionally) finalizes commands as they're emitted.
for (invocation in parseResult.invocation.flatten()) {
    invocation.command.run()
}

Now we can implement the new command types discussed in #503. All the functionality of CliktCommand is moved to a new BaseCliktCommand that has a self type variable to enforce that subcommands are the same type. This PR includes three implementations:

abstract class CliktCommand(name: String? = null) : BaseCliktCommand<CliktCommand>(name) {
    abstract fun run()
}

abstract class SuspendingCliktCommand(name: String? = null) : BaseCliktCommand<SuspendingCliktCommand>(name) {
    abstract suspend fun run()
}

/**
 * A version of [CliktCommand] that returns a value from the [run] function, which is then passed to
 * subcommands.
 *
 * This command works best if you set [allowMultipleSubcommands] to `true`.
 */
abstract class ChainedCliktCommand<T>(name: String? = null) : BaseCliktCommand<ChainedCliktCommand<T>>(name) {
    abstract fun run(value: T): T
}

Those are the full implementations; not abridged. Implementing your own base type is simple enough that I don't expect to build any more in.

Fixes #342 Fixes #378 Fixes #434 Fixes #449 Fixes #489 Fixes #503

sgammon commented 6 months ago

@ajalt has this been released yet? if not, could a release be run?

ajalt commented 6 months ago

Not yet, but you can use a snapshot build in the meantime.