Skip to content

Options

Options are named command-line parameters that users specify with flags like --verbose or -f filename. They form the backbone of most CLI interfaces.

Basic Option Creation

Use the option() function to create command-line options:

class MyApp : Arguments() {
    // Basic string option (nullable)
    val name by option("--name", "-n")

    // Multiple switches for the same option
    val help by option("--help", "-h", "-?")
}

Option Types

String Options

class MyApp : Arguments() {
    // Nullable string (default)
    val name by option("--name", "-n")                    // String?

    // Non-nullable with default
    val format by option("--format", "-f").default("json")  // String

    // Required string
    val input by option("--input").required()             // String
}

Numeric Options

class MyApp : Arguments() {
    // Integer options
    val count by option("--count").int()                  // Int?
    val port by option("--port").int().default(8080)     // Int

    // Other numeric types
    val rate by option("--rate").float()                 // Float?
    val size by option("--size").long()                  // Long?
    val precision by option("--precision").double()      // Double?
}

Boolean Options

class MyApp : Arguments() {
    // Basic boolean flag
    val verbose by option("--verbose", "-v").bool()      // Boolean?

    // Boolean with default
    val debug by option("--debug").bool().default(false) // Boolean

    // Negatable boolean (generates --no-cache)
    val cache by option("--cache").bool().negatable().default(true)
}

Enum Options

enum class LogLevel { DEBUG, INFO, WARN, ERROR }

class MyApp : Arguments() {
    // Enum with case-insensitive matching
    val level by option("--level").enum<LogLevel>()      // LogLevel?

    // Enum with default
    val mode by option("--mode").enum<ProcessMode>().default(ProcessMode.AUTO)
}

Restricted String Values

class MyApp : Arguments() {
    // Restrict to specific values
    val format by option("--format").oneOf("json", "xml", "yaml")

    // Case-sensitive restriction
    val protocol by option("--protocol").oneOf("HTTP", "HTTPS", ignoreCase = false)
}

Option Configuration

Help Text

class MyApp : Arguments() {
    val verbose by option("--verbose", "-v").bool().default(false)
        .help("Enable verbose output with detailed information")

    val port by option("--port").int().default(8080)
        .help("Port number for the server (1-65535)")
}

Hidden Options

class MyApp : Arguments() {
    // Hidden from help output (useful for debug options)
    val debugMode by option("--debug-mode").bool().hidden()

    // Secret configuration
    val apiKey by option("--api-key").fromEnv("API_KEY").hidden()
}

Value Requirements

class MyApp : Arguments() {
    // Option can work as flag or accept value
    val debugLevel by option("--debug-level")
        .int()
        .requiresValue(false)    // --debug-level or --debug-level 3

    // Always requires a value
    val output by option("--output")
        .requiresValue(true)     // Must be --output filename
}

Option Behavior

Short Option Clustering

When clusterChar is enabled (default), users can combine short options:

# These are equivalent
myapp -v -f json -o output.txt
myapp -vfo output.txt json     # Clustered short options

Value Separators

Options support multiple ways to specify values:

# All equivalent (with default separators = and :)
myapp --format json
myapp --format=json
myapp --format:json

Boolean Option Variations

class MyApp : Arguments() {
    val cache by option("--cache").bool().negatable()
}
# User can specify
myapp --cache         # Sets cache = true
myapp --no-cache      # Sets cache = false (auto-generated)

Custom Negation Prefix

class MyApp : Arguments() {
    val compress by option("--compress").bool()
        .negatable("disable-")    // Generates --disable-compress
}

Advanced Option Features

Callbacks

class MyApp : Arguments() {
    val verbose by option("--verbose").bool().default(false)
        .onValue { isVerbose ->
            if (isVerbose) {
                println("Verbose mode enabled")
                configureLogging(LogLevel.DEBUG)
            }
        }
}

Domain Restrictions

class MyApp : Arguments() {
    val buildDomain by domain("build")
    val testDomain by domain("test")

    // Only available in build domain
    val optimize by option("--optimize").bool()
        .onlyInDomains(::buildDomain)

    // Available in multiple domains
    val target by option("--target")
        .onlyInDomains(::buildDomain, ::testDomain)
}

Complete Option Examples

File Processing Tool

class FileProcessor : Arguments() {
    // Input/Output options
    val inputFile by option("--input", "-i").required()
        .help("Input file to process")

    val outputDir by option("--output-dir", "-o").default("./output")
        .help("Output directory for processed files")

    // Processing options
    val format by option("--format", "-f")
        .oneOf("json", "xml", "yaml", "csv")
        .default("json")
        .help("Output format")

    val compress by option("--compress", "-c").bool().default(false)
        .help("Compress output files")

    // Behavior options
    val verbose by option("--verbose", "-v").bool().default(false)
        .help("Enable verbose output")

    val maxThreads by option("--threads", "-t").int().default(1)
        .help("Maximum number of processing threads")

    // Advanced options
    val configFile by option("--config").fromEnv("PROCESSOR_CONFIG")
        .help("Configuration file path")

    val dryRun by option("--dry-run").bool().default(false)
        .help("Show what would be done without executing")

    val help by help()
    val version by version("1.0.0")
}

Development Tool

enum class BuildTarget { DEBUG, RELEASE, PROFILE }
enum class LogLevel { TRACE, DEBUG, INFO, WARN, ERROR }

