Command Line Subcommands in Kotlin

Here’s the translation of the Go code example to Kotlin, with explanations in Markdown format suitable for Hugo:

import kotlin.system.exitProcess

fun main(args: Array<String>) {
    // We declare a subcommand using a when expression to check the first argument
    // and proceed to define new flags specific for each subcommand.
    when {
        args.isEmpty() -> {
            println("expected 'foo' or 'bar' subcommands")
            exitProcess(1)
        }
        args[0] == "foo" -> {
            val fooCmd = FooCommand()
            fooCmd.parse(args.drop(1))
            println("subcommand 'foo'")
            println("  enable: ${fooCmd.enable}")
            println("  name: ${fooCmd.name}")
            println("  tail: ${fooCmd.remainingArgs}")
        }
        args[0] == "bar" -> {
            val barCmd = BarCommand()
            barCmd.parse(args.drop(1))
            println("subcommand 'bar'")
            println("  level: ${barCmd.level}")
            println("  tail: ${barCmd.remainingArgs}")
        }
        else -> {
            println("expected 'foo' or 'bar' subcommands")
            exitProcess(1)
        }
    }
}

// For each subcommand, we create a class to handle its specific flags
class FooCommand {
    var enable: Boolean = false
    var name: String = ""
    lateinit var remainingArgs: List<String>

    fun parse(args: List<String>) {
        val parser = ArgParser(args)
        enable = parser.flag("enable", "enable")
        name = parser.option("name", "", "name") ?: ""
        remainingArgs = parser.remaining
    }
}

class BarCommand {
    var level: Int = 0
    lateinit var remainingArgs: List<String>

    fun parse(args: List<String>) {
        val parser = ArgParser(args)
        level = parser.option("level", 0, "level")?.toIntOrNull() ?: 0
        remainingArgs = parser.remaining
    }
}

// A simple argument parser to handle flags and options
class ArgParser(private val args: List<String>) {
    val remaining = mutableListOf<String>()

    fun flag(name: String, description: String): Boolean {
        return "-$name" in args
    }

    fun option(name: String, default: String, description: String): String? {
        val index = args.indexOf("-$name")
        if (index != -1 && index + 1 < args.size) {
            return args[index + 1]
        }
        return null
    }

    init {
        var i = 0
        while (i < args.size) {
            if (args[i].startsWith("-")) {
                i += 2
            } else {
                remaining.add(args[i])
                i++
            }
        }
    }
}

To run the program, save it as CommandLineSubcommands.kt and use the Kotlin compiler:

$ kotlinc CommandLineSubcommands.kt -include-runtime -d CommandLineSubcommands.jar
$ java -jar CommandLineSubcommands.jar foo -enable -name=joe a1 a2
subcommand 'foo'
  enable: true
  name: joe
  tail: [a1, a2]

Now try the bar subcommand:

$ java -jar CommandLineSubcommands.jar bar -level 8 a1
subcommand 'bar'
  level: 8
  tail: [a1]

But bar won’t accept foo’s flags:

$ java -jar CommandLineSubcommands.jar bar -enable a1
subcommand 'bar'
  level: 0
  tail: [-enable, a1]

In this Kotlin version, we’ve implemented a simple argument parsing mechanism to handle subcommands and their respective flags. The ArgParser class provides basic functionality to parse flags and options from the command line arguments.

Each subcommand is represented by its own class (FooCommand and BarCommand), which encapsulates the parsing logic for its specific flags.

The main function uses a when expression (Kotlin’s equivalent of a switch statement) to determine which subcommand was invoked and processes the arguments accordingly.

While this implementation doesn’t provide the same level of robustness as the flag package in Go, it demonstrates how to achieve similar functionality in Kotlin for handling command-line subcommands with their own sets of flags.

Next, we’ll look at environment variables, another common way to parameterize programs.