Usage

Annotate the desired main function with command(). Scala-argparse will then generate a standard main method and command line parser with bells-and-whistles such as help messages and bash completion.1

1

Note that until Scala 3 supports macro annotations (probably in version 3.3.0), you will need to write a tiny boilerplate snippet as shown in the introductory example.

The generated code uses a lower-level interface, which you can also use directly if you would like more flexibility than what the auto-generated CLI provides.

Parameter Mapping

Scala method parameters will be mapped to command line parameters in the following way:

  • Parameters with defaults will become --named-parameters= on the command line. Furthermore, boolean parameters become --flags, meaning that they don't need to take a 'true' argument on the command line.

  • Parameters without defaults become positional parameters.

  • Parameters of type Seq[?] become repeatable parameters on the command line.

E.g.

import argparse.default as ap

@ap.command()
def main(
  namedParameter: String = "a",
  flag: Boolean = false,
  repeatable: Seq[String] = Seq(),
  positional1: Int,
  positional2: Int,
  remaining: Seq[String]
) =
  println(s"namedParameter=$namedParameter")
  println(s"flag=$flag")
  println(s"repeatable=$repeatable")
  println(s"positional1=$positional1")
  println(s"positional2=$positional2")
  println(s"remaining=$remaining")

// boilerplate until Scala 3 supports macro annotations
def main(args: Array[String]) = argparse.main(this, args)
$ app --named-parameter b --repeatable a 1 2 3 --flag 4 --repeatable b 5
namedParameter=b
flag=true
repeatable=List(a, b)
positional1=1
positional2=2
remaining=List(3, 4, 5)

$ app --help
Usage: [OPTIONS] POSITIONAL1 POSITIONAL2 REMAINING...
Options:
      --bash-completion string  generate bash completion for this command
      --flag                    
      --help                    show this message and exit
      --named-parameter string  
      --repeatable string       

Parameter Types

Support for reading arguments from the command line as Scala types is provided for many types out-of-the-box. Some examples:

  • numeric types
  • java.io, java.nio and os.Path file types
  • various java.time date types
  • key=value pairs of other supported types

E.g.

import argparse.default as ap

@ap.command()
def main(
  num: Int = 0,
  num2: Double = 0,
  path: os.Path = os.pwd, // relative paths on the command line will be resolved to absolute paths w.r.t. to pwd
  keyValue: (String, Int) = ("a" -> 2),
  keyValues: Seq[(String, Int)] = Seq()
) =
  println(s"num=$num")
  println(s"num2=$num2")
  println(s"path=$path")
  println(s"keyValue=$keyValue")
  println(s"keyValues=$keyValues")

// boilerplate until Scala 3 supports macro annotations
def main(args: Array[String]) = argparse.main(this, args)
$ app --num 42 --num2 1.1 --path /a/b/c --key-value hello=2 --key-values a=1 --key-values b=2
num=42
num2=1.1
path=/a/b/c
keyValue=(hello,2)
keyValues=List((a,1), (b,2))

$ app --num 1.1
error processing argument --num: '1.1' is not an integral number
run with '--help' for more information

$ app --help
Usage: [OPTIONS]
Options:
      --bash-completion string  generate bash completion for this command
      --help                    show this message and exit
      --key-value string=int    
      --key-values string=int   
      --num int                 
      --num2 float              
      --path path               

The mechanism by which command line arguments are converted to Scala types is highly customizable and new types can easily be added.

Parameter Overrides

The generated command line parameters can further be customized by annotating Scala parameters with certain annotations:

  • @alias(): set other names by which the parameter will be available. This is particularly useful for defining single-letter short names for frequently used parameters.

  • @env(): set the name of an environment variable which will be used to lookup the parameter if it is not found on the command line.

  • @name(): override the name derived from the parameter name. This can be used as an escape hatch for changing positional to named arguments and vice versa.

E.g.

import argparse.default as ap

@ap.command()
def main(
  @ap.alias("-s", "--address") server: String = "a",
  @ap.env("APPLICATION_CREDENTIALS") creds: os.Path = os.pwd / "creds",
  @ap.name("--named") positional: Int
) =
  println(s"server=$server")
  println(s"creds=$creds")
  println(s"positional=$positional")

// boilerplate until Scala 3 supports macro annotations
def main(args: Array[String]) = argparse.main(this, args)
$ APPLICATION_CREDENTIALS=/secret app -s localhost --named 42
server=localhost
creds=/secret
positional=42

Output Mapping

The returned values of annotated functions are automatically converted to strings and printed to standard out. There are builtin conversions for some common return values:

  • iterables of products (aka case classes) are printed in a tabular format
  • other iterables are printed one per line
  • byte arrays and other sources of binary data are streamed
  • futures are awaited

In other cases, the toString method of the returned value is simply called.

E.g.

import argparse.default as ap

case class Item(
  name: String,
  value: Double,
  comment: String
)

@ap.command()
def main() =
  List(
    Item("item1", 2, ""),
    Item("item2", 0.213, "notice the numeric alignment"),
    Item("item3", -100.2, ""),
    Item("item4", 10.2, "a comment"),
    Item("another_item", 12.54, "a comment"),
    Item("", 12.54, "item has no name"),
    Item("etc", 0, "...")
  )

// boilerplate necessary until macro annotations become available in Scala 3
def main(args: Array[String]): Unit = argparse.main(this, args)
$ app
NAME         VALUE    COMMENT                     
item1           2.0                               
item2           0.213 notice the numeric alignment
item3        -100.2                               
item4          10.2   a comment                   
another_item   12.54  a comment                   
               12.54  item has no name            
etc             0.0   ...                         

You can also define your own conversions by defining instances of the argparse.core.OutputApi#Printer typeclass.

Error Handling

In case a command throws, only the exception's message is printed. The stack trace is not shown unless a DEBUG environment variable is defined.

You can change this behavior by overriding the handleError function of the OutputApi trait.

Nested Commands

As an application grows, it is common to organise different "actions" or "flavours" of the application under nested commands, each taking their own list of parameters. See the git or docker tools for some such examples.

In scala-argparse, nested commands use the same mechanism as single, top-level commands, with one small twist: instead of annotating a method with command(), you annotate a class definition (or a method that returns an instance of a class containing other commands). This can be done recursively, and classes can declare parameters which can be referenced by child commands.

E.g.

import argparse.default as ap

@ap.command()
class app():

  @ap.command()
  def version() = println(s"v1000")

  @ap.command()
  class op(factor: Double = 1.0):

    @ap.command()
    def showFactor() = println(s"the factor is $factor")

    @ap.command()
    def multiply(operand: Double) = println(s"result is: ${factor * operand}")

// this is boilerplate for now; it will become obsolete once macro-annotations
// are released
def main(args: Array[String]): Unit = argparse.main(this, args)
$ app
missing argument: command
run with '--help' for more information

$ app version
v1000

$ app op show-factor
the factor is 1.0

$ app op multiply 2
result is: 2.0

$ app op --factor 3 multiply 2
result is: 6.0

Bells and Whistles

Any program that uses scala-argparse automatically gets:

  • A concise help dialogue (that is formatted according to your terminal's current dimensions) derived from the main function's scaladoc comment.

    You can view the help dialogue by passing the --help flag.

  • A bash-completion script, which will allow users to get tab-completion in their terminal.

    The bash completion script can be generated by passing a --bash-completion=<program name> argument.

  • Bash-awareness for interactive bash completion.

Next Steps

  • Now that you know the high-level API, check out the lower-level API, which underpins the former and can be helpful for understanding customizations.

  • Read the API docs. Start with the argparse.default bundle.