class DevTool : Arguments() {
    // Core options
    val target by option("--target", "-t").enum<BuildTarget>().default(BuildTarget.DEBUG)
        .help("Build target (debug, release, profile)")

    val logLevel by option("--log-level", "-l").enum<LogLevel>().default(LogLevel.INFO)
        .help("Set logging verbosity")

    // Feature flags
    val optimize by option("--optimize", "-O").bool().default(false)
        .help("Enable optimization (implied for release builds)")

    val parallel by option("--parallel", "-j").bool().default(false)
        .help("Enable parallel compilation")

    val incremental by option("--incremental").bool().negatable().default(true)
        .help("Enable incremental compilation (use --no-incremental to disable)")

    // Output options
    val quiet by option("--quiet", "-q").bool().default(false)
        .help("Suppress non-essential output")

    val outputDir by option("--output-dir").default("build/")
        .help("Output directory for build artifacts")

    // Advanced options
    val memoryLimit by option("--memory").int()
        .help("Memory limit in MB for build process")

    val configOverride by option("--config-override").list()
        .help("Override configuration values (key=value)")

    val help by help()
    val version by version("2.1.0")
}

Arity Options

Arity options consume exactly N values per invocation, enabling structured multi-value parsing. This is useful for options that need to accept multiple related values as a group.

Basic Arity

class MyApp : Arguments() {
    // Coordinate pair: --coords 10 20
    val coords by option("--coords").int().arity(2)          // List<Int>?

    // 3D point: --point 1.0 2.5 3.1
    val point by option("--point").float().arity(3)          // List<Float>?

    // Required arity option
    val center by option("--center").int().arity(2).required()  // List<Int>
}

Usage Examples

# Single coordinate pair
myapp --coords 10 20
# Result: coords = [10, 20]

# Multiple 3D points
myapp --point 0 0 0 --point 1 1 1 --point 2 2 2
# Error: arity(3) with single invocation expects exactly 3 values
# Correct: --point 0 0 0  (this creates one 3D point)

Arity with Collections

Combine arity with collections for powerful multi-value parsing:

class GeometryApp : Arguments() {
    // Multiple line segments: each --line takes 4 values (x1, y1, x2, y2)
    val lines by option("--line").int().list().arity(4)      // List<List<Int>>?

    // Unique triangles: each --triangle takes 3 points (x, y, z)
    val triangles by option("--triangle").float().set().arity(3)  // Set<List<Float>>?
}

Arity Collection Usage

# Multiple line segments
myapp --line 0 0 10 10 --line 5 5 15 15 --line 20 20 30 30
# Result: lines = [[0, 0, 10, 10], [5, 5, 15, 15], [20, 20, 30, 30]]

# Unique triangles (duplicates removed)
myapp --triangle 0 0 1 --triangle 1 1 2 --triangle 0 0 1
# Result: triangles = {[0.0, 0.0, 1.0], [1.0, 1.0, 2.0]}  # duplicate removed

Arity Validation

class MathApp : Arguments() {
    // Validate each coordinate pair
    val coords by option("--coords").int().list().arity(2)
        .validate("coordinates must be positive") { pair ->
            pair.all { it > 0 }
        }

    // Validate the entire collection of ranges
    val ranges by option("--range").int().list().arity(2)
        .validate("range must be valid (start < end)") { range ->
            range[0] < range[1]
        }
        .validateCollection("ranges must not overlap") { ranges ->
            ranges.sortedBy { it[0] }.zipWithNext().all { (a, b) -> a[1] <= b[0] }
        }
}

Arity Constraints

class AdvancedApp : Arguments() {
    // At least 2 rectangles required
    val rectangles by option("--rect").int().list().arity(4).atLeast(2)  // List<List<Int>>

    // Default coordinate
    val origin by option("--origin").int().arity(2).default(listOf(0, 0))  // List<Int>
}

Arity Limitations

Arity options have some constraints:

// These combinations are NOT allowed and will throw ConfigError:
// val invalid1 by option("--test").requiresValue(false).arity(2)  // arity requires values
// val invalid2 by option("--test").fromEnv("VAR").arity(2)        // arity incompatible with env

Best Practices

1. Provide Both Long and Short Forms

// Good: Provides both convenience and clarity
val verbose by option("--verbose", "-v")
val output by option("--output", "-o")

// Acceptable for less common options
val configFile by option("--config")

2. Use Meaningful Default Values

// Good: Sensible defaults
val port by option("--port").int().default(8080)
val format by option("--format").default("json")

// Consider: Are null defaults appropriate?
val optionalFile by option("--optional-file")  // String? = null

3. Write Clear Help Text

// Good: Clear, actionable help
val threads by option("--threads", "-t").int().default(4)
    .help("Number of worker threads (1-32, default: 4)")

// Avoid: Vague or unhelpful text
val threads by option("--threads").int().help("Thread setting")
class MyApp : Arguments() {
    // Input/Output group
    val inputFile by option("--input", "-i")
    val outputDir by option("--output", "-o")
    val format by option("--format", "-f")

    // Behavior group
    val verbose by option("--verbose", "-v")
    val quiet by option("--quiet", "-q")
    val dryRun by option("--dry-run")

    // Performance group
    val threads by option("--threads", "-t")
    val memoryLimit by option("--memory")
}

Options are the primary way users interact with your CLI tool. Design them to be intuitive, well-documented, and consistent with user expectations.