Commands¶
Clikt supports arbitrarily nested commands. You can add one command as a child of another with the
subcommands
function, which can be
called either in an init
block, or on an existing instance.
Executing Nested Commands¶
For commands with no children,
run
is called whenever the
command line is parsed (unless parsing is aborted from an error or an option like --help
).
If a command has children, this isn’t the case. Instead, its run
is
called only if a child command is invoked, just before the subcommand’s
run
. If a parent command is called without specifying a subcommand,
the help page is printed and run
is not called.
class Tool : CliktCommand() {
val verbose by option().flag("--no-verbose")
override fun run() {
echo("Verbose mode is ${if (verbose) "on" else "off"}")
}
}
class Execute : CliktCommand() {
override fun run() {
echo("executing")
}
}
fun main(args: Array<String>) = Tool().subcommands(Execute()).main(args)
$ ./tool
Usage: tool [<options>] <command> [<args>]...
Options:
--verbose / --no-verbose
-h, --help Show this message and exit
Commands:
execute
$ ./tool --verbose execute
Verbose mode is on
executing
Customizing Command Name¶
The default name for subcommands is inferred as a lowercase name from the command class name. You
can also set a name manually in the
CliktCommand
constructor.
class Tool : CliktCommand() {
override fun run()= Unit
}
class Execute : CliktCommand(name = "RUN-ME") {
override fun run() {
echo("executing")
}
}
fun main(args: Array<String>) = Tool().subcommands(Execute()).main(args)
$ ./tool RUN-ME
executing
$ ./tool -h
Usage: tool [<options>] <command> [<args>]...
Options:
-h, --help Show this message and exit
Commands:
RUN-ME
Passing Parameters¶
When calling subcommands, the position of options and arguments on the command line affect which command will parse them. A parameter is parsed by a command if it occurs after the command name, but before any other command names.
class Tool : CliktCommand() {
override fun help(context: Context) = "A tool that runs"
val verbose by option().flag("--no-verbose")
override fun run() = Unit
}
class Execute : CliktCommand() {
override fun help(context: Context) = "Execute the command"
val name by option()
override fun run() = Unit
}
fun main(args: Array<String>) = Tool().subcommands(Execute()).main(args)
$ ./tool --help
Usage: tool [<options>] <command> [<args>]...
A tool that runs
Options:
--verbose / --no-verbose
-h, --help Show this message and exit
Commands:
execute Execute the command
If you instead execute --help
after the subcommand, the subcommand’s
help is printed:
$ ./tool execute --help
Usage: execute [<options>]
Execute the command
Options:
--name <text>
-h, --help Show this message and exit
But executing ./tool --help execute
, with the option before the
subcommand, will cause the parent’s help option to be invoked, printing
out Tool
’s help page as if you just typed ./tool --help
.
Nested Handling And Contexts¶
Normally nested command are independent of each other: a child can’t
access its parent’s parameters. This makes composing commands much
easier, but what if you want to pass information to a child command? You
can do so with the command’s Context
.
Every time the command line is parsed, each command creates a new
context object for itself that is linked to its parent’s context.
Context
objects have a number of properties that can be used to
customize command line parsing. Although each command creates its own
context, the configuration is inherited from the parent context.
Context
objects also have a [data
][Context.data] map and obj
property that
hold objects that can be accessed from child commands.
data class MyConfig(var verbose: Boolean = false)
class Tool : CliktCommand() {
val verbose by option().flag("--no-verbose")
val config by findOrSetObject { MyConfig() }
override fun run() {
config.verbose = if (verbose) "on" else "off"
}
}
class Execute : CliktCommand() {
val config by requireObject<MyConfig>()
override fun run() {
echo("Verbose mode is ${config.verbose}")
}
}
fun main(args: Array<String>) = Tool().subcommands(Execute()).main(args)
$ ./tool --verbose execute
Verbose mode is on
The findObject
, findOrSetObject
, and
requireObject
functions will walk up the context tree until they find a
obj
with the given type. If no such object exists, they will either return null
,
throw an exception, or create an instance of the object and store it on the command’s context,
depending on which function you use. If you need more than one object, you can pass a key
to these
functions, and they’ll look for an object with that key and type in the context’s data
map.
Keep in mind that the findOrSetObject
property is lazy and won’t set the
Context’s obj
until its value is accessed. If you need to set an object for subcommands without
accessing the property, you should use currentContext.findOrSetObject
,
or set currentContext.obj
or Context.Builder.obj
directly,
instead.
class Tool : CliktCommand() {
override fun run() {
// runs eagerly
currentContext.findOrSetObject { MyConfig() }
}
}
class Tool : CliktCommand() {
override fun run() {
// runs eagerly, won't look for parent contexts
currentContext.obj = MyConfig()
}
}
Tool().context {
// runs eagerly, won't look for parent contexts
obj = MyConfig()
}
Tip
If you need to share resources that need to be cleaned up, you can use
currentContext.registerCloseable
Running Parent Command Without Children¶
Normally, if a command has children, run
is not called unless a child command is invoked on
the command line. Instead, --help
is called on the parent. If you want to change this behavior to
always call run()
on the parent, you can do so by setting invokeWithoutSubcommand
to true
. The
Context
will then have information on the subcommand that is about to be invoked, if there is one.
class Tool : CliktCommand() {
override val invokeWithoutSubcommand = true
override fun run() {
val subcommand = currentContext.invokedSubcommand
if (subcommand == null) {
echo("invoked without a subcommand")
} else {
echo("about to run ${subcommand.name}")
}
}
}
class Execute : CliktCommand() {
override fun run() {
echo("running subcommand")
}
}
$ ./tool
invoked without a subcommand
$./tool execute
about to run execute
running subcommand
Customizing Contexts¶
Contexts have a number of properties that can be customized,
and which are inherited by child commands.
You can change these properties with the context
builder function,
which can be called in an init
block, or on a command instance.
For example, you can change the name of help option. These definitions are equivalent:
class Cli : NoOpCliktCommand() {
init {
context { helpOptionNames = setOf("/help") }
}
}
fun main(args: Array<String>) = Cli()
class Cli : NoOpCliktCommand()
fun main(args: Array<String>) = Cli()
.context { helpOptionNames = setOf("/help") }
.main(args)
$ ./cli --help
Usage: cli [<options>]
Options:
-h, --help print the help
Printing the Help Message When No Arguments Are Given¶
Normally, if a command is called with no values on the command line, a usage error is printed if
there are required parameters, or run
is called if there aren’t any.
You can change this behavior by passing overriding printHelpOnEmptyArgs = true
in your command.
This will cause a help message to be printed when no values are provided on the command
line, regardless of the parameters in your command.
class Cli : CliktCommand() {
override val printHelpOnEmptyArgs = true
val arg by argument()
override fun run() { echo("Command ran") }
}
$ ./cli
Usage: cli [<options>]
Options:
-h, --help print the help
Warnings and Other Messages¶
When you want to show information to the user, you’ll usually want to use the functions for printing to stdout directly.
However, there’s another mechanism that can be useful when writing reusable parameter code:
command messages. These messages are buffered during parsing and printed all at once immediately
before a command’s run
is called.
They are not printed if there are any errors in parsing. This type of message is used by Clikt for
deprecating options
.
You can issue a command message by calling
CliktCommand.issueMessage
or with the
message
function available in the context of parameter transformers.
class Cli : CliktCommand() {
// This will print the warning when the option is given, but not if there are errors
val opt by option().validate {
if (it.isEmpty()) message("Empty strings are not recommended")
}
override fun run() {
echo("command run")
}
}
$ ./cli --opt=''
Empty strings are not recommended
command run
$ ./cli --opt='' --oops
Error: no such option: "--oops".
You can disable automatic message printing on the command’s context:
class Cli : CliktCommand() {
init { context { printExtraMessages = false } }
val opt by option().validate {
if (it.isEmpty()) message("Empty strings are not recommended")
}
override fun run() {
echo("command run")
}
}
$ ./cli --opt=''
command run
Chaining and Repeating Subcommands¶
Some command line interfaces allow you to call more than one subcommand at a time. For example, you
might do something like gradle clean build publish
to run the clean
task, then the build
task,
then the publish
task, which are all subcommands of gradle
.
To do this with Clikt, override allowMultipleSubcommands = true
in your command.
class Compiler: CliktCommand() {
override val allowMultipleSubcommands = true
override fun run() {
echo("Running compiler")
}
}
class Clean: CliktCommand() {
val force by option().flag()
override fun run() {
echo("Cleaning (force=$force)")
}
}
class Build: CliktCommand() {
val file by argument().file()
override fun run() {
echo("Building $file")
}
}
fun main(args: Array<String>) = Compiler().subcommands(Clean(), Build()).main(args)
$ ./compiler clean --force build main.kt
Running compiler
Cleaning (force=true)
Building main.kt
The parent command will run
once, and each subcommand will run
once each time they’re
called.
Warning
Enabling allowMultipleSubcommands
will disable allowInterspersedArgs
on the command and all its subcommands. If both were allowed to be enabled at the same time, then
not all command lines could be parsed unambiguously.
Subcommands of a command with allowMultipleSubcommands=true
can themselves have subcommands, but
cannot have allowMultipleSubcommands=true
.
Command pipelines¶
If you have multiple subcommands you might want to pass the output of one subcommand to the next. For example, the ImageMagick tool lets you apply a series of transformations to an image by invoking multiple subcommands.
To do this with Clikt, you could pass your output through the
Context.obj
, but another option is to use a
ChainedCliktCommand, which allows you to return values from your run
function that will be
passed to the next subcommand.
In this example, we’ll write simple text editing pipeline that takes an initial string, and then applies a series of transformations to it, printing the final result:
class EditText : ChainedCliktCommand<String>() {
override val allowMultipleSubcommands: Boolean = true
val text by argument()
override fun run(value: String): String = text
}
class RepeatText : ChainedCliktCommand<String>("repeat") {
val count by option().int().default(1)
override fun run(value: String): String {
return value.repeat(count)
}
}
class UppercaseText : ChainedCliktCommand<String>("uppercase") {
override fun run(value: String): String {
return value.uppercase()
}
}
class ReplaceText : ChainedCliktCommand<String>("replace") {
val oldValue by argument()
val newValue by argument()
override fun run(value: String): String {
return value.replace(oldValue, newValue)
}
}
fun main(args: Array<String>) {
val command = EditText()
.subcommands(RepeatText(), UppercaseText(), ReplaceText())
val result = command.main(args, "")
command.echo(result)
}
$ ./edit-text 'hello ' uppercase repeat --count=3 replace H Y
YELLO YELLO YELLO