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

Rewrite parser to support custom run signatures #508

Merged
merged 43 commits into from Apr 28, 2024
Merged

Conversation

ajalt
Copy link
Owner

@ajalt ajalt commented Apr 14, 2024

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

@ajalt ajalt marked this pull request as ready for review April 14, 2024 22:59
@ajalt ajalt merged commit a6d6542 into master Apr 28, 2024
4 checks passed
@ajalt ajalt deleted the manual-execution branch April 28, 2024 16:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
1 participant