hammerlab/case-app

Name: case-app

Owner: Hammer Lab

Description: Type-level & seamless command-line argument parsing for Scala

Forked from: alexarchambault/case-app

Created: 2017-07-05 17:22:43.0

Updated: 2017-07-05 17:22:49.0

Pushed: 2017-07-05 17:23:12.0

Homepage:

Size: 194

Language: Scala

GitHub Committers

UserMost Recent Commit# Commits

Other Committers

UserEmailMost Recent Commit# Commits

README

case-app

Type-level & seamless command-line argument parsing for Scala

Build Status Gitter Maven Central Scaladoc

Imports

The code snippets below assume that the content of caseapp is imported,

rt caseapp._
Parse a simple set of options
 class Options(
er: Option[String],
ableFoo: Boolean = false,
le: List[String]


App.parse[Options](
q("--user", "alice", "--file", "a", "--file", "b")
 Right((Options(Some("alice"), false, List("a", "b")), Seq.empty))
Required and optional arguments

All arguments are required by default. To define an optional argument simply wrap its type into Option[T].

Optional arguments can also be defined by providing a default value. There are two ways to do that:

 class Options(
er: Option[String],
ableFoo: Boolean = false,
le: List[String] = Nil


App.parse[Options](Seq()) == Right((Options(None, false, Nil), Seq.empty))
Lists

Some arguments can be specified several times on the command-line. These should be typed as lists, e.g. file in

 class Options(
er: Option[String],
ableFoo: Boolean = false,
le: List[String]


App.parse[Options](
q("--file", "a", "--file", "b")
 Right((Options(None, false, List("a", "b")), Seq.empty))

If an argument is specified several times, but is not typed as a List (or an accumulating type, see below), the final value of its corresponding field is the last provided in the arguments.

Whole application with argument parsing

case-app can take care of the creation of the main method parsing command-line arguments.

rt caseapp._

 class ExampleOptions(
o: String,
r: Int


ct Example extends CaseApp[ExampleOptions] {

f run(options: ExampleOptions, arg: RemainingArgs): Unit = {
// Core of the app
// ...



Example in the above example will then have a main method, parsing the arguments it is given to an ExampleOptions, then calling the run method if parsing was successful.

Automatic help and usage options

Running the above example with the --help (or -h) option will print an help message of the form

ple
e: example [options]
foo  <value>
bar  <value>

Calling it with the --usage option will print

e: example [options]
Customizing items of the help / usage message

Several parts of the above help message can be customized by annotating ExampleOptions or its fields:

Name("MyApp")
Version("0.1.0")
gName("my-app-cli")
 class ExampleOptions(
elpMessage("the foo")
alueDescription("foo")
foo: String,
elpMessage("the bar")
alueDescription("bar")
bar: Int

Called with the --help or -h option, would print

p 0.1.0
e: my-app-cli [options]
foo  <foo>: the foo
bar  <bar>: the bar

Note the application name that changed, on the first line. Note also the version number appended next to it. The program name, after Usage:, was changed too.

Lastly, the options value descriptions (<foo> and <bar>) and help messages (the foo and the bar), were customized.

Extra option names

Alternative option names can be specified, like

 class ExampleOptions(
xtraName("f")
foo: String,
xtraName("b")
bar: Int

--foo and -f, and --bar and -b would then be equivalent.

Long / short options

Field names, or extra names as above, longer than one letter are considered long options, prefixed with --. One letter long names are short options, prefixed with a single -.

 class ExampleOptions(
 Int,
o: String

would accept --foo bar and -a 2 as arguments to set foo or a.

Pascal case conversion

Field names or extra names as above, written in pascal case, are split and hyphenized.

 class Options(
oBar: Double

would accept arguments like --foo-bar 2.2.

Reusing options

Sets of options can be shared between applications:

 class CommonOptions(
o: String,
r: Int


 class First(
z: Double,
ecurse
common: CommonOptions


 ...



 class Second(
s: Long,
ecurse
common: CommonOptions


 ...


Commands

case-app has a support for commands.

ed trait DemoCommand

 class First(
o: Int,
r: String
tends DemoCommand

 class Second(
z: Double
tends DemoCommand

ct MyApp extends CommandApp[DemoCommand] {
f run(command: DemoCommand, args: RemainingArgs): Unit = {}

MyApp can then be called with arguments like

pp first --foo 2 --bar a
pp second --baz 2.4
Counters

Needs to be updated

Some more complex options can be specified multiple times on the command-line and should be “accumulated”. For example, one would want to define a verbose option like

 class Options(
xtraName("v") verbose: Int

Verbosity would then have be specified on the command-line like --verbose 3. But the usual preferred way of increasing verbosity is to repeat the verbosity option, like in -v -v -v. To accept the latter, tag verbose type with Counter:

 class Options(
xtraName("v") verbose: Int @@ Counter

verbose (and v) option will then be viewed as a flag, and the verbose variable will contain the number of times this flag is specified on the command-line.

It can optionally be given a default value other than 0. This value will be increased by the number of times -v or --verbose was specified in the arguments.

User-defined option types

Needs to be updated

Use your own option types by defining implicit ArgParsers for them, like in

rt caseapp.core.ArgParser

t Custom

icit val customArgParser: ArgParser[Custom] =
gParser.instance[Custom]("custom") { s =>
// parse s
// return
// - Left("error message") in case of error
// - Right(custom) in case of success
???

Then use them like

 class Options(
stom: Custom,
o: String

Migration from the previous version

Shared options used to be automatic, and now require the @Recurse annotation on the field corresponding to the shared options. This prevents ambiguities with custom types as above.

Usage

Add to your build.sbt

lvers += Resolver.sonatypeRepo("releases")
aryDependencies += "com.github.alexarchambault" %% "case-app" % "1.2.0-M2"

Note that case-app depends on shapeless 2.3. Use the 1.0.0 version if you depend on shapeless 2.2.

It is built against scala 2.10, 2.11, and 2.12.

If you are using scala 2.10.x, also add the macro paradise plugin to your build,

aryDependencies +=
mpilerPlugin("org.scalamacros" % "paradise" % "2.0.1" cross CrossVersion.full)
Contributors
See also

Eugene Yokota, the current maintainer of scopt, and others, compiled an (eeextremeeeely long) list of command-line argument parsing libraries for Scala, in this StackOverflow question.

Unlike scopt, case-app is less monadic / abstract data types based, and more straight-to-the-point and descriptive / algebric data types oriented.

Notice

Copyright (c) 2014-2017 Alexandre Archambault and contributors. See LICENSE file for more details.

Released under Apache 2.0 license.


This work is supported by the National Institutes of Health's National Center for Advancing Translational Sciences, Grant Number U24TR002306. This work is solely the responsibility of the creators and does not necessarily represent the official views of the National Institutes of Health.