Options¶
Options are added to commands by defining a property delegate with the
option
function.
Basic Options¶
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!
Option Names¶
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!
Customizing Options¶
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 the existing functions without any repeated code.
Default Values¶
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, or you want to use another parameter as the default,
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
option isn’t present on the command line.
Warning
The lambda you pass to defaultLazy
is normally called at most once. But the lambda references
another parameter, it may be called more than once in order to make sure the parameter it references
is available.
Multi Value Options¶
Options can take a fixed number of values separated by whitespace, a variable number of values separated by a delimiter, or a variable number of values separated by a whitespace.
Options With Fixed Number of Values¶
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
Splitting an Option Value on a Delimiter¶
You can use split
to allow a variable number of values to a single option invocation by
separating the values with non-whitespace delimiters. This will also split values from environment
variables.
class C : CliktCommand() {
val profiles by option("-P", envvar="PROFILES").split(",")
override fun run() {
for (profile in profiles) {
echo(profile)
}
}
}
$ ./split -P profile-1,profile-2
profile-1
profile-2
$ export PROFILES=profile-1,profile-2
$ ./split
profile-1
profile-2
Options with an Optional Value¶
You can create options that take zero or one values with optionalValue
or
optionalValueLazy
.
class C : CliktCommand() {
val log by option().optionalValue("debug").default("none")
override fun run() {
echo("log level: $log")
}
}
$ ./command --log
log level: debug
$ ./command --log=verbose
log level: verbose
$ ./command
log level: none
Warning
If a user specifies the value without an =
, as in --log debug
, the debug
will always be interpreted as a
value for the option, even if the command accepts arguments. This might be confusing to your users, so you
can require that the option value always be specified with =
by passing
optionalValue(acceptsUnattachedValue=false)
.
Options With a Variable Number of Values¶
If you want your option to take a variable number of values, but want to split the value on whitespace
rather than a delimiter, you can use
varargValues
.
class Order : CliktCommand() {
val sizes: List<String> by option().varargValues()
override fun run() {
echo("You ordered: $sizes")
}
}
$ ./order --sizes small medium
You ordered: ["small", "medium"]
By default, varargValues
requires at least one value, and has no maximum limit. You can configure the limits by
passing min
and max
arguments to varargValues
.
Multiple Options¶
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()
Default values for option().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"))
Deduplicating option().multiple() into a unique set¶
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]
Key-Value and Map Options¶
You can split an option’s value into a key-value pair with splitPair
. By default, the
delimiter =
will be used to split. You can also use associate
to allow the option
to be specified multiple times, and have its values collected in a map.
class Build : CliktCommand() {
val systemProp: Map<String, String> by option("-D", "--system-prop").associate()
override fun run() {
echo(systemProp)
}
}
$ ./build -Dplace=here --system-prop size=small
{place=here, size=small}
Boolean Flag Options¶
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
Tip
You can diasable short option grouping by setting
Context.allowGroupedShortOptions
to false
.
Counted Flag Options¶
You might want a flag option that counts the number of times it occurs on the command line. You can
use counted
for this.
You can specify a limit
for the number of times the counted
option can be given,
and either clamp
the value or show an error if the limit is exceeded.
class Log : CliktCommand() {
val verbosity by option("-v").counted(limit=3, clamp=true)
override fun run() {
echo("Verbosity level: $verbosity")
}
}
$ ./log -vvv
Verbosity level: 3
Feature Switch Flags¶
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
Choice Options¶
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
Mutually Exclusive Option Groups¶
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.
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().convert { 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.
Co-Occurring Option Groups¶
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 of 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.
Choice and Switch Options With Groups¶
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() {
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)
Number Options Without a Name¶
If you have an int or long option, you might want to allow it to be specified without need the
option name. For example, git log -2
and git log -n 2
are equivalent. You can add an option like
this by passing acceptsValueWithoutName=true
to int()
or long()
.
class Tool : CliktCommand() {
val level by option("-l", "--level", metavar = "<number>")
.int(acceptsValueWithoutName = true)
override fun run() {
echo("Level: $level")
}
}
$ ./tool -20
Level: 20
$ ./tool --level=3
Level: 3
$ ./tool --help
Usage: tool [<options>]
Options:
-<number>, -l, --level <number>
-h, --help Show this message and exit
Caution
You may only have one option with acceptsValueWithoutName=true
per command
Prompting For Input¶
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.
Password Prompts¶
You can also create an 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:
Your hidden password: hunter2
Eager Options¶
Sometimes you want an option to halt execution immediately and print a
message. For example, the built-in --help
option, or the --version
option that many programs have.
The --help
option is added automatically to commands, and --version
can be added using
versionOption
. Since these options don’t have values, you can’t define them using
property delegates. 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
Custom Eager Options¶
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("$name version 1.0")
}
}
}
Custom Eager Options With Values¶
If you need an eager option that takes a value, pass eager=true
to option()
.
val color by option(eager=true).flag("--no-color", default=true)
Warning
Eager options can’t reference other options or arguments, since they’re evaluated before parsing the
rest of the command line. They can be declared in regular OptionGroups
, but not in other types of
groups like switch groups.
Deprecating Options¶
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)
Unknown Options¶
You may want to collect unknown options for manual processing. You can do this by overriding
treatUnknownOptionsAsArgs = true
in your command. This will cause Clikt to treat unknown options
as positional arguments rather than reporting an error when one is encountered. You’ll need to
define an argument().multiple()
property to collect the options, otherwise an
error will still be reported.
class Wrapper : CliktCommand() {
init { context { allowInterspersedArgs = false }}
override val treatUnknownOptionsAsArgs = true
val command by option().required()
val arguments by argument().multiple()
override fun run() {
val cmd = (listOf(command) + arguments).joinToString(" ")
val proc = Runtime.getRuntime().exec(cmd)
echo(proc.inputStream.bufferedReader().readText())
proc.waitFor()
}
}
$ ./wrapper --command=git tag --help | head -n4
GIT-TAG(1) Git Manual GIT-TAG(1)
NAME
git-tag - Create, list, delete or verify a tag object signed with GPG
Warning
Multiple short options in a single token (e.g. using -abc
to specify -a
, -b
, and -c
in a
single token) will still report an error if it contains a mixture of known and unknown options. To
avoid this, don’t declare single-letter names for options in commands that use
treatUnknownOptionsAsArgs
.
You’ll often want to set allowInterspersedArgs = false
on your Context
when using treatUnknownOptionsAsArgs
. You may also find that subcommands are a better fit than
treatUnknownOptionsAsArgs
for your use case.
Values From Environment Variables¶
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 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
Multiple Values from Environment Variables¶
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
split
.
Flag Option Values from Environment Variables¶
For flag options, any of the following (case-insensitive) environment variable values will be
interpreted as true
:
"true"
,"t"
,"1"
,"yes"
,"y"
,"on"
The following (case-insensitive) values wil be interpreted as false
:
"false"
,"f"
,"0"
,"no"
,"n"
,"off"
All other values are invalid.
Overriding system environment variables¶
You can set a custom function that will be used instead of the system environment variables with ContextBuilder.readEnvvar.
@Test
fun `test envvar`() {
val envvars = mapOf("MY_TOOL_OPTION" to "value")
val tool = MyTool().context {
readEnvvar = { envvars[it] }
}
tool.parse(emptyList())
assertEquals("value", tool.option)
}
Values from Configuration Files¶
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 <clikt@example.com>
. But
you can also set it in your git configuration file: user.email=clikt@example.com
.
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. For these two sources, you can customize the keys used to
look up options by passing the result of ValueSource.getKey
or
ValueSource.envvarKey
to the source’s getKey
constructor parameter.
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.
Configuration Files and Environment Variables¶
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
.
Windows and Java-Style Option Prefixes¶
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
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.
Option Transformation Order¶
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.
You can call convert
multiple times, but you can only apply one transform of each other
type. So option().default("").multiple()
is invalid, 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) }