Skip to content

Parsing Methods

Argos provides flexible parsing methods that allow you to handle different scenarios from simple command-line tools to complex applications with sophisticated error handling and user feedback.

Basic Parsing Methods

parse()

The most straightforward parsing method that returns null on errors:

class MyApp : Arguments() {
    val input by option("--input").required()
    val output by option("--output").default("output.txt")
    val verbose by option("--verbose").bool()

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

fun main(args: Array<String>) {
    val app = MyApp()

    app.parse(args) ?: return

    println("Input: ${app.input}")
    println("Output: ${app.output}")
    println("Verbose: ${app.verbose}")
}

parse() with Callbacks

More flexible parsing with success and error callbacks:

fun main(args: Array<String>) {
    val app = MyApp()

    val result = app.parse(args,
        onSuccess = { parsedArgs ->
            println("Successfully parsed arguments!")
            println("Input: ${parsedArgs.input}")
            println("Output: ${parsedArgs.output}")
        },
        onError = { error, args ->
            println("Parse failed: ${error.message}")
            println("Use --help for usage information")
        }
    )

    if (result == null) {
        // Parsing failed, error callback was called
        return
    }

    // Continue with application logic
    processFiles(result)
}

Advanced Parsing Patterns

Handling Different Error Types

import onl.ycode.argos.ParseError
import onl.ycode.argos.MissingOptionError
import onl.ycode.argos.InvalidValueError
import onl.ycode.argos.ConstraintViolationError

fun main(args: Array<String>) {
    val app = MyApp()

    app.parse(args,
        onSuccess = {
            // Continue with application logic
        },
        onError = { error, _ ->
            when (error) {
                is MissingOptionError -> {
                    println("Missing required option: ${error.option}")
                    println("Use --help to see all required options")
                }
                is InvalidValueError -> {
                    println("Invalid value '${error.value}' for option ${error.option}")
                    println("Expected: ${error.expectedType}")
                }
                is ConstraintViolationError -> {
                    println("Constraint violation: ${error.message}")
                    if (error.suggestions.isNotEmpty()) {
                        println("Suggestions:")
                        error.suggestions.forEach { println("  - $it") }
                    }
                }
                else -> {
                    println("Parse error: ${error.message}")
                }
            }
        }
    ) ?: return
}

Conditional Parsing

class ConfigurableApp : Arguments() {
    val configFile by option("--config")
    val input by option("--input")
    val output by option("--output")
}

fun main(args: Array<String>) {
    val app = ConfigurableApp()

    // First pass: check for config file
    val firstResult = app.parse(args,
        onError = { _, _ -> /* Ignore first pass errors */ }
    )

    if (firstResult == null && app.configFile != null) {
        // Load additional arguments from config file
        val configArgs = loadArgsFromConfig(app.configFile!!)
        val combinedArgs = configArgs + args

        app.parse(combinedArgs) ?: return
    } else if (firstResult == null) {
        println("Error: Failed to parse arguments")
        return
    }

    // Proceed with parsed arguments
}

fun loadArgsFromConfig(configFile: String): Array<String> {
    // Load configuration and convert to command line arguments
    // Implementation depends on config format (JSON, YAML, properties, etc.)
    return emptyArray()
}

Error Aggregation

Multiple Error Reporting

class MyApp : Arguments(
    aggregateErrors = true,                 // Show multiple errors at once
    maxAggregatedErrors = 10               // Limit number of errors shown
) {
    val email by option("--email")
        .validate("Must contain @") { it.contains("@") }
        .required()

    val port by option("--port").int()
        .validate("Must be valid port") { it in 1..65535 }
        .required()

    val count by option("--count").int()
        .validate("Must be positive") { it > 0 }
        .default(1)
}

fun main(args: Array<String>) {
    val app = MyApp()

    app.parse(arrayOf("--email", "invalid", "--port", "99999"),
        onError = { error, _ ->
            // With aggregateErrors = true, this will show:
            // Error: Multiple validation errors:
            // - Invalid value for --email: 'invalid' - Must contain @
            // - Invalid value for --port: '99999' - Must be valid port
            println(error.message)
        }
    ) ?: return
}

Error Prioritization

fun main(args: Array<String>) {
    val app = MyApp()

    val result = app.parse(args,
        onSuccess = { parsedArgs ->
            // Validate business logic after parsing
            val businessErrors = validateBusinessLogic(parsedArgs)
            if (businessErrors.isNotEmpty()) {
                businessErrors.forEach { println("Business rule violation: $it") }
                return@parse null  // Return null to indicate failure
            }

            // All validation passed
            runApplication(parsedArgs)
            parsedArgs  // Return parsed args
        },
        onError = { error, args ->
            when (error) {
                is MissingOptionError -> {
                    println("Missing required option: ${error.option}")
                    app.printUsage()
                }
                is InvalidValueError -> {
                    println("Invalid value: ${error.message}")
                    app.printUsage()
                }
                else -> {
                    println("Parse error: ${error.message}")
                    println("Use --help for detailed usage")
                }
            }
        }
    ) ?: return
}

fun validateBusinessLogic(args: MyApp): List<String> {
    val errors = mutableListOf<String>()

    // Custom business validation that can't be expressed as constraints
    if (args.input != null && args.output != null) {
        if (File(args.input!!).absolutePath == File(args.output!!).absolutePath) {
            errors.add("Input and output files cannot be the same")
        }
    }

    return errors
}

Domain-Aware Parsing

Multi-Command Parsing

class GitTool : Arguments() {
    val commitDomain by domain("commit")
        .required(::message)

