6 minutes read

The command line is an interface that allows the user to interact with the operating system through text commands. A command-line parser is a tool that interprets these commands. It takes a string of text (entered by the user or from a file) and turns it into objects that the program can use.

Introduction to kotlinx-cli

kotlinx-cli is a library for creating command-line applications in Kotlin. Let's look at how to install kotlinx-cli. First, you need to add the following dependency in your project's build.gradle file:

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-cli:0.3.5")
}

Main concepts of kotlinx-cli

After installing kotlinx-cli in your project, you can start using it. Below are some of the main concepts and examples of their use.

Example 1: Creating command-line arguments

import kotlinx.cli.*

fun main(args: Array<String>) {
    val parser = ArgParser("example")
    val name by parser.option(ArgType.String, description = "Your name").required()
    parser.parse(args)
    println("Hello, $name!")
}

Output for the arg --name Jon:

Hello, Jon!

Example2: Creating command-line arguments with several values specified in the command-line string

import kotlinx.cli.*

fun main(args: Array<String>) {
    val parser = ArgParser("example")
    val name by parser.option(ArgType.String, description = "Your name").multiple()
    parser.parse(args)
    println("Hello, ${name[0]}!")
    println("Hello, ${name[1]}!")
}

Output for the arg --name John --name Doe:

Hello, John!
Hello, Doe!

Example 3: Using flags and options

import kotlinx.cli.*

fun main(args: Array<String>) {
    val parser = ArgParser("example")
    val verbose by parser.option(ArgType.Boolean, shortName = "v", description = "Enable verbose mode")
    parser.parse(args)
    if (verbose == true) {
        println("Verbose mode is enabled")
    } else {
        println("Verbose mode is not enabled")
    }
}

Example 4: Working with various data types

import kotlinx.cli.*

fun main(args: Array<String>) {
    val parser = ArgParser("example")
    val count by parser.option(ArgType.Int, description = "Number of times to repeat").default(1)
    parser.parse(args)
    repeat(count) {
        println("Hello, Kotlin!")
    }
}

Advanced topics in kotlinx-cli

kotlinx-cli provides several advanced features that can be useful in more complex applications.

Example 1: Creating subcommands

import kotlinx.cli.*

@OptIn(ExperimentalCli::class)
class MyCommand(parser: ArgParser): Subcommand(parser.toString(), "myCommand") {
    val parser.option by option(ArgType.Int, description = "Some option")
    override fun execute() {
        println("My command is executed with option value $option")
    }
}

fun main(args: Array<String>) {
    val parser = ArgParser("example")
    val command = MyCommand(parser)
    parser.parse(args)
    command.execute() // printing "My command is executed with option value null"
                      // null because we do not implement options
}

Example 2: Customizing error messages

import jdk.internal.joptsimple.OptionException
import kotlinx.cli.*

fun main(args: Array<String>) {
    val parser = ArgParser("example")
    val name by parser.option(ArgType.String, description = "Your name").required()
    try {
        parser.parse(args)
    } catch (e: OptionException) {
        println("Sorry, you must provide your name.")
    }
}

Example 3: Handling exceptions and errors

import kotlinx.cli.*

fun main(args: Array<String>) {
    val parser = ArgParser("example")
    val count by parser.option(ArgType.Int, description = "Number of times to repeat").default(1)
    try {
        parser.parse(args)
        repeat(count) {
            println("Hello, Kotlin!")
        }
    } catch (e: IllegalArgumentException) {
        println("Invalid argument: ${e.message}")
    }
}

Practical example of using kotlinx-cli

Let's develop a simple application that will use some of the concepts discussed above.

Example: Creating a simple application using kotlinx-cl

import kotlinx.cli.*

/**
 * @OptIn(ExperimentalCli::class)
 * This annotation marks an experimental API for working with command-line arguments.
 * Beware using the annotated API, especially if you're developing a library, since your
 * library might become binary incompatible with the future versions of the CLI library.
 * Any usage of a declaration annotated with @ExperimentalCli must be accepted either by annotating
 * that usage with the UseExperimental annotation, e.g., @UseExperimental(ExperimentalCli::class),
 * or by using the compiler argument
 */
@OptIn(ExperimentalCli::class)
class GreetCommand(parser: ArgParser): Subcommand(parser.toString(), "greet") {
    val names by parser.option(ArgType.String, description = "User's name").required()
    val times by parser.option(ArgType.Int, description = "How many times to greet").default(1)

    override fun execute() {
        repeat(times) {
            println("Hello, $names!")
        }
    }
}

fun main(args: Array<String>) {
    val parser = ArgParser("example")
    val greetCommand = GreetCommand(parser)
    parser.parse(args)
    greetCommand.execute()
}

Output for the arg --names Jon --times 5 :

Hello, Jon!
Hello, Jon!
Hello, Jon!
Hello, Jon!
Hello, Jon!

Best practices and recommendations

  • Describe each option and each subcommand. This will help users understand what your program does.
  • Always handle exceptions that may occur when parsing arguments. This will help prevent unexpected crashes of your program.
  • Use argument types for user input validation. This will ensure that your program gets data in the correct format.

Conclusion

We have covered the basic concepts and some advanced features of working with kotlinx-cli and provided several practical examples. With this knowledge, you will be able to create powerful and flexible command-line applications in Kotlin.

5 learners liked this piece of theory. 3 didn't like it. What about you?
Report a typo