Parameters¶
Clikt supports two types of parameters: options and positional arguments. If you’re following Unix conventions with your interface, you should use options for most parameters. Options are usually optional, and arguments are frequently required.
Differences¶
Arguments have the advantage of being able to accept a variable number of values, while Options are limited to a fixed number of values. Other than that restriction, options have more capabilities than arguments.
Options can:
- Act as flags (options don’t have to take values)
- Prompt for missing input
- Load values from environment variables
In general, arguments are usually used for values like file paths or URLs, or for required values, and options are used for everything else.
Parameter Names¶
Both options and arguments can infer their names (or the metavar in the case of arguments) from the name of the property. You can also specify the names manually. Options can have any number of names, where arguments only have a single metavar.
class Cli : CliktCommand() {
val inferredOpt by option()
val inferred by argument()
val explicitOpt by option("-e", "--explicit")
val explicitArg by argument("<explicit>")
override fun run() = Unit
}
Usage: cli [<options>] <inferred> <explicit>
Options:
--inferred-opt <text>
-e, --explicit <text>
-h, --help Show this message and exit
Parameter Types¶
Both options and arguments can convert the String that the user inputs to other types.
Types work by transforming the return value of the property delegate. By default, parameters have a string type:
val opt: String? by option(help="an option")
val arg: String by argument(help="an argument")
To convert the input to an integer, for example, use the int()
extension function:
val opt: Int? by option(help="an option").int()
val arg: Int by argument(help="an argument").int()
Built-In Types¶
There are a number of built-in types that can be applied to options and arguments.
Int and Long¶
option().int()
andargument().int()
option().long()
andargument().long()
option().uint()
andargument().uint()
option().ulong()
andargument().ulong()
By default, any value that fits in the integer type is accepted.
You can restrict the values to a range with restrictTo()
,
which allows you to either clamp the input to the range,
or fail with an error if the input is outside the range.
Float and Double¶
As with integers, you can restrict the input to a range with restrictTo()
.
Boolean¶
You will normally want to use flags for boolean options. Explicit boolean value
conversion is also available if you need, for example, a tri-state Boolean?
parameter.
Choice¶
You can restrict the values to a set of values, and optionally map the input to a new value. For example, to create an option that only accepts the value “a” or “b”:
val opt: String? by option().choice("a", "b")
You can also convert the restricted set of values to a new type:
val color: Int? by argument().choice("red" to 1, "green" to 2)
Choice parameters accept values that are case-sensitive by default. This can be configured by
passing ignoreCase = true
.
Enum¶
Like choice
, but uses the values of an enum type.
enum class Color { RED, GREEN }
val color: Color? by option().enum<Color>()
Enum parameters accept case-insensitive values by default. This can be configured by passing
ignoreCase = false
.
You can also pass a lambda to map the enum values to option names.
val color: Color? by option().enum<Color> { it.name.lowercase() }
File paths¶
These conversion functions take extra parameters that allow you to require that values are file paths that have certain attributes, such as that they are directories, or they are writable files.
File path InputStream
and OutputStream
s¶
option().inputStream()
andargument().inputStream()
option().outputStream()
andargument().outputStream()
Like file and path, these conversions take file path values, but expose them as open streams for
reading or writing. They support the unix convention of passing -
to specify stdin or stdout
rather than a file on the filesystem. You’ll need to close the streams yourself. You can also use
stdin or stdout as their default values.
If you need to check if one of these streams is pointing to a file rather than stdin or stdout, you
can use isCliktParameterDefaultStdin
or isCliktParameterDefaultStdout
.
Custom Types¶
You can convert parameter values to a custom type by using
argument().convert()
and option().convert()
.
These functions take a lambda that converts the input String
to any type.
If the parameter takes multiple values, or an option appears multiple times in argv
,
the conversion lambda is called once for each value.
Any errors that are thrown from the lambda are automatically caught and
a usage message is printed to the user. If you need to trigger
conversion failure, you can use fail("error message")
instead of
raising an exception.
For example, you can create an option of type BigDecimal
like this:
class Cli: CliktCommand() {
val opt by option().convert { it.toBigDecimal() }
override fun run() = echo("opt=$opt")
}
$ ./cli --opt=1.5
opt=1.5
$ ./cli --opt=foo
Usage: cli [<options>]
Error: Invalid value for "--opt": For input string: "foo"
Metavars¶
You can also pass option().convert()
a metavar
that will be printed in the help page instead of the default of value
.
We can modify the above example to use a metavar and an explicit error message:
class Cli: CliktCommand() {
val opt by option(help="a real number").convert("float") {
it.toBigDecimalOrNull() ?: fail("A real number is required")
}
override fun run() = echo("opt=$opt")
}
$ ./cli --opt=foo
Usage: cli [<options>]
Error: Invalid value for "--opt": A real number is required
$ ./cli --help
Usage: cli [<options>]
Options:
--opt <float> a real number
-h, --help Show this message and exit
Chaining¶
You can call convert
more than once on the same parameter. This allows you to reuse existing
conversion functions. For example, you could automatically read the text of a file parameter.
class FileReader: CliktCommand() {
val file: String by argument()
.file(mustExist=true, canBeDir=false)
.convert { it.readText() }
override fun run() {
echo("Your file contents: $file")
}
}
$ echo 'some text' > myfile.txt
$ ./filereader ./myfile.txt
Your file contents: some text
Parameter Validation¶
After converting a value to a new type, you can perform additional validation on the converted value
with check()
and validate()
(or the argument
equivalents).
check()
¶
check()
is similar the stdlib function of the same name: it takes
lambda that returns a boolean to indicate if the parameter value is valid or not, and reports an
error if it returns false. The lambda is only called if the parameter value is non-null.
class Tool : CliktCommand() {
val number by option(help = "An even number").int()
.check("value must be even") { it % 2 == 0 }
override fun run() {
echo("number=$number")
}
}
$ ./tool --number=2
number=2
$ ./tool
number=null
$ ./tool --number=1
Usage: tool [<options>]
Error: invalid value for --number: value must be even
validate()
¶
For more complex validation, you can use validate()
. This function takes a lambda
that returns nothing, but can call fail("error message")
if the value is invalid. You can also
call require()
, which will fail if the provided expression is false. Like check
, the lambda is
only called if the value is non-null.
The lambdas you pass to validate
are called after the values for all options and arguments have
been set, so (unlike in transforms) you can reference other parameters:
class Tool : CliktCommand() {
val number by option().int().default(0)
val biggerNumber by option().int().validate {
require(it > number) {
"--bigger-number must be bigger than --number"
}
}
override fun run() {
echo("number=$number, biggerNumber=$biggerNumber")
}
}
$ ./tool --number=1
number=1, biggerNumber=null
$ ./tool --number=1 --bigger-number=0
Usage: tool [<options>]
Error: --bigger-number must be bigger than --number