How to create git like command line interface in Scala

Recently I’ve been building a small application that exposed only command line interface to the user. While I found some solutions that suggested to use plain Scala for this task (which was pretty straighforward) I wanted something that is more suitable. For example I wanted to make sure that –help will be auto-generated so I don’t have to define it manually.

After little research I found scopt which had all the features I needed. Syntax may be a bit confusing at first especially for people new to Scala (myself included) but after a while you will appreciate it’s simplicity.

As an example of what scopt can do I decided to implement a demo app resembling GIT CLI with branch and commit child commands. This will allow for executing commands like git commit [options] or git checkout <branch> [options].

We start with defining a Config case class that will represent options, arguments and commands as well as their type and default values.

Next step is to define behaviors/actions in the body of scopt.OptionParser. There is a better explanation of what things mean on scopt github page.

Last part is to call parser with pattern matching that will give you access to config object we previously defined and we can test for set arguments.

object Main extends App {
    val version = "0.1"

    case class Config (
          mode: String = "",
          version: Boolean = false,
          commitAll: Boolean = false,
          commitMessage: String = "",
          checkoutBranch: String = "")

    def gitCheckout(branch: String) = {
        println("Checking out branch: " + branch)
    }

    def gitCommit(message: String, all: Boolean) = {
        if (all) println("Committing all staged files.")
        if (message == "") println("Execute default editor to force enter commit message...")
        else println("Commit message: " + message)
    }

    val parser = new scopt.OptionParser[Config]("git") {
        head("Git scopt demo", Main.version)
        opt[Unit]("version") abbr("v") action((_, c) => c.copy(version = true)) text("Prints the Git suite version that the git program came from.")

        cmd("checkout") action ((_, c) => c.copy(mode = "checkout")) children(
            arg[String]("branch") unbounded() required() action((x, c) => c.copy(checkoutBranch = x)) text("git-checkout - Checkout a branch or paths to the working tree")
        ) text("git-branch - List, create, or delete branches")

        cmd("commit") action ((_, c) => c.copy(mode = "commit")) children (
            opt[Unit]("all") abbr("a") action((x, c) => c.copy(commitAll = true)) text("Tell the command to automatically stage files that have been modified and deleted, but new files you have not told Git about are not affected"),
            opt[String]("message") abbr("m") action((x, c) => c.copy(commitMessage = x)) text("Use the given <msg> as the commit message. If multiple -m options are given, their values are concatenated as separate paragraphs.")
        ) text("git-commit - Record changes to the repository")

        help("help") text("prints this usage text")
    }

    parser.parse(args, Config()) match {
        case Some(config) => {
            if (config.mode == "checkout") Main.gitCheckout(config.checkoutBranch)
            if (config.mode == "commit") Main.gitCommit(config.commitMessage, config.commitAll)
            if (config.version) println("Version is: " + Main.version)
        }
        case None => println("Please use --help argument for usage")
    }
}

 

After compiling this app you can run <appname> --help which should produce following output.

Git scopt demo 0.1
Usage: git [checkout|commit] [options] ...

  -v | --version
        Prints the Git suite version that the git program came from.
Command: checkout branch
git-branch - List, create, or delete branches
  branch
        git-checkout - Checkout a branch or paths to the working tree
Command: commit [options]
git-commit - Record changes to the repository
  -m  | --message 
        Use the given  as the commit message. If multiple -m options are given, their values are concatenated as separate paragraphs.
  -a | --all
        Tell the command to automatically stage files that have been modified and deleted, but new files you have not told Git about are not affected
  --help
        prints this usage text

Executing <appname> commit -a -m "This is example commit message" will produce:

Committing all staged files.
Commit message: This is example commit message

Leave a Reply

Your email address will not be published. Required fields are marked *