Skip to content

Latest commit

 

History

History
278 lines (208 loc) · 8.12 KB

parameters.md

File metadata and controls

278 lines (208 loc) · 8.12 KB

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().

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.

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.

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 option().validate() and argument().validate(). validate 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. The lambda is only called if the value is non-null.

val opt by option().int().validate {
    require(it % 2 == 0) { "value must be even" }
}

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