Multiplatform command line interface parsing for Kotlin
—
Built-in type converters for common data types including numeric types, enums, choices, and platform-specific types like files and paths.
Convert parameters to numeric types with optional range restrictions.
/** Convert to Int */
fun RawOption.int(): NullableOption<Int, Int>
fun RawArgument.int(): ProcessedArgument<Int, Int>
/** Convert to Long */
fun RawOption.long(): NullableOption<Long, Long>
fun RawArgument.long(): ProcessedArgument<Long, Long>
/** Convert to Float */
fun RawOption.float(): NullableOption<Float, Float>
fun RawArgument.float(): ProcessedArgument<Float, Float>
/** Convert to Double */
fun RawOption.double(): NullableOption<Double, Double>
fun RawArgument.double(): ProcessedArgument<Double, Double>
/** Convert to UInt (unsigned integer) */
fun RawOption.uint(): NullableOption<UInt, UInt>
fun RawArgument.uint(): ProcessedArgument<UInt, UInt>
/** Convert to ULong (unsigned long) */
fun RawOption.ulong(): NullableOption<ULong, ULong>
fun RawArgument.ulong(): ProcessedArgument<ULong, ULong>Usage Examples:
class MyCommand : CliktCommand() {
// Basic 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()
private val maxSize by option("--max-size", help = "Maximum size").ulong()
// Numeric arguments
private val count by argument(name = "COUNT", help = "Number of items").int()
private val ratio by argument(name = "RATIO", help = "Ratio value").float()
override fun run() {
echo("Port: $port")
echo("Timeout: ${timeout}s")
echo("Max size: $maxSize bytes")
echo("Count: $count")
echo("Ratio: $ratio")
}
}Restrict numeric values to specific ranges.
/**
* Restrict numeric value to a specific range
* @param range Valid range for the value
*/
fun <T : Comparable<T>> NullableOption<T, T>.restrictTo(range: ClosedRange<T>): NullableOption<T, T>
fun <T : Comparable<T>> ProcessedArgument<T, T>.restrictTo(range: ClosedRange<T>): ProcessedArgument<T, T>Usage Examples:
class MyCommand : CliktCommand() {
// Port number between 1 and 65535
private val port by option("--port", help = "Port number")
.int().restrictTo(1..65535).default(8080)
// Percentage between 0 and 100
private val confidence by option("--confidence", help = "Confidence percentage")
.double().restrictTo(0.0..100.0)
// Thread count between 1 and available processors
private val threads by argument(name = "THREADS", help = "Number of threads")
.int().restrictTo(1..Runtime.getRuntime().availableProcessors())
}Convert parameters to boolean values.
/** Convert to Boolean */
fun RawOption.boolean(): NullableOption<Boolean, Boolean>
fun RawArgument.boolean(): ProcessedArgument<Boolean, Boolean>Usage Examples:
class MyCommand : CliktCommand() {
// Boolean option (requires explicit true/false value)
private val enableFeature by option("--enable-feature", help = "Enable the feature")
.boolean().default(false)
// Boolean argument
private val shouldProcess by argument(name = "PROCESS", help = "Whether to process (true/false)")
.boolean()
override fun run() {
echo("Feature enabled: $enableFeature")
echo("Should process: $shouldProcess")
}
}Convert parameters to enum values with case-insensitive matching.
/** Convert to Enum with case-insensitive matching */
inline fun <reified T : Enum<T>> RawOption.enum(
ignoreCase: Boolean = true,
key: (T) -> String = { it.name }
): NullableOption<T, T>
inline fun <reified T : Enum<T>> RawArgument.enum(
ignoreCase: Boolean = true,
key: (T) -> String = { it.name }
): ProcessedArgument<T, T>Usage Examples:
class MyCommand : CliktCommand() {
enum class LogLevel { DEBUG, INFO, WARN, ERROR }
enum class OutputFormat { JSON, XML, YAML, CSV }
// Enum option with default
private val logLevel by option("--log-level", help = "Logging level")
.enum<LogLevel>().default(LogLevel.INFO)
// Enum argument
private val format by argument(name = "FORMAT", help = "Output format")
.enum<OutputFormat>()
// Case-sensitive enum
private val mode by option("--mode", help = "Processing mode")
.enum<ProcessingMode>(ignoreCase = false)
override fun run() {
echo("Log level: $logLevel")
echo("Format: $format")
echo("Mode: $mode")
}
}Convert parameters to values from predefined choices.
/** Convert using choice map */
fun <T : Any> RawOption.choice(
choices: Map<String, T>,
metavar: String = choices.keys.joinToString("|", "(", ")"),
ignoreCase: Boolean = false
): NullableOption<T, T>
fun <T : Any> RawArgument.choice(
choices: Map<String, T>,
ignoreCase: Boolean = false
): ProcessedArgument<T, T>
/** Convert using pair choices */
fun <T : Any> RawOption.choice(
vararg choices: Pair<String, T>,
metavar: String = choices.map { it.first }.joinToString("|", "(", ")"),
ignoreCase: Boolean = false
): NullableOption<T, T>
fun <T : Any> RawArgument.choice(
vararg choices: Pair<String, T>,
ignoreCase: Boolean = false
): ProcessedArgument<T, T>
/** Convert using string choices */
fun RawOption.choice(
vararg choices: String,
metavar: String = choices.joinToString("|", "(", ")"),
ignoreCase: Boolean = false
): NullableOption<String, String>
fun RawArgument.choice(
vararg choices: String,
ignoreCase: Boolean = false
): ProcessedArgument<String, String>Usage Examples:
class MyCommand : CliktCommand() {
// String choices
private val compression by option("--compression", help = "Compression algorithm")
.choice("none", "gzip", "bzip2", "xz").default("none")
// Choice with custom values
private val verbosity by option("-v", "--verbosity", help = "Verbosity level")
.choice(mapOf(
"quiet" to 0,
"normal" to 1,
"verbose" to 2,
"debug" to 3
)).default(1)
// Case-insensitive choices
private val protocol by argument(name = "PROTOCOL", help = "Network protocol")
.choice("http", "https", "ftp", "sftp", ignoreCase = true)
override fun run() {
echo("Compression: $compression")
echo("Verbosity: $verbosity")
echo("Protocol: $protocol")
}
}Convert parameters to file system objects (available on JVM platform only).
/** Convert to java.io.File */
fun RawOption.file(): NullableOption<File, File>
fun RawArgument.file(): ProcessedArgument<File, File>
/** Convert to java.nio.file.Path */
fun RawOption.path(): NullableOption<Path, Path>
fun RawArgument.path(): ProcessedArgument<Path, Path>Usage Examples:
class MyCommand : CliktCommand() {
// File options
private val configFile by option("--config", help = "Configuration file")
.file().default(File("config.properties"))
private val outputDir by option("--output-dir", help = "Output directory")
.path().default(Paths.get("output"))
// File arguments
private val inputFile by argument(name = "INPUT", help = "Input file to process").file()
private val backupPath by argument(name = "BACKUP", help = "Backup location").path()
override fun run() {
echo("Config file: ${configFile.absolutePath}")
echo("Output directory: ${outputDir.toAbsolutePath()}")
echo("Input file: ${inputFile.name}")
echo("Backup path: $backupPath")
}
}Convert parameters to input/output streams (available on JVM platform only).
/** Convert to InputStream */
fun RawOption.inputStream(): NullableOption<InputStream, InputStream>
fun RawArgument.inputStream(): ProcessedArgument<InputStream, InputStream>
/** Convert to OutputStream */
fun RawOption.outputStream(): NullableOption<OutputStream, OutputStream>
fun RawArgument.outputStream(): ProcessedArgument<OutputStream, OutputStream>Usage Examples:
class MyCommand : CliktCommand() {
// Stream options (stdin/stdout supported with "-")
private val input by option("--input", help = "Input stream (use '-' for stdin)")
.inputStream().default(System.`in`)
private val output by option("--output", help = "Output stream (use '-' for stdout)")
.outputStream().default(System.out)
override fun run() {
input.use { inputStream ->
output.use { outputStream ->
inputStream.copyTo(outputStream)
}
}
}
}Create custom type converters for domain-specific types.
/**
* Convert using custom conversion function
* @param metavar Placeholder for help text
* @param conversion Custom conversion function
*/
fun <T : Any> RawOption.convert(
metavar: String,
conversion: ValueConverter<String, T>
): NullableOption<T, T>
fun <T : Any> RawArgument.convert(
conversion: ArgValueConverter<String, T>
): ProcessedArgument<T, T>Usage Examples:
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.regex.Pattern
class MyCommand : CliktCommand() {
// Date conversion
private val startDate by option("--start-date", help = "Start date (YYYY-MM-DD)")
.convert("DATE") { LocalDate.parse(it) }
// DateTime conversion
private val timestamp by option("--timestamp", help = "Timestamp (ISO format)")
.convert("DATETIME") {
LocalDateTime.parse(it, DateTimeFormatter.ISO_LOCAL_DATE_TIME)
}
// URL conversion
private val apiEndpoint by option("--api-endpoint", help = "API endpoint URL")
.convert("URL") {
require(it.startsWith("http")) { "URL must start with http or https" }
java.net.URL(it)
}
// Regex pattern conversion
private val pattern by argument(name = "PATTERN", help = "Regular expression pattern")
.convert { Pattern.compile(it) }
// Custom data class conversion
data class Coordinate(val lat: Double, val lon: Double)
private val location by option("--location", help = "Location as 'lat,lon'")
.convert("LAT,LON") {
val parts = it.split(",")
require(parts.size == 2) { "Location must be in format 'lat,lon'" }
Coordinate(parts[0].toDouble(), parts[1].toDouble())
}
override fun run() {
echo("Start date: $startDate")
echo("Timestamp: $timestamp")
echo("API endpoint: $apiEndpoint")
echo("Pattern: ${pattern.pattern()}")
echo("Location: $location")
}
}Type conversion functions receive a context object with useful properties and methods.
/**
* Context for option type conversion
*/
interface OptionTransformContext {
val context: Context
val option: Option
val name: String
/** Throw conversion error */
fun fail(message: String): Nothing
}
/**
* Context for argument type conversion
*/
interface ArgumentTransformContext {
val context: Context
val argument: Argument
val name: String
/** Throw conversion error */
fun fail(message: String): Nothing
}Usage Examples:
class MyCommand : CliktCommand() {
// Using context in conversion
private val positiveInt by option("--count", help = "Positive integer")
.convert("INT") { value ->
val parsed = value.toIntOrNull()
?: fail("'$value' is not a valid integer")
if (parsed <= 0) {
fail("Value must be positive, got $parsed")
}
parsed
}
// Access option name in conversion
private val percentage by option("--percentage", help = "Percentage value")
.convert("PERCENT") { value ->
val parsed = value.toDoubleOrNull()
?: fail("Option $name requires a number, got '$value'")
if (parsed !in 0.0..100.0) {
fail("Option $name must be between 0 and 100, got $parsed")
}
parsed
}
}Install with Tessl CLI
npx tessl i tessl/maven-com-github-ajalt--clikt-jvm