Skip to content

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

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 OutputStreams

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