CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-github-ajalt--clikt-jvm

Multiplatform command line interface parsing for Kotlin

Pending
Overview
Eval results
Files

parameter-groups.mddocs/

Parameter Groups

Organize related parameters into groups with mutual exclusion, co-occurrence, and choice-based selection patterns.

Capabilities

Basic Option Groups

Group related options together for better help organization and validation.

/**
 * Basic option group for organizing related parameters
 * @param name Group name for help display
 * @param help Help text for the group
 */
open class OptionGroup(val name: String? = null, val help: String? = null) {
    /** Access to parent command */
    protected val command: CliktCommand get() = TODO()
    
    /** Create option in this group */
    fun 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
    ): RawOption
}

Usage Examples:

class DatabaseOptions : OptionGroup(name = "Database Options", help = "Database connection settings") {
    val host by option("--db-host", help = "Database host").default("localhost")
    val port by option("--db-port", help = "Database port").int().default(5432)
    val username by option("--db-user", help = "Database username").required()
    val password by option("--db-password", help = "Database password").required()
}

class LoggingOptions : OptionGroup(name = "Logging Options") {
    val level by option("--log-level", help = "Logging level")
        .choice("debug", "info", "warn", "error").default("info")
    val file by option("--log-file", help = "Log file path")
}

class MyCommand : CliktCommand() {
    private val database by DatabaseOptions()
    private val logging by LoggingOptions()
    
    override fun run() {
        echo("Connecting to ${database.host}:${database.port}")
        echo("Log level: ${logging.level}")
    }
}

Mutually Exclusive Options

Create groups where only one option can be specified at a time.

/**
 * Create mutually exclusive option group
 * @param options Options that are mutually exclusive
 */
fun ParameterHolder.mutuallyExclusiveOptions(
    vararg options: OptionDelegate<*>
): MutuallyExclusiveOptions

/**
 * Mutually exclusive option group
 */
class MutuallyExclusiveOptions : ParameterGroup {
    val option: Option?
}

Usage Examples:

class MyCommand : CliktCommand() {
    // Individual options
    private val verbose by option("-v", "--verbose", help = "Verbose output").flag()
    private val quiet by option("-q", "--quiet", help = "Quiet output").flag()
    private val debug by option("-d", "--debug", help = "Debug output").flag()
    
    // Make them mutually exclusive
    private val outputMode by mutuallyExclusiveOptions(verbose, quiet, debug)
    
    override fun run() {
        when {
            verbose -> echo("Verbose mode enabled")
            quiet -> echo("Quiet mode enabled")
            debug -> echo("Debug mode enabled")
            else -> echo("Default output mode")
        }
    }
}

// Alternative approach with enum
class MyCommand2 : CliktCommand() {
    enum class OutputMode { VERBOSE, QUIET, DEBUG }
    
    private val verbose by option("-v", "--verbose", help = "Verbose output")
        .flag().convert { OutputMode.VERBOSE }
    private val quiet by option("-q", "--quiet", help = "Quiet output")
        .flag().convert { OutputMode.QUIET }
    private val debug by option("-d", "--debug", help = "Debug output")
        .flag().convert { OutputMode.DEBUG }
    
    private val outputMode by mutuallyExclusiveOptions(verbose, quiet, debug)
}

Co-occurring Option Groups

Create groups where all options must be specified together.

/**
 * Mark option group as co-occurring (all options required together)
 */
fun <T : OptionGroup> T.cooccurring(): ParameterGroupDelegate<T?>

Usage Examples:

class AuthOptions : OptionGroup(name = "Authentication") {
    val username by option("--username", help = "Username").required()
    val password by option("--password", help = "Password").required()
}

class TlsOptions : OptionGroup(name = "TLS Configuration") {
    val certFile by option("--cert", help = "Certificate file").required()
    val keyFile by option("--key", help = "Private key file").required()
    val caFile by option("--ca", help = "CA certificate file")
}

class MyCommand : CliktCommand() {
    // Either provide both username and password, or neither
    private val auth by AuthOptions().cooccurring()
    
    // Either provide cert and key (and optionally CA), or none
    private val tls by TlsOptions().cooccurring()
    
    override fun run() {
        auth?.let { authOptions ->
            echo("Authenticating as ${authOptions.username}")
        } ?: echo("No authentication")
        
        tls?.let { tlsOptions ->
            echo("Using TLS with cert: ${tlsOptions.certFile}")
        } ?: echo("No TLS")
    }
}

Choice Groups

Create groups where the user selects one of several predefined option sets.

/**
 * Create choice group with predefined option sets
 * @param choices Map of choice names to values
 */
fun <T : Any> CliktCommand.groupChoice(
    vararg choices: Pair<String, T>
): ParameterGroupDelegate<T?>

Usage Examples:

// Define different deployment configurations
sealed class DeploymentConfig {
    data class Development(val debugPort: Int = 8000) : DeploymentConfig()
    data class Staging(val replicas: Int = 2) : DeploymentConfig()
    data class Production(val replicas: Int = 5, val healthCheck: Boolean = true) : DeploymentConfig()
}

