Home Code Other Software

Table of Contents

Introduction

Argot is a command-line parser library for Scala, supporting:

Installation

Argot is published to the Bintray Maven repository, which is automatically linked to Bintray’s JCenter repository. (From JCenter, it’s eventually pushed to the automatically sync’d with the Maven Central Repository.

Installing for Maven

If you’re using Maven, just specify the artifact, and Maven will do the rest for you:

Here’s a sample Maven POM “dependency” snippet:

<dependency>
  <groupId>org.clapper</groupId>
  <artifactId>argot_2.10</artifactId>
  <version>1.0.3</version>
</dependency>

If you cannot resolve the artifact, then add the JCenter repository:

<repositories>
  <repository>
    <snapshots>
      <enabled>false</enabled>
    </snapshots>
    <id>central</id>
    <name>bintray</name>
    <url>http://jcenter.bintray.com</url>
  </repository>
  ...
</repositories>

If the version you want has not (yet) been pushed to either Maven Central or JCenter, then you can pull it directly from my Bintray repo, by adding this repository:

<repositories>
  <repository>
    <snapshots>
      <enabled>false</enabled>
    </snapshots>
    <id>central</id>
    <name>bintray</name>
    <url>http://dl.bintray.com/bmc/maven</url>
  </repository>
</repositories>

For more information on using Maven and Scala, see Josh Suereth’s Scala Maven Guide.

Using with SBT

0.12.x or 0.13.x

If you’re using SBT 0.12.x or better to compile your code, use the following line in your build.sbt file (for Quick Configuration). If you’re using an SBT 0.11.x Full Configuration, you’re obviously smart enough to figure out what to do, on your own.

libraryDependencies += "org.clapper" %% "argot" % "1.0.3"

If the version you want can’t be found (i.e., it hasn’t yet been pushed to Bintray’s JCenter or to Maven Central), simply add my Bintray repo:

resolvers += "Brian Clapper's Bintray" at "http://dl.bintray.com/bmc/maven"

Argot is also registered with [Doug Tangren][]’s excellent ls.implicit.ly catalog. If you use the ls SBT plugin, you can install Argot with

sbt> ls-install argot

Building from Source

Source Code Repository

The source code for the Argot library is maintained on GitHub. To clone the repository, run this command:

git clone git://github.com/bmc/argot.git

Build Requirements

Building the Argot library requires SBT 0.11.1 (or better). Install SBT, as described at the SBT web site.

Building Argot

Assuming you have an sbt shell script (or .BAT file, for [shudder] Windows), run:

sbt compile test package

The resulting jar file will be in the top-level target directory.

Runtime Requirements

Argot requires the following libraries to be available at runtime, for some, or all, of its methods.

Maven and SBT should automatically download these libraries for you.

Using Argot

ArgotParser is a command-line parser and the main entry point for the API. An ArgotParser embodies a representation of the command line: its expected options and their value types, the expected positional parameters and their value types, the name of the program, and other values.

Using Argot boils down to creating a command line specification and using it to parse a command line. To accomplish that goal you:

Each of these steps is described further, below.

Supported Syntax

ArgotParser supports:

The cooltool Example

The remainder of this usage section discusses how to build a command-line specification and parse it. We’ll be creating the specification for a fictitious tool called “cooltool”, with the following usage (as generated by Argot):

cooltool: Version 1.0

Usage: cooltool [OPTIONS] outputfile [input] ...

OPTIONS

-e emailaddr
-e emailaddr
--email emailaddr  Address to receive emailed results (May be specified
                   multiple times.)

-i n
--iterations n     Total iterations

-n
--noerror          Do not abort on error.

-u username
--user username    User to receive email. Email address is queried from
                   database. (May be specified multiple times.)

-q
--quiet
-v
--verbose          Increment (-v, --verbose) or decrement (-q, --quiet) the
                   verbosity level.

PARAMETERS

outputfile  Output file to which to write.

input       Input files to read. If not specified, use stdin. (May be specified
            multiple times.)

Creating the Parser

The constructor for the ArgotParser class takes five parameters, four of which are optional:

For cooltool, our ArgotParser looks like this:

import org.clapper.argot._

val parser = new ArgotParser("cooltool", preUsage=Some("Version 1.0"))

That line of code defines a parser with a prefix message, but no post-usage message, using the default output width and a non-compact usage string.

Specifying the Options

Options may be specified in any order.

Options have one or more names. Single-character names are assumed to be preceded by a single hyphen (-); multicharacter names are assumed to be preceded by a double hyphen (--). Single character names can be combined, POSIX-style.

Given the cooltool usage, the following command lines are identical:

cooltool -v -n -i 10 out
cooltool --verbose --noerror --iterations 10 --output out
cooltool -nvi10 out
cooltool -nv -i10 out

There are three kinds of options:

Each type of is discussed further, below.

Single-value Options

A single-value option is one that takes a single value. The first occurrence of the option on the command line sets the value. Any subsequent occurrences replace the previously set values.

Single-value options are defined with the option() methods, which return a typed instance of the SingleValueOption class. The SingleValueOption object contains a value field, of type Option[T]. If the option doesn’t appear on the command line, value will be None. Otherwise, it’ll be set to Some(value). There’s no provision for assigning a default value, since that can be accomplished via the Option class’s getOrElse() method.

For cooltool, there is one single-value option:

-i iterations
--iterations iterations

To create this option, use the following code fragment:

import ArgotConverters._

val iterations = parser.option[Int](List("i", "iterations"), "n",
                                    "total iterations")

There are several things to note here:

  1. The option method takes a type parameter. In this case, we’ve supplied an Int, indicating that we want to convert the value to an Int.
  2. The valid names for the option are specified in the initial parameter, a list. The single-character name, “i”, corresponds to a -i option on the command line. The multicharacter name, “iterations”, corresponds to --iterations.

Introducing Automatic Conversions

The actual definition of the option method is:

def option[T](names: List[String], valueName: String, description: String)
             (implicit convert: (String, SingleValueOption[T]) => T):
    SingleValueOption[T] =

Note the second parameter list, with the implicit convert parameter. This parameter specifies a conversion function that will convert the string value into the desired type (Int in our case). Argot has some pre-defined conversion functions for common types, in the org.clapper.argot.ArgotConverters module. If you pull the contents of that module into your namespace, then you don’t have to specify a conversion function for common types. This import:

import ArgotConverters._

makes those built-in implicit conversion functions available.

You can supply your own function, however. We could just as easily have defined iterations like this:

val iterations = parser.option[Int](List("i", "iterations"), "n", "total iterations") {
  (sValue, opt) =>

  try {
    sValue.toInt
  }

  catch {
    case _: NumberFormatException =>
      throw new ArgotConversionException(
        "Option " + opt.name + ": \"" + sValue + "\" isn't a number."
      )
  }
}

We could also have used an implicit function:

implicit def convertIterations(sValue: String, opt: CommandLineArgument[]): Int = {
  try {
    sValue.toInt
  }
  catch {
    case _: NumberFormatException =>
      throw new ArgotConversionException(
        "Option " + opt.name + ": \"" + sValue + "\" isn't a number"
      )
  }
}
val iterations = parser.option[Int](List("i", "iterations"), "n",
                                    "total iterations")

Defining the conversion function as an implicit is useful if you want to use the same conversion function with multiple parameters.

The built-in Argot String-to-Int conversion function looks pretty much like the convertIterations() function, above.

All the option-specification and parameter-specification methods support implicit conversion functions.

Some common reasons to supply your own conversion function include:

We’ll see some examples of both of these cases in subsequent sections.

Multi-value Options

A multi-value option is one that takes a single value, but if it appears multiple times on the command line, each occurrence adds its value to the list of already accumulated values for the option. The values are stored in a Scala sequence. Each occurrence of the option on the command line adds the associated value to the sequence. If the option never appears on the command line, its value will be an empty list.

Multi-value options are defined with the multiOption() methods, which a typed instance of the MultiValueOption class. The MultiValueOption object contains a value field, of type Seq[T]. If the option doesn’t appear on the command line, value will be Nil. Otherwise, it will contain all the values that were present for each invocation of the option on the command line. There’s no provision for assigning a default value.

For cooltool, there are two multi-value options: --email and --user. One takes a string and can use the default conversion function. The other takes an email address and supplies its own conversion function, to check the validity of the supplied parameter. The code for each is shown below:

val users = parser.multiOption[String](
    List("u", "user"), "username",
    "User to receive email. Email"address is queried from database."
)

val emails = parser.multiOption[String](
    List("e", "email"), "emailaddr", "Address to receive emailed results."
) {
  (s, opt) =>

   val ValidAddress = """^[^@]+@[^@]+\.[a-zA-Z]+$""".r
   ValidAddress.findFirstIn(s) match {
    case None    => parser.usage("Bad email address \"" + s +
                                 "\" for " + opt.name + " option.")
    case Some(_) => s
  }
}

Flag Options

Flag options take no values; they either appear on the command line, or they don’t. Typically, flag options are associated with boolean value, though Argot will permit you to associate them with any type you choose.

Flag options permit you to segregate the option names into on names and off names. With boolean flag options, the on names set the value to true, and the off names set the value to values. With typed flag options, what happens depends on the conversion function.

cooltool has two flag options. The --noerror option is a simple boolean flag; specify --noerror (or -n), and the flag is set to true. Otherwise, the flag is unset. The verbosity flag option, however, is an integer. Specify -v or --verbose, and the verbosity level gets incremented; specify -q or --quiet, and the verbosity level gets decremented.

The code for both options follows:

val noError = parser.flag[Boolean](List("n", "noerror"),
                                   "Do not abort on error.")

val verbose = parser.flag[Int](List("v", "verbose"),
                               List("q", "quiet"),
                               "Increment (-v, --verbose) or " +
                               "decrement (-q, --quiet) the " +
                               "verbosity level.") {
  (onOff, opt) =>

  import scala.math

  val currentValue = opt.value.getOrElse(0)
  val newValue = if (onOff) currentValue + 1 else currentValue - 1
  math.max(0, newValue)
}

Positional Parameters

Positional parameters are the parameters following options. They have the following characteristics.

cooltool has two position parameters.

Here’s the code for each parameter:

val output = parser.parameter[String]("outputfile",
                                      "Output file to which to write.",
                                      false)

val input = parser.multiParameter[File]("input",
                                        "Input files to read. If not " +
                                        "specified, use stdin.",
                                        true) {
  (s, opt) =>

  val file = new File(s)
  if (! file.exists)
      parser.usage("Input file \"" + s + "\" does not exist.")

  file
}

Putting It All Together

The entire main program for cooltool looks like this:

package org.clapper.argot
import java.io.File
import scala.math

object CoolTool {
  // Argument specifications

  import ArgotConverters._

  val parser = new ArgotParser(
    "test",
    preUsage=Some("ArgotTest: Version 0.1. Copyright (c) " +
                  "2012, Brian M. Clapper. Pithy quotes go here.")
  )

  val iterations = parser.option[Int](List("i", "iterations"), "n",
                                      "Total iterations")
  val verbose = parser.flag[Int](List("v", "verbose"),
                                 List("q", "quiet"),
                                 "Increment (-v, --verbose) or " +
                                 "decrement (-q, --quiet) the " +
                                 "verbosity level.") {
    (onOff, opt) =>

    import scala.math

    val currentValue = opt.value.getOrElse(0)
    val newValue = if (onOff) currentValue + 1 else currentValue - 1
    math.max(0, newValue)
  }

  val noError = parser.flag[Boolean](List("n", "noerror"),
                                     "Do not abort on error.")
  val users = parser.multiOption[String](List("u", "user"), "username",
                                         "User to receive email. Email " +
                                         "address is queried from " +
                                         "database.")

  val email = parser.multiOption[String](List("e", "email"), "emailaddr",
                                         "Address to receive emailed " +
                                         "results.") {
    (s, opt) =>

    val ValidAddress = """^[^@]+@[^@]+\.[a-zA-Z]+$""".r
    ValidAddress.findFirstIn(s) match {
      case None    => parser.usage("Bad email address \"" + s +
                                   "\" for " + opt.name + " option.")
      case Some(_) => s
    }
  }

  val output = parser.parameter[String]("outputfile",
                                        "Output file to which to write.",
                                        false)

  val input = parser.multiParameter[File]("input",
                                          "Input files to read. If not " +
                                          "specified, use stdin.",
                                          true) {
    (s, opt) =>

    val file = new File(s)
    if (! file.exists)
      parser.usage("Input file \"" + s + "\" does not exist.")

    file
  }

  // The guts of the program (omitted here)
  def runCoolTool = {
    ...
  }

  // Main program
  def main(args: Array[String]) {
    try {
      parser.parse(args)
      runCoolTool
    }

    catch {
      case e: ArgotUsageException => println(e.message)
    }
  }
}

Resetting the Parser

If you want to re-use the same ArgotParser, you must reset its parameter and option values, clearing them; otherwise, they will retain the parsed values from the previous parse. The ArgotParser class provides a convenient way to reset everything:

val p = new ArgotParser(...)

...

p.parse(args)

...

p.reset()  // resets all internal state

API Documentation

The Scaladoc-generated the API documentation is available locally. In addition, you can generate your own version with:

sbt doc

The change log is here.

Similar Work

There are other Scala- and Java-based argument parsers available. While this list is by no means exhaustive, here are some examples:

This Stack Overflow article on the topic is also worth reading.

Author

Brian M. Clapper

Contributing to Argot

Argot is still under development. If you have suggestions or contributions, feel free to fork the Argot repository, make your changes, and send me a pull request.

Copyright and License

Argot is copyright © 2010-2011 Brian M. Clapper and is released under a BSD License.

Patches

I gladly accept patches from their original authors. Feel free to email patches to me or to fork the Argot repository and send me a pull request. Along with any patch you send: