Multiplatform command line interface parsing for Kotlin
—
Exception classes for command line processing errors and program flow control. Clikt provides a comprehensive set of exceptions that are automatically caught and formatted when using CliktCommand.main().
Core exception hierarchy for CLI error handling.
/**
* Base exception for command line processing errors
* @param message Error message
* @param cause Underlying cause
* @param statusCode Exit code (default 1)
* @param printError Whether to print to stderr (default true)
*/
open class CliktError(
message: String? = null,
cause: Exception? = null,
val statusCode: Int = 1,
val printError: Boolean = true
) : RuntimeException(message, cause)
/**
* Interface for CliktErrors that have a context attached
*/
interface ContextCliktError {
/** The context of the command that raised this error */
var context: Context?
}
/**
* Base class for user errors
* @param message Error message
* @param paramName Parameter name that caused the error
* @param statusCode Exit code (default 1)
*/
open class UsageError(
message: String?,
var paramName: String? = null,
statusCode: Int = 1
) : CliktError(message, statusCode = statusCode), ContextCliktError {
constructor(message: String, argument: Argument, statusCode: Int = 1)
constructor(message: String, option: Option, statusCode: Int = 1)
constructor(argument: Argument, statusCode: Int = 1)
constructor(option: Option, statusCode: Int = 1)
/** Format the error message for display */
open fun formatMessage(localization: Localization, formatter: ParameterFormatter): String
override var context: Context?
}Exceptions used to control program flow and output.
/**
* Exception that indicates the command's help should be printed
* @param context Command context
* @param error Whether to print to stderr (default false)
* @param statusCode Exit code (default 0)
*/
class PrintHelpMessage(
override var context: Context?,
val error: Boolean = false,
statusCode: Int = 0
) : CliktError(printError = false, statusCode = statusCode), ContextCliktError
/**
* Exception that indicates a message should be printed
* @param message Message to print
* @param statusCode Exit code (default 0)
* @param printError Whether to print to stderr (default false)
*/
open class PrintMessage(
message: String,
statusCode: Int = 0,
printError: Boolean = false
) : CliktError(message, statusCode = statusCode, printError = printError)
/**
* Indicate that the program finished in a controlled manner
* @param statusCode Exit code
*/
open class ProgramResult(statusCode: Int) : CliktError(statusCode = statusCode)
/**
* Internal error that signals Clikt to abort
*/
class Abort : ProgramResult(statusCode = 1)
/**
* Exception that indicates shell completion code should be printed
*/
class PrintCompletionMessage(message: String) : PrintMessage(message, statusCode = 0)Specific exceptions for parameter validation and parsing errors.
/**
* Multiple usage errors occurred
* @param errors List of usage errors
*/
class MultiUsageError(
val errors: List<UsageError>
) : UsageError(null, statusCode = errors.first().statusCode) {
companion object {
/** Build MultiUsageError from error list, or return single error/null */
fun buildOrNull(errors: List<UsageError>): UsageError?
}
override fun formatMessage(localization: Localization, formatter: ParameterFormatter): String
}
/**
* A parameter was given invalid format or type
*/
class BadParameterValue : UsageError {
constructor(message: String)
constructor(message: String, argument: Argument)
constructor(message: String, option: Option)
constructor(message: String, option: Option, name: String)
override fun formatMessage(localization: Localization, formatter: ParameterFormatter): String
}
/**
* A required option was not provided
* @param option The missing option
*/
class MissingOption(option: Option) : UsageError(option) {
override fun formatMessage(localization: Localization, formatter: ParameterFormatter): String
}
/**
* A required argument was not provided
* @param argument The missing argument
*/
class MissingArgument(argument: Argument) : UsageError(argument) {
override fun formatMessage(localization: Localization, formatter: ParameterFormatter): String
}
/**
* A subcommand was provided that does not exist
* @param paramName The invalid subcommand name
* @param possibilities List of valid subcommand names
*/
class NoSuchSubcommand(
paramName: String,
private val possibilities: List<String> = emptyList()
) : UsageError(null, paramName) {
override fun formatMessage(localization: Localization, formatter: ParameterFormatter): String
}
/**
* An option was provided that does not exist
* @param paramName The invalid option name
* @param possibilities List of valid option names
*/
class NoSuchOption(
paramName: String,
private val possibilities: List<String> = emptyList()
) : UsageError(null, paramName) {
override fun formatMessage(localization: Localization, formatter: ParameterFormatter): String
}
/**
* An option was supplied with incorrect number of values
* @param minValues Expected minimum number of values
* @param paramName The option name
*/
class IncorrectOptionValueCount(
private val minValues: Int,
paramName: String
) : UsageError(null, paramName) {
constructor(option: Option, paramName: String)
override fun formatMessage(localization: Localization, formatter: ParameterFormatter): String
}
/**
* An argument was supplied with incorrect number of values
* @param nvalues Expected number of values
* @param argument The argument
*/
class IncorrectArgumentValueCount(
val nvalues: Int,
argument: Argument
) : UsageError(argument) {
constructor(argument: Argument)
override fun formatMessage(localization: Localization, formatter: ParameterFormatter): String
}
/**
* Multiple mutually exclusive options were supplied
* @param names List of conflicting option names
*/
class MutuallyExclusiveGroupException(
val names: List<String>
) : UsageError(null) {
override fun formatMessage(localization: Localization, formatter: ParameterFormatter): String
}Exceptions related to file processing and configuration.
/**
* A required configuration file was not found
* @param filename The missing file name
*/
class FileNotFound(
val filename: String
) : UsageError(null) {
override fun formatMessage(localization: Localization, formatter: ParameterFormatter): String
}
/**
* A configuration file failed to parse correctly
* @param filename The file that failed to parse
* @param message Error description
* @param lineno Optional line number where error occurred
*/
class InvalidFileFormat(
private val filename: String,
message: String,
private val lineno: Int? = null
) : UsageError(message) {
override fun formatMessage(localization: Localization, formatter: ParameterFormatter): String
}import com.github.ajalt.clikt.core.*
import com.github.ajalt.clikt.parameters.options.*
import com.github.ajalt.clikt.parameters.arguments.*
class MyCommand : CliktCommand() {
private val file by argument(help = "Input file")
private val count by option("--count", "-c", help = "Number of items").int()
override fun run() {
// Throw custom error
if (!File(file).exists()) {
throw UsageError("File not found: $file")
}
// Validation with context
count?.let { c ->
if (c <= 0) {
throw BadParameterValue("Count must be positive", ::count.option)
}
}
}
}
fun main(args: Array<String>) {
try {
MyCommand().main(args)
} catch (e: CliktError) {
// Custom error handling
println("Error: ${e.message}")
exitProcess(e.statusCode)
}
}class VersionCommand : CliktCommand() {
private val version by option("--version", help = "Show version").flag()
override fun run() {
if (version) {
// Print version and exit successfully
throw PrintMessage("MyApp version 1.0.0")
}
}
}
class AbortCommand : CliktCommand() {
private val dangerous by option("--dangerous", help = "Dangerous operation").flag()
override fun run() {
if (dangerous && !confirmDangerous()) {
// Abort without error message
throw Abort()
}
}
private fun confirmDangerous(): Boolean {
echo("This is a dangerous operation. Are you sure? (y/N)")
return readLine()?.lowercase() == "y"
}
}class DatabaseCommand : CliktCommand() {
private val host by option("--host", help = "Database host").required()
private val port by option("--port", help = "Database port").int().default(5432)
override fun run() {
// Custom validation with proper error reporting
if (port !in 1..65535) {
throw BadParameterValue(
"Port must be between 1 and 65535, got $port",
::port.option
)
}
try {
connectToDatabase(host, port)
} catch (e: SQLException) {
throw UsageError("Failed to connect to database: ${e.message}")
}
}
}class ValidateCommand : CliktCommand() {
private val files by argument(help = "Files to validate").multiple()
override fun run() {
val errors = mutableListOf<UsageError>()
for (file in files) {
try {
validateFile(file)
} catch (e: Exception) {
errors.add(BadParameterValue("Invalid file $file: ${e.message}"))
}
}
// Throw multiple errors at once
MultiUsageError.buildOrNull(errors)?.let { throw it }
echo("All files validated successfully")
}
}Install with Tessl CLI
npx tessl i tessl/maven-com-github-ajalt--clikt-jvm