class MyCommand : CliktCommand() {
    private val deployment by groupChoice(
        "--dev" to DeploymentConfig.Development(),
        "--staging" to DeploymentConfig.Staging(),
        "--prod" to DeploymentConfig.Production()
    )
    
    override fun run() {
        when (val config = deployment) {
            is DeploymentConfig.Development -> {
                echo("Development deployment with debug port ${config.debugPort}")
            }
            is DeploymentConfig.Staging -> {
                echo("Staging deployment with ${config.replicas} replicas")
            }
            is DeploymentConfig.Production -> {
                echo("Production deployment with ${config.replicas} replicas, health check: ${config.healthCheck}")
            }
            null -> {
                echo("No deployment configuration specified")
            }
        }
    }
}

// More complex choice groups with actual option groups
class DatabaseGroup : OptionGroup() {
    val host by option("--db-host").default("localhost")
    val port by option("--db-port").int().default(5432)
}

class FileGroup : OptionGroup() {
    val path by option("--file-path").required()
    val format by option("--file-format").choice("json", "xml").default("json")
}

class MyCommand2 : CliktCommand() {
    private val source by groupChoice(
        "--database" to DatabaseGroup(),
        "--file" to FileGroup()
    )
    
    override fun run() {
        when (val config = source) {
            is DatabaseGroup -> {
                echo("Using database at ${config.host}:${config.port}")
            }
            is FileGroup -> {
                echo("Using file ${config.path} with format ${config.format}")
            }
            null -> {
                echo("No data source specified")
            }
        }
    }
}

Advanced Group Patterns

Combine multiple group types for complex parameter relationships.

/**
 * Parameter group interface
 */
interface ParameterGroup {
    val groupName: String?
    val groupHelp: String?
}

/**
 * Parameter group delegate interface
 */
interface ParameterGroupDelegate<out T> {
    operator fun getValue(thisRef: ParameterHolder, property: KProperty<*>): T
}

Usage Examples:

// Complex nested group structure
class ServerConfig : OptionGroup(name = "Server Configuration") {
    val host by option("--host").default("0.0.0.0")
    val port by option("--port").int().default(8080)
}

class DatabaseConfig : OptionGroup(name = "Database Configuration") {
    val url by option("--db-url").required()
    val poolSize by option("--db-pool-size").int().default(10)
}

class CacheConfig : OptionGroup(name = "Cache Configuration") {
    val enabled by option("--cache-enabled").flag()
    val ttl by option("--cache-ttl").int().default(3600)
}

class MyCommand : CliktCommand() {
    // Required server config
    private val server by ServerConfig()
    
    // Either database or cache can be optional, but not both
    private val database by DatabaseConfig().cooccurring()
    private val cache by CacheConfig().cooccurring()
    
    override fun run() {
        require(database != null || cache != null) {
            "Either database or cache configuration must be provided"
        }
        
        echo("Server: ${server.host}:${server.port}")
        
        database?.let { db ->
            echo("Database: ${db.url} (pool size: ${db.poolSize})")
        }
        
        cache?.let { c ->
            if (c.enabled) {
                echo("Cache enabled with TTL: ${c.ttl}s")
            } else {
                echo("Cache disabled")
            }
        }
    }
}

// Validation across groups
class MyAdvancedCommand : CliktCommand() {
    private val inputFile by option("--input").file()
    private val outputFile by option("--output").file()
    
    class ProcessingOptions : OptionGroup(name = "Processing") {
        val threads by option("--threads").int().default(1)
        val batchSize by option("--batch-size").int().default(100)
    }
    
    private val processing by ProcessingOptions()
    
    override fun run() {
        // Cross-group validation
        if (inputFile == outputFile) {
            echo("Warning: Input and output files are the same", err = true)
        }
        
        if (processing.threads > Runtime.getRuntime().availableProcessors()) {
            echo("Warning: Thread count exceeds available processors", err = true)
        }
        
        echo("Processing with ${processing.threads} threads, batch size ${processing.batchSize}")
    }
}

Group Validation and Error Handling

/**
 * Exception thrown when mutually exclusive options are used together
 */
class MutuallyExclusiveGroupException(val names: List<String>) : UsageError()

/**
 * Custom group validation
 */
abstract class ParameterGroup {
    /** Validate group after all parameters are parsed */
    protected open fun validate() {}
}

Usage Examples:

class CustomValidationGroup : OptionGroup(name = "Custom Validation") {
    val minValue by option("--min").int()
    val maxValue by option("--max").int()
    
    // Custom validation logic
    override fun validate() {
        val min = minValue
        val max = maxValue
        
        if (min != null && max != null && min > max) {
            throw UsageError("Minimum value ($min) cannot be greater than maximum value ($max)")
        }
    }
}

class MyCommand : CliktCommand() {
    private val range by CustomValidationGroup()
    
    override fun run() {
        echo("Range: ${range.minValue} to ${range.maxValue}")
    }
}

Install with Tessl CLI

npx tessl i tessl/maven-com-github-ajalt--clikt-jvm

docs

arguments.md

configuration-sources.md

core-commands.md

exceptions.md

index.md

options.md

parameter-groups.md

parameter-types.md

shell-completion.md

testing-utilities.md

tile.json