Options are added to commands by defining a property delegate with the
option
function.
The default option takes one value of type String
. The property is
nullable. If the option is not given on the command line, the property
value will be null. If the option is given at least once, the property
will return the value of the last occurrence of the option.
class Hello: CliktCommand() {
val name by option(help="your name")
override fun run() {
echo("Hello, $name!")
}
}
$ ./hello --name=Foo
Hello, Foo!
If you don't specify names for an option, a lowercase hyphen-separated
name is automatically inferred from the property. For example, val myOpt by option()
will create an option that can be called with
--my-opt
.
You can also specify any number of names for an option manually:
class Hello: CliktCommand() {
val name by option("-n", "--name", help="your name")
override fun run() {
echo("Hello, $name!")
}
}
Option names that are two characters long (like -n
) are treated as
POSIX-style short options. You call them with a value like this:
$ ./hello -nfoo
Hello, foo!
$ ./hello -n foo
Hello, foo!
All other option names are considered long options, and can be called like this:
$ ./hello --name=foo
Hello, foo!
$ ./hello --name foo
Hello, foo!
The option behavior and delegate type can be customized by calling extension functions on the
option
call. For example, here are some different option declarations:
val a: String? by option()
val b: Int? by option().int()
val c: Pair<Int, Int>? by option().int().pair()
val d: Pair<Int, Int> by option().int().pair().default(0 to 0)
val e: Pair<Float, Float> by option().float().pair().default(0f to 0f)
There are three main types of behavior that can be customized independently:
- The type of each value in the option.
The value type is
String
by default, but can be customized with built-in functions likeint
orchoice
, or manually withconvert
. This is detailed in the parameters page. - The number of values that the option requires.
Options take one value by default, but this can be changed with
built-in functions like
pair
andtriple
, or manually withtransformValues
. - How to handle all calls to the option (i.e. if the option is not present, or is present more than once).
By default, the option delegate value is the null if the option is not given on the command line,
and will use the value of the last occurrence if the option is given more than once. You can
change this behavior with functions like
default
andmultiple
.
Since the three types of customizations are orthogonal, you can choose which ones you want to use, and if you implement a new customization, it can be used with all of the existing functions without any repeated code.
By default, option delegates return null
if the option wasn't provided
on the command line. You can instead return a default value with default
.
class Pow : CliktCommand() {
val exp by option("-e", "--exp").double().default(1.0)
override fun run() {
echo("2 ^ $exp = ${(2.0).pow(exp)}")
}
}
$ ./pow -e 8
2 ^ 8.0 = 256.0
$ ./pow
2 ^ 1.0 = 2.0
If the default value is expensive to compute, you can use
defaultLazy
instead of default
.
It has the same effect, but you give it a lambda returning the default value,
and the lambda will only be called if the default value is used.
Options can take any fixed number of values separated by whitespace, or a variable number of values separated by a non-whitespace delimiter you specify. If you want a variable number of values separated by whitespace, you need to use an argument instead.
There are built in functions for options that take two values (pair
, which uses a Pair
),
or three values (triple
, which uses a Triple
).
You can change the type of each value as normal with functions like int
.
If you need more values, you can provide your own container with
transformValues
. You
give that function the number of values you want, and a lambda that will transform a list of values
into the output container. The list will always have a size equal to the number you specify. If the
user provides a different number of values, Clikt will inform the user and your lambda won't be
called.
data class Quad<out T>(val a: T, val b: T, val c: T, val d: T)
fun <T> Quad<T>.toList(): List<T> = listOf(a, b, c, d)
class Geometry : CliktCommand() {
val square by option().int().pair()
val cube by option().int().triple()
val tesseract by option().int().transformValues(4) { Quad(it[0], it[1], it[2], it[3]) }
override fun run() {
echo("Square has dimensions ${square?.toList()?.joinToString("x")}")
echo("Cube has dimensions ${cube?.toList()?.joinToString("x")}")
echo("Tesseract has dimensions ${tesseract?.toList()?.joinToString("x")}")
}
}
$ ./geometry --square 1 2 --cube 3 4 5 --tesseract 6 7 8 9
Square has dimensions 1x2
Cube has dimensions 3x4x5
Tesseract has dimensions 6x7x8x9
You can use split
to allow a
variable number of values to a single option invocation by separating the values with non-whitespace
delimiters.
class C : CliktCommand() {
val profiles by option("-P").split(",")
override fun run() {
for (profile in profiles) {
echo(profile)
}
}
}
$ ./split -P profile-1,profile-2
profile-1
profile-2
Normally, when an option is provided on the command line more than once,
only the values from the last occurrence are used. But sometimes you
want to keep all values provided. For example, git commit -m foo -m bar
would create a commit message with two lines: foo
and bar
. To
get this behavior with Clikt, you can use multiple
. This
will cause the property delegate value to be a list, where each item in
the list is the value of from one occurrence of the option. If the option
is never given, the list will be empty (or you can specify a default to use).
class Commit : CliktCommand() {
val message: List<String> by option("-m").multiple()
override fun run() {
echo(message.joinToString("\n"))
}
}
$ ./commit -m foo -m bar
foo
bar
You can combine multiple
with item type conversions and multiple values.
val opt: List<Pair<Int, Int>> by option().int().pair().multiple()
You can also supply a default value to multiple
or require at least one value be present
on the command line. These are specified as arguments rather than with separate extension functions
since they don't change the type of the delegate.
val opt: List<String> by option().multiple(required=true)
val opt: List<String> by option().multiple(default=listOf("default message"))
You can discard duplicate values from a multiple
option with unique
.
class Build : CliktCommand() {
val platforms: Set<String> by option("-p").multiple().unique()
override fun run() {
echo("Building for platforms: $platforms")
}
}
$ ./build -p android -p ios -p android
Building for platforms: [android, ios]
Flags are options that don't take a value. Boolean flags can be enabled
or disabled, depending on the name used to invoke the option. You can
turn an option into a boolean flag with flag
. That function takes an optional
list of secondary names that will be added to any existing or inferred
names for the option. If the option is invoked with one of the secondary
names, the delegate will return false. It's a good idea to always set
secondary names so that a user can disable the flag if it was enabled
previously.
class Cli : CliktCommand() {
val flag by option("--on", "-o").flag("--off", "-O", default = false)
override fun run() {
echo(flag)
}
}
$ ./cli -o
true
$ ./cli --on --off
false
Multiple short flag options can be combined when called on the command line:
class Cli : CliktCommand() {
val flagA by option("-a").flag()
val flagB by option("-b").flag()
val foo by option("-f")
override fun run() {
echo("$flagA $flagB $foo")
}
}
$ ./cli -abfFoo
true true Foo
You might want a flag option that counts the number of times it occurs on the command line. You can
use counted
for this.
class Log : CliktCommand() {
val verbosity by option("-v").counted()
override fun run() {
echo("Verbosity level: $verbosity")
}
}
$ ./log -vvv
Verbosity level: 3
Another way to use flags is to assign a value to each option name. You can do this with
switch
, which takes a map of
option names to values. Note that the names in the map replace any previously specified or inferred
names.
class Size : CliktCommand() {
val size by option().switch(
"--large" to "large",
"--small" to "small"
).default("unknown")
override fun run() {
echo("You picked size $size")
}
}
$ ./size --small
You picked size small
You can restrict the values that a regular option can take to a set of values using
choice
. You can also map the
input values to new types.
class Digest : CliktCommand() {
val hash by option().choice("md5", "sha1")
override fun run() {
echo(hash)
}
}
$ ./digest --hash=md5
md5
$ ./digest --hash=sha256
Usage: digest [OPTIONS]
Error: Invalid value for "--hash": invalid choice: sha256. (choose from md5, sha1)
$ ./digest --help
Usage: digest [OPTIONS]
Options:
--hash [md5|sha1]
-h, --help Show this message and exit
If choice
or switch
options aren't flexible enough,
you can use mutuallyExclusiveOptions
to group any nullable options into a mutually exclusive group. If more than one of the options in
the group is given on the command line, the last value is used.
If you want different types for each option, you can wrap them in a sealed class. You can also use
wrapValue
if you have an existing conversion function like int or file
you'd like to use.
sealed class Fruit {
data class Oranges(val size: String): Fruit()
data class Apples(val count: Int): Fruit()
}
class Order : CliktCommand() {
val fruit: Fruit? by mutuallyExclusiveOptions<Fruit>(
option("--oranges").convert { Oranges(it) },
option("--apples").int().wrapValue { Apples(it) }
)
override fun run() = echo(fruit)
}
$ ./order --apples=10
Apples(count=10)
$ ./order --oranges=small
Oranges(size=small)
$ ./order --apples=10 --oranges=large
Oranges(size=large)
You can enforce that only one of the options is given with single
:
val fruit: Fruit? by mutuallyExclusiveOptions<Fruit>(
option("--apples").convert { Apples(it.toInt()) },
option("--oranges").convert { Oranges(it) }
).single()
$ ./order --apples=10 --oranges=small
Usage: order [OPTIONS]
Error: option --apples cannot be used with --oranges
Like regular options, you can make the entire group
required
, or give it a default
value.
Like other option groups, you can specify a name
and
help
text for the group if you want to set the group apart in the help output.
Sometimes you have a set of options that only make sense when specified together. To enforce this,
you can make an option group cooccurring
.
Co-occurring groups must have at least one
required
option, and may also
have non-required options. The required
constraint is enforced if any of the options in the group
are given on the command line. If none if the options are given, the value of the group is null.
class UserOptions : OptionGroup() {
val name by option().required()
val age by option().int()
}
class Tool : CliktCommand() {
val userOptions by UserOptions().cooccurring()
override fun run() {
userOptions?.let {
echo(it.name)
echo(it.age)
} ?: echo("No user options")
}
}
$ ./tool
No user options
$ ./tool --name=jane --age=30
jane
30
$ ./tool --age=30
Usage: tool [OPTIONS]
Error: Missing option "--name".
Like other option groups, you can specify a name
and
help
text for the group if you want to set the group apart in the help output.
If you have different groups of options that only make sense when another option has a certain value,
you can use groupChoice
and groupSwitch
.
groupChoice
options are similar to choice
options, but instead of mapping a value to
a single new type, they map a value to a co-occurring OptionGroup
.
Options for groups other than the selected one are ignored, and only the selected group's required
constraints are enforced. In the same way, groupSwitch
options are similar to switch
options.
sealed class LoadConfig(name: String): OptionGroup(name)
class FromDisk : LoadConfig("Options for loading from disk") {
val path by option().file().required()
val followSymlinks by option().flag()
}
class FromNetwork: LoadConfig("Options for loading from network") {
val url by option().required()
val username by option().prompt()
val password by option().prompt(hideInput = true)
}
class Tool : CliktCommand(help = "An example of a custom help formatter that uses ansi colors") {
val load by option().groupChoice(
"disk" to FromDisk(),
"network" to FromNetwork()
)
override fun run() {
when(val it = load) {
is FromDisk -> echo("Loading from disk: ${it.path}")
is FromNetwork -> echo("Loading from network: ${it.url}")
null -> echo("Not loading")
}
}
}
$ ./tool --load=disk --path=./config --follow-symlinks
Loading from disk: .\config
$ ./tool --load=network --url=www.example.com --username=admin
Password: *******
Loading from network: www.example.com
$ ./tool --load=disk
Usage: cli [OPTIONS]
Error: Missing option "--path".
$ ./tool --load=whoops
Usage: cli [OPTIONS]
Error: Invalid value for "--load": invalid choice: whoops. (choose from disk, network)
In some cases, you might want to create an option that uses the value
given on the command line if there is one, but prompt the user for input
if one is not provided. Clikt can take care of this for you with the prompt
function.
class Hello : CliktCommand() {
val name by option().prompt()
override fun run() {
echo("Hello $name")
}
}
./hello --name=foo
Hello foo
./hello
Name: foo
Hello foo
The default prompt string is based on the option name, but
prompt
takes a number of parameters to customize the output.
You can also create a option that uses a hidden prompt and asks for confirmation. This combination of behavior is commonly used for passwords.
class Login : CliktCommand() {
val password by option().prompt(requireConfirmation = true, hideInput = true)
override fun run() {
echo("Your hidden password: $password")
}
}
$ ./login
Password:
Repeat for confirmation:
Your hidden password: hunter2
Sometimes you want an option to halt execution immediately and print a
message. For example, the built-on --help
option, or the --version
option that many programs have. Neither of these options have any value
associated with them, and they stop command line parsing as soon as
they're encountered.
The --help
option is added automatically to commands, and --version
can be added using
versionOption
. Since
the option doesn't have a value, you can't define it using a property delegate. Instead, call the
function on a command directly, either in an init
block, or on a command instance.
These definitions are equivalent:
class Cli : NoOpCliktCommand() {
init {
versionOption("1.0")
}
}
fun main(args: Array<String>) = Cli().main(args)
class Cli : NoOpCliktCommand()
fun main(args: Array<String>) = Cli().versionOption("1.0").main(args)
$ ./cli --version
cli version 1.0
If you want to define your own option with a similar behavior, you can do so by calling
eagerOption
. This function takes an action
that is called when the option is
encountered on the command line. To print a message and halt execution normally from the callback,
you can throw a PrintMessage
exception, and
CliktCommand.main
will take care of printing the message. If you want to exit
normally without printing a message, you can throw Abort(error=false)
instead.
You can define your own version option like this:
class Cli : NoOpCliktCommand() {
init {
eagerOption("--version") {
throw PrintMessage("$commandName version 1.0")
}
}
}
You can communicate to users that an option is deprecated with
option().deprecated()
. By default, this function will add a tag to the option's help
message, and print a warning to stderr if the option is used.
You can customize or omit the warning message and help tags, or change the warning into an error.
class Cli : CliktCommand() {
val opt by option(help = "option 1").deprecated()
val opt2 by option(help = "option 2").deprecated("WARNING: --opt2 is deprecated, use --new-opt instead", tagName = null)
val opt3 by option(help = "option 3").deprecated(tagName = "pending deprecation", tagValue = "use --new-opt instead")
val opt4 by option(help = "option 4").deprecated(error = true)
override fun run() = echo("command run")
}
$ ./cli --opt=x
WARNING: option --opt is deprecated
command run
$ ./cli --opt2=x
WARNING: --op2 is deprecated, use --new-opt instead
command run
$ ./cli --opt3=x
WARNING: option --opt3 is deprecated
command run
$ ./cli --opt4=x
ERROR: option --opt4 is deprecated
$ ./cli --help
Usage: cli [OPTIONS]
Options:
--opt TEXT option 1 (deprecated)
--opt2 TEXT option 2
--opt3 TEXT option 3 (pending deprecation: use --new-opt instead)
--opt4 TEXT option 4 (deprecated)
Clikt supports reading option values from environment variables if they
aren't given on the command line. This feature is helpful when
automating tools. For example, when using git commit
, you can set the
author date with a command line parameter:
git commit --date=10/21/2015
. But you can also set it with an
environment variable: GIT_AUTHOR_DATE=10/21/2015 git commit
.
Clikt will read option values from environment variables as long as it has an envvar name for the option. There are two ways to set that name: you can set the name manually for an option, or you can enable automatic envvar name inference.
To set the envvar name manually, pass the name to
option
:
class Hello : CliktCommand() {
val name by option(envvar = "MY_NAME")
override fun run() {
echo("Hello $name")
}
}
$ export MY_NAME=Foo
$ ./hello
Hello Foo
$ export MY_NAME=Foo
$ ./hello --name=Bar
Hello Bar
You can enable automatic envvar name inference by setting the autoEnvvarPrefix
on a command's
context
. This will cause all options without
an explicit envvar name to be given an uppercase underscore-separated envvar name. Since the prefix
is set on the context
, it is propagated to
subcommands. If you have a a subcommand called foo
with an option --bar
, and your prefix is
MY_TOOL
, the option's envvar name will be MY_TOOL_FOO_BAR
.
class Hello : CliktCommand() {
init {
context { autoEnvvarPrefix = "HELLO" }
}
val name by option()
override fun run() {
echo("Hello $name")
}
}
$ export HELLO_NAME=Foo
$ ./hello
Hello Foo
You might need to allow users to specify multiple values for an option in a single environment
variable. You can do this by creating an option with
multiple
. The environment
variable's value will be split according a regex, which defaults to split on whitespace for most
types. file
will change the pattern
to split according to the operating system's path splitting rules. On Windows, it will split on
semicolons (;
). On other systems, it will split on colons (:
). You can also specify a split
pattern by passing it to the envvarSplit
parameter of option
.
class Hello : CliktCommand() {
val names by option(envvar = "NAMES").multiple()
override fun run() {
for (name in names) echo("Hello $name")
}
}
$ export NAMES=Foo Bar
$ ./hello
Hello Foo
Hello Bar
Clikt also supports reading option values from one or more configuration files (or other sources)
when they aren't present on the command line. For example, when using git commit
, you can set the
author email with a command line parameter: git commit --author='Clikt <[email protected]>
. But
you can also set it in your git configuration file: [email protected]
.
Clikt allows you to specify one or more sources of option values that will be read from with the
Context.valueSource
builder.
class Hello : CliktCommand() {
init {
context {
valueSource = PropertiesValueSource.from("myconfig.properties")
}
}
val name by option()
override fun run() {
echo("Hello $name")
}
}
$ echo "name=Foo" > myconfig.properties
$ ./hello
Hello Foo
You can also pass multiple sources to Context.valueSources
, and each
source will be searched for the value in order.
Clikt includes support for reading values from a map, and (on JVM) from Java Properties files. You can add any other file type by implementing ValueSource. See the JSON sample for an implementation that uses kotlinx.serialization to load values from JSON files.
Every option can read values from both environment variables and configuration files. By default,
Clikt will use the value from an environment variable before the value from a configuration file,
but you can change this by setting Context.readEnvvarBeforeValueSource
to
false
.
When specifying option names manually, you can use any prefix (as long as it's entirely punctuation).
For example, you can make a Windows-style interface with slashes:
class Hello: CliktCommand() {
val name by option("/name", help="your name")
override fun run() {
echo("Hello, $name!")
}
}
$ ./hello /name Foo
Hello, Foo!
Or you can make a Java-style interface that uses single-dashes for long options:
class Hello: CliktCommand() {
val name by option("-name", help="your name")
override fun run() {
echo("Hello, $name!")
}
}
$ ./hello -name Foo
Hello, Foo!
Note that inferred names will always have a POSIX-style prefix like
--name
. If you want to use a different prefix, you should specify all
option names manually.
Clikt has a large number of extension functions that can modify options.
When applying multiple functions to the same option,
there's only one valid order for the functions to be applied.
For example, option().default(3).int()
will not compile,
because default
must be applied after the value type conversion.
Similarly, you can only apply one transform of each type.
So option().int().float()
is invalid since int
and float
both change the value type, as is option().default("").multiple()
since default
and multiple
both transform the
call list (if you need a custom default value for multiple
, you can pass it one as an argument).
Here's an integer option with one of each available transform in a valid order:
val opt: Pair<Int, Int> by option("-o", "--opt")
.int()
.restrictTo(1..100)
.pair()
.default(1 to 2)
.validate { require(it.second % 2 == 0) }