Multiplatform command line interface parsing for Kotlin
—
Declarative option parsing with type conversion, validation, and default values. Options are declared as delegated properties with a fluent API for configuration.
Create options using the option() function with support for multiple names, help text, environment variables, and completion.
/**
* Create an option
* @param names Option names (e.g., "-v", "--verbose")
* @param help Help text for the option
* @param metavar Placeholder for option value in help
* @param hidden Hide option from help
* @param helpTags Extra help formatter information
* @param envvar Environment variable name
* @param valueSourceKey Key for value source lookup
* @param completionCandidates Completion candidates
*/
fun CliktCommand.option(
vararg names: String,
help: String = "",
metavar: String? = null,
hidden: Boolean = false,
helpTags: Map<String, String> = emptyMap(),
envvar: String? = null,
valueSourceKey: String? = null,
completionCandidates: CompletionCandidates? = null
): RawOptionUsage Examples:
class MyCommand : CliktCommand() {
// Basic option
private val verbose by option("-v", "--verbose", help = "Enable verbose output")
// Option with environment variable
private val apiKey by option("--api-key", envvar = "API_KEY", help = "API key")
// Hidden option
private val debug by option("--debug", hidden = true, help = "Debug mode")
}Convert option values to specific types with built-in converters.
/**
* Convert option value using custom conversion function
*/
inline fun <InT : Any, ValueT : Any> NullableOption<InT, InT>.convert(
metavar: String,
completionCandidates: CompletionCandidates? = null,
crossinline conversion: ValueConverter<InT, ValueT>
): NullableOption<ValueT, ValueT>
inline fun <InT : Any, ValueT : Any> NullableOption<InT, InT>.convert(
crossinline metavar: (Context) -> String,
completionCandidates: CompletionCandidates? = null,
crossinline conversion: ValueConverter<InT, ValueT>
): NullableOption<ValueT, ValueT>
/** Convert to Int */
fun RawOption.int(): NullableOption<Int, Int>
/** Convert to Long */
fun RawOption.long(): NullableOption<Long, Long>
/** Convert to Float */
fun RawOption.float(): NullableOption<Float, Float>
/** Convert to Double */
fun RawOption.double(): NullableOption<Double, Double>
/** Convert to Boolean */
fun RawOption.boolean(): NullableOption<Boolean, Boolean>
/** Convert to Enum */
fun <T : Enum<T>> RawOption.enum(): NullableOption<T, T>
/** Convert using choice map */
fun <T : Any> RawOption.choice(choices: Map<String, T>): NullableOption<T, T>
/** Convert using string choices */
fun RawOption.choice(vararg choices: String): NullableOption<String, String>Usage Examples:
class MyCommand : CliktCommand() {
// Numeric options
private val port by option("--port", help = "Port number").int().default(8080)
private val timeout by option("--timeout", help = "Timeout in seconds").double()
// Enum option
enum class LogLevel { DEBUG, INFO, WARN, ERROR }
private val logLevel by option("--log-level").enum<LogLevel>().default(LogLevel.INFO)
// Choice option
private val format by option("--format").choice("json", "xml", "yaml").default("json")
// Custom conversion
private val date by option("--date").convert("DATE") { LocalDate.parse(it) }
}Process option values with defaults, validation, and multiple value handling.
/** Provide default value */
fun <T : Any> NullableOption<T, T>.default(value: T): OptionDelegate<T>
/** Provide lazy default value */
fun <T : Any> NullableOption<T, T>.defaultLazy(value: () -> T): OptionDelegate<T>
/** Mark option as required */
fun <T : Any> NullableOption<T, T>.required(): OptionDelegate<T>
/** Accept multiple values */
fun <T : Any> NullableOption<T, T>.multiple(): OptionDelegate<List<T>>
/** Convert multiple values to Set */
fun <T : Any> OptionDelegate<List<T>>.unique(): OptionDelegate<Set<T>>
/** Accept exactly 2 values */
fun <T : Any> NullableOption<T, T>.pair(): NullableOption<Pair<T, T>, T>
/** Accept exactly 3 values */
fun <T : Any> NullableOption<T, T>.triple(): NullableOption<Triple<T, T, T>, T>Usage Examples:
class MyCommand : CliktCommand() {
// Default values
private val host by option("--host").default("localhost")
private val port by option("--port").int().default(8080)
// Lazy default
private val timestamp by option("--timestamp").defaultLazy { Instant.now().toString() }
// Required option
private val apiKey by option("--api-key").required()
// Multiple values
private val includes by option("--include").multiple()
private val tags by option("--tag").multiple().unique()
// Paired values
private val coordinates by option("--coord").int().pair()
override fun run() {
echo("Host: $host:$port")
echo("Includes: $includes")
echo("Coordinates: $coordinates")
}
}Validate option values with custom validators and built-in checks.
/**
* Validate option value
* @param validator Custom validation function
*/
fun <T> NullableOption<T, T>.validate(validator: OptionValidator<T>): NullableOption<T, T>
/**
* Check option value with boolean condition
* @param message Error message if check fails
* @param validator Boolean check function
*/
fun <T> NullableOption<T, T>.check(
message: String,
validator: (T) -> Boolean
): NullableOption<T, T>Usage Examples:
class MyCommand : CliktCommand() {
// Custom validation
private val port by option("--port").int().validate {
require(it in 1..65535) { "Port must be between 1 and 65535" }
}
// Boolean check
private val percentage by option("--percentage").int().check("Must be 0-100") {
it in 0..100
}
// Multiple validations
private val email by option("--email")
.check("Must contain @") { "@" in it }
.check("Must end with .com") { it.endsWith(".com") }
}Special option types for common CLI patterns.
/** Boolean flag option (no value) */
fun RawOption.flag(default: Boolean = false): OptionDelegate<Boolean>
/** Count option occurrences */
fun RawOption.counted(): OptionDelegate<Int>
/** Switch option with choices */
fun <T> RawOption.switch(choices: Map<String, T>): OptionDelegate<T?>
/** Version option that prints version and exits */
fun CliktCommand.versionOption(
version: String,
names: Set<String> = setOf("-V", "--version"),
help: String = "Show the version and exit",
message: String = version
): UnitUsage Examples:
class MyCommand : CliktCommand() {
// Flag option
private val verbose by option("-v", "--verbose", help = "Verbose output").flag()
// Counted option
private val verbosity by option("-v", "--verbose", help = "Increase verbosity").counted()
// Switch option
private val mode by option("--mode").switch(
mapOf(
"--dev" to "development",
"--prod" to "production",
"--test" to "testing"
)
)
// Version option
init {
versionOption("1.0.0")
}
override fun run() {
if (verbose) echo("Verbose mode enabled")
echo("Verbosity level: $verbosity")
echo("Mode: $mode")
}
}Options that are processed before normal parameter parsing.
/**
* Create eager option that processes before normal parsing
*/
fun CliktCommand.eagerOption(
vararg names: String,
help: String = "",
hidden: Boolean = false,
helpTags: Map<String, String> = emptyMap()
): EagerOption
abstract class EagerOption : Option {
/** Process the option value */
abstract fun process(context: Context, value: String)
}/**
* Base option interface
*/
interface Option {
/** A name representing the values for this option that can be displayed to the user */
fun metavar(context: Context): String?
/** The description of this option, usually a single line */
fun optionHelp(context: Context): String
/** The names that can be used to invoke this option */
val names: Set<String>
/** Names that can be used for a secondary purpose, like disabling flag options */
val secondaryNames: Set<String>
/** The min and max number of values that must be given to this option */
val nvalues: IntRange
/** If true, this option should not appear in help output */
val hidden: Boolean
/** Extra information about this option to pass to the help formatter */
val helpTags: Map<String, String>
/** Optional set of strings to use when the user invokes shell autocomplete */
val completionCandidates: CompletionCandidates
/** Optional explicit key to use when looking this option up from a ValueSource */
val valueSourceKey: String?
/** If true, this option can be specified without a name e.g. `-2` instead of `-o2` */
val acceptsNumberValueWithoutName: Boolean
/** If true, the presence of this option will halt parsing immediately */
val eager: Boolean
/** If false, invocations must be of the form `--foo=1` or `-f1` */
val acceptsUnattachedValue: Boolean
/** Information about this option for the help output */
fun parameterHelp(context: Context): HelpFormatter.ParameterHelp.Option?
/** Called after this command's argv is parsed to transform and store the option's value */
fun finalize(context: Context, invocations: List<Invocation>)
/** Called after all parameters have been finalized to perform validation */
fun postValidate(context: Context)
}
/**
* Option property delegate interface
*/
interface OptionDelegate<T> : GroupableOption, ReadOnlyProperty<ParameterHolder, T>, PropertyDelegateProvider<ParameterHolder, ReadOnlyProperty<ParameterHolder, T>> {
/** The value for this option */
val value: T
/** Implementations must call ParameterHolder.registerOption */
override operator fun provideDelegate(
thisRef: ParameterHolder,
property: KProperty<*>
): ReadOnlyProperty<ParameterHolder, T>
override fun getValue(thisRef: ParameterHolder, property: KProperty<*>): T = value
}
/**
* Raw unprocessed option
*/
interface RawOption : Option
/**
* Option that may have null value
*/
interface NullableOption<out T, ValueT> : Option
/**
* Option with processed values
*/
interface OptionWithValues<ValueT, EachT, AllT> : Option
/**
* Flag option for boolean values
*/
interface FlagOption : Option
/**
* Eager option processed before normal parsing
*/
abstract class EagerOption : Option {
abstract fun process(context: Context, value: String)
}typealias ValueConverter<InT, ValueT> = OptionTransformContext.(InT) -> ValueT
typealias OptionValidator<T> = OptionTransformContext.(T) -> UnitInstall with Tessl CLI
npx tessl i tessl/maven-com-github-ajalt--clikt-jvm