    val branchDomain by domain("branch")
        .exactlyOne(::create, ::delete, ::list)

    val mergeDomain by domain("merge")
        .required(::sourceBranch)
        .conflicts(::fastForward, ::noFastForward)

    val message by option("--message", "-m")
        .onlyInDomains(::commitDomain)

    val create by option("--create").bool()
        .onlyInDomains(::branchDomain)

    val delete by option("--delete").bool()
        .onlyInDomains(::branchDomain)

    val list by option("--list").bool()
        .onlyInDomains(::branchDomain)

    val sourceBranch by option("--source-branch")
        .onlyInDomains(::mergeDomain)

    val fastForward by option("--ff").bool()
        .onlyInDomains(::mergeDomain)

    val noFastForward by option("--no-ff").bool()
        .onlyInDomains(::mergeDomain)

    val help by help()
}

fun main(args: Array<String>) {
    val app = GitTool()

    app.parse(args,
        onError = { error, _ ->
            println("Error: ${error.message}")

            // Provide domain-specific help
            when {
                args.any { it == "commit" } -> println("Use 'git-tool commit --help' for commit options")
                args.any { it == "branch" } -> println("Use 'git-tool branch --help' for branch options")
                args.any { it == "merge" } -> println("Use 'git-tool merge --help' for merge options")
                else -> println("Use 'git-tool --help' for available commands")
            }
        }
    ) ?: return

    when {
        app.commitDomain -> {
            println("Creating commit with message: ${app.message}")
            performCommit(app.message!!)
        }
        app.branchDomain -> {
            when {
                app.create -> createBranch()
                app.delete -> deleteBranch()
                app.list -> listBranches()
            }
        }
        app.mergeDomain -> {
            val strategy = when {
                app.fastForward -> MergeStrategy.FAST_FORWARD
                app.noFastForward -> MergeStrategy.NO_FAST_FORWARD
                else -> MergeStrategy.AUTO
            }
            performMerge(app.sourceBranch!!, strategy)
        }
        else -> {
            println("No command specified. Use --help for available commands.")
        }
    }
}

Interactive Parsing

Progressive Option Collection

class InteractiveApp : Arguments() {
    val batchMode by option("--batch").bool()
    val configFile by option("--config")
    val inputFiles by option("--input").list()
    val outputDir by option("--output-dir")
}

fun main(args: Array<String>) {
    val app = InteractiveApp()

    app.parse(args,
        onError = { error, _ ->
            if (app.batchMode) {
                // In batch mode, fail immediately
                println("Batch mode error: ${error.message}")
            } else {
                // In interactive mode, collect missing information
                println("Some options are missing. Let's collect them interactively:")
                collectMissingOptions(app, error)
            }
        }
    ) ?: return

    runApplication(app)
}

fun collectMissingOptions(app: InteractiveApp, error: ParseError) {
    println("Error: ${error.message}")

    // Collect missing required options interactively
    if (app.inputFiles.isEmpty()) {
        print("Enter input files (space-separated): ")
        val files = readLine()?.split(" ") ?: emptyList()
        // Would need to re-parse with the additional arguments
    }

    if (app.outputDir == null) {
        print("Enter output directory: ")
        val outputDir = readLine()
        // Would need to re-parse with the additional arguments
    }
}

Validation Feedback Loop

fun main(args: Array<String>) {
    val app = MyApp()
    var attempts = 0
    val maxAttempts = 3

    while (attempts < maxAttempts) {
        val result = app.parse(args,
            onError = { error, _ ->
                attempts++

                if (attempts >= maxAttempts) {
                    println("Too many parse failures. Exiting.")
                    return@parse null
                }

                println("Parse attempt $attempts failed: ${error.message}")

                // Provide contextual help and potentially modify args
                when (error) {
                    is MissingOptionError -> {
                        println("Would you like to provide ${error.option} interactively? (y/n)")
                        val response = readLine()
                        if (response?.lowercase() == "y") {
                            print("Enter value for ${error.option}: ")
                            val value = readLine()
                            if (value != null) {
                                args = args + arrayOf(error.option, value)
                            }
                        }
                    }
                    else -> {
                        println("Use --help for usage information")
                        return@parse null
                    }
                }
            }
        )

        if (result != null) break  // Success
        if (attempts >= maxAttempts) return
    }
}

Integration Patterns

Framework Integration

// Spring Boot integration example
@Component
class ArgosConfigurer {

    @EventListener
    fun configureFromArgs(event: ApplicationReadyEvent) {
        val args = event.applicationContext.environment
            .getProperty("spring.application.args", Array<String>::class.java)
            ?: emptyArray()

        val cliArgs = MyApp()
        cliArgs.parse(args,
            onSuccess = { configureApplication(it) },
            onError = { error, _ ->
                logger.error("CLI argument parse error: ${error.message}")
                // Decide whether to fail startup or use defaults
            }
        )
    }
}

// Testing integration
class MyAppTest {
    @Test
    fun testValidArguments() {
        val app = MyApp()
        val args = arrayOf("--input", "test.txt", "--output", "result.txt")

        val result = app.parse(args)
        assertNotNull(result)
        assertEquals("test.txt", app.input)
        assertEquals("result.txt", app.output)
    }

    @Test
    fun testInvalidArguments() {
        val app = MyApp()
        val args = arrayOf("--invalid-option", "value")

        val result = app.parse(args)
        assertNull(result)

        assertTrue(exception.message.contains("invalid-option"))
    }
}

Logging Integration

import java.util.logging.Logger
import java.util.logging.Level

class LoggingApp : Arguments() {
    val logLevel by option("--log-level")
        .enum<Level>()
        .default(Level.INFO)

    val logFile by option("--log-file")

    val input by option("--input").required()
}

fun main(args: Array<String>) {
    val app = LoggingApp()

    app.parse(args) ?: return

    // Configure logging based on parsed arguments
    val logger = Logger.getLogger("MyApp")
    logger.level = app.logLevel

    if (app.logFile != null) {
        val handler = java.util.logging.FileHandler(app.logFile)
        logger.addHandler(handler)
    }

    logger.info("Application starting with input: ${app.input}")

    // Continue with application logic
}

Best Practices

1. Choose the Right Parsing Method

// Simple applications: parse
fun simpleMain(args: Array<String>) {
    val app = SimpleApp()
    app.parse(args) ?: return
    runApp(app)
}

// Complex applications: parse with callbacks
fun complexMain(args: Array<String>) {
    val app = ComplexApp()
    app.parse(args,
        onSuccess = { runComplexApp(it) },
        onError = { error, _ ->
            println("Error: ${error.message}")
            app.printUsage()
        }
    ) ?: return

2. Provide Helpful Error Messages

// Good: Contextual error handling
catch (e: ParseError) {
    when (e) {
        is MissingOptionError -> {
            println("Missing required option: ${e.option}")
            println("Example: myapp ${e.option} <value>")
        }
        is InvalidValueError -> {
            println("Invalid value '${e.value}' for ${e.option}")
            println("Expected: ${e.expectedType}")
            if (e.suggestions.isNotEmpty()) {
                println("Did you mean: ${e.suggestions.joinToString()}")
            }
        }
        else -> println("Error: ${e.message}")
    }
    println("\nUse --help for detailed usage information")
}

3. Handle Edge Cases

// Good: Handle empty arguments
fun main(args: Array<String>) {
    if (args.isEmpty()) {
        println("No arguments provided. Use --help for usage information.")
        kotlin.system.exitProcess(1)
    }

    val app = MyApp()
    // Continue with parsing...
}

// Good: Handle help/version early
fun main(args: Array<String>) {
    if (args.any { it in listOf("--help", "-h", "--version", "-V") }) {
        val app = MyApp()
        app.parse(args)  // Let help/version options handle themselves
        return
    }

    // Continue with normal parsing...
}

4. Structure Complex Applications

// Good: Separate parsing from business logic
fun main(args: Array<String>) {
    val config = parseArgumentsToConfig(args)
    val application = createApplication(config)
    application.run()
}

fun parseArgumentsToConfig(args: Array<String>): ApplicationConfig? {
    val cliArgs = MyApp()
    return cliArgs.parse(args,
        onError = { error, _ -> handleParseError(error) }
    )?.let { ApplicationConfig.fromCliArgs(it) }
}

Argos parsing methods provide flexibility for different application architectures while maintaining consistent error handling and user experience across simple scripts and complex multi-command applications.