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
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
andos.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.