261

What is a good way of parsing command line arguments in Scala?

Related:

Eugene Yokota
  • 94,654
  • 45
  • 215
  • 319

26 Answers26

249

For most cases you do not need an external parser. Scala's pattern matching allows consuming args in a functional style. For example:

object MmlAlnApp {
  val usage = """
    Usage: mmlaln [--min-size num] [--max-size num] filename
  """
  def main(args: Array[String]) {
    if (args.length == 0) println(usage)
    val arglist = args.toList
    type OptionMap = Map[Symbol, Any]

    def nextOption(map : OptionMap, list: List[String]) : OptionMap = {
      def isSwitch(s : String) = (s(0) == '-')
      list match {
        case Nil => map
        case "--max-size" :: value :: tail =>
                               nextOption(map ++ Map('maxsize -> value.toInt), tail)
        case "--min-size" :: value :: tail =>
                               nextOption(map ++ Map('minsize -> value.toInt), tail)
        case string :: opt2 :: tail if isSwitch(opt2) => 
                               nextOption(map ++ Map('infile -> string), list.tail)
        case string :: Nil =>  nextOption(map ++ Map('infile -> string), list.tail)
        case option :: tail => println("Unknown option "+option) 
                               exit(1) 
      }
    }
    val options = nextOption(Map(),arglist)
    println(options)
  }
}

will print, for example:

Map('infile -> test/data/paml-aln1.phy, 'maxsize -> 4, 'minsize -> 2)

This version only takes one infile. Easy to improve on (by using a List).

Note also that this approach allows for concatenation of multiple command line arguments - even more than two!

Roberto Bonvallet
  • 31,943
  • 5
  • 40
  • 57
pjotrp
  • 2,547
  • 1
  • 15
  • 4
  • Very nice. I am wondering what exactly the "isSwitch" check for and why it's being used in the 4th case statement? Thanks for the great example! – MCP Aug 19 '13 at 18:00
  • 4
    isSwitch simply checks for the first character being a dash '-' – pjotrp Aug 27 '13 at 11:00
  • I am getting a: cannot resolve symbol exit. Can anyone help out as to why this might be happening? – theMadKing Jun 16 '15 at 13:38
  • 8
    `nextOption` is not a good name for the function. It's a function that returns a map - the fact that it is recursive is an implementation detail. It's like writing a `max` function for a collection and calling it `nextMax` simply because you wrote it with explicit recursion. Why not just call it `optionMap`? – itsbruce Aug 18 '15 at 13:18
  • 4
    @itsbruce I just want to add to/modify your point--it would be most "proper" from a readability/maintainability to define `listToOptionMap(lst:List[String])` with the function `nextOption` defined within that, with a final line saying `return nextOption(Map(), lst)`. That said, I have to confess that I've made much more egregious shortcuts in my time than the one in this answer. – tresbot Aug 19 '15 at 05:38
  • 7
    @theMadKing in the code above `exit(1)` may need to be `sys.exit(1)` – tresbot Aug 19 '15 at 05:52
  • 4
    I like your solution. Here's the modification to handle multiple `file` parameters: `case string :: tail => { if (isSwitch(string)) { println("Unknown option: " + string) sys.exit(1) } else nextOption(map ++ Map('files -> (string :: map('files).asInstanceOf[List[String]])), tail)`. The map also needs a default value of `Nil`, i.e. `val options = nextOption(Map() withDefaultValue Nil, args.toList)`. What I don't like is having to resort to `asInstanceOf`, due to the `OptionMap` values being of type `Any`. Is there a better solution? – Mauro Lacy Oct 13 '16 at 17:44
  • 2
    Here is an addition to handle a flag, i.e., a command line argument with no associated value: `case "--verbose" :: tail => nextOption(map ++ Map('verbose -> true), tail)`. Later we can get a boolean whether the flag is present or not with: `val verbose = options.contains('verbose)`. – Jorge Nov 16 '16 at 23:04
  • I do not get the case string :: opt2 :: tail if isSwitch(opt2) : would it mean that the filename is provided before another option "opt2" ? when is this opt2 parsed ? – Antonin Mar 06 '17 at 15:21
  • 1
    I like this solution since it doesn't require using any external libraries and it is still idiomatic and concise. To address the issue @MauroLacy brought up, I might define an inner singleton object to hold the arguments with their types instead of using a Map. – DBear Jun 02 '22 at 16:32
207

scopt/scopt

val parser = new scopt.OptionParser[Config]("scopt") {
  head("scopt", "3.x")

  opt[Int]('f', "foo") action { (x, c) =>
    c.copy(foo = x) } text("foo is an integer property")

  opt[File]('o', "out") required() valueName("<file>") action { (x, c) =>
    c.copy(out = x) } text("out is a required file property")

  opt[(String, Int)]("max") action { case ((k, v), c) =>
    c.copy(libName = k, maxCount = v) } validate { x =>
    if (x._2 > 0) success
    else failure("Value <max> must be >0") 
  } keyValueName("<libname>", "<max>") text("maximum count for <libname>")

  opt[Unit]("verbose") action { (_, c) =>
    c.copy(verbose = true) } text("verbose is a flag")

  note("some notes.\n")

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

  arg[File]("<file>...") unbounded() optional() action { (x, c) =>
    c.copy(files = c.files :+ x) } text("optional unbounded args")

  cmd("update") action { (_, c) =>
    c.copy(mode = "update") } text("update is a command.") children(
    opt[Unit]("not-keepalive") abbr("nk") action { (_, c) =>
      c.copy(keepalive = false) } text("disable keepalive"),
    opt[Boolean]("xyz") action { (x, c) =>
      c.copy(xyz = x) } text("xyz is a boolean property")
  )
}
// parser.parse returns Option[C]
parser.parse(args, Config()) map { config =>
  // do stuff
} getOrElse {
  // arguments are bad, usage message will have been displayed
}

The above generates the following usage text:

scopt 3.x
Usage: scopt [update] [options] [<file>...]

  -f <value> | --foo <value>
        foo is an integer property
  -o <file> | --out <file>
        out is a required file property
  --max:<libname>=<max>
        maximum count for <libname>
  --verbose
        verbose is a flag
some notes.

  --help
        prints this usage text
  <file>...
        optional unbounded args

Command: update
update is a command.

  -nk | --not-keepalive
        disable keepalive    
  --xyz <value>
        xyz is a boolean property

This is what I currently use. Clean usage without too much baggage. (Disclaimer: I now maintain this project)

Eugene Yokota
  • 94,654
  • 45
  • 215
  • 319
  • 6
    I like the builder pattern DSL much better, because it enables delegation of parameter construction to modules. – Daniel C. Sobral Feb 23 '10 at 12:05
  • 3
    Note: unlike shown, scopt doesn't need that many type annotations. – Blaisorblade Jul 22 '12 at 14:41
  • scopt and scallop are available in Maven Central; the others don't seem to be (I couldn't find them). Also, it looks like paulp/optional has become alexy/optional. – Jim Pivarski Aug 13 '14 at 21:51
  • does scopt project has some document? – Rollen Holt Jan 04 '15 at 09:08
  • 1
    Just wanted to point out that Argot is definitely on Maven Central (have been using it for a few years now). – Tomer Gabel Feb 01 '15 at 12:38
  • 11
    If you're using this for parsing args for a spark job, be warned that they don't play nicely together. Literally nothing I tried could get spark-submit to work with scopt :-( – jbrown Mar 06 '15 at 13:12
  • 2
    @jbrown I found it interesting that one of the top search results for scopt usage in github is in [Spark](https://github.com/apache/spark/blob/master/examples/src/main/scala/org/apache/spark/examples/mllib/SparseNaiveBayes.scala). But I have no experience with either library, and no idea how incompatible the two are. – beardc Apr 29 '15 at 20:04
  • 5
    @BirdJaguarIV If spark uses scopt that was probably the problem - conflicting versions in the jar or something. I use scallop with spark jobs instead and haven't had any problems. – jbrown Apr 29 '15 at 20:48
  • 17
    Ironically though this library automagically generates good CLI documentation, the code looks little better than brainf*ck. – Coder Guy Jun 15 '15 at 17:08
  • What are the import statements? My IDE can't find "Config". – Andriy Drozdyuk Jan 15 '16 at 20:42
  • 1
    @drozzy See https://github.com/scopt/scopt#usage "Either case, first you need a case class that represents the configuration" – Eugene Yokota Jan 16 '16 at 01:42
  • 1
    @jbrown Not sure if this is still relevant to you, but I just tried scopt with my Spark (2.0.0) application, and it worked just fine for me. – Jack Leow Aug 15 '16 at 23:52
  • 1
    When someone writes an example, its nicer to provide a version that actually compiles. How would I know what opt stands for and what is the package definition for the same? – Arunav Sanyal Dec 11 '16 at 01:20
  • 1
    I prefer scallop (https://github.com/scallop/scallop). Much simpler implementation, cleaner docs. No offense to the maintainers of scopt, I just prefer KISS. – acumartini Dec 23 '16 at 20:04
64

I realize that the question was asked some time ago, but I thought it might help some people, who are googling around (like me), and hit this page.

Scallop looks quite promising as well.

Features (quote from the linked github page):

  • flag, single-value and multiple value options
  • POSIX-style short option names (-a) with grouping (-abc)
  • GNU-style long option names (--opt)
  • Property arguments (-Dkey=value, -D key1=value key2=value)
  • Non-string types of options and properties values (with extendable converters)
  • Powerful matching on trailing args
  • Subcommands

And some example code (also from that Github page):

import org.rogach.scallop._;

object Conf extends ScallopConf(List("-c","3","-E","fruit=apple","7.2")) {
  // all options that are applicable to builder (like description, default, etc) 
  // are applicable here as well
  val count:ScallopOption[Int] = opt[Int]("count", descr = "count the trees", required = true)
                .map(1+) // also here work all standard Option methods -
                         // evaluation is deferred to after option construction
  val properties = props[String]('E')
  // types (:ScallopOption[Double]) can be omitted, here just for clarity
  val size:ScallopOption[Double] = trailArg[Double](required = false)
}


// that's it. Completely type-safe and convenient.
Conf.count() should equal (4)
Conf.properties("fruit") should equal (Some("apple"))
Conf.size.get should equal (Some(7.2))
// passing into other functions
def someInternalFunc(conf:Conf.type) {
  conf.count() should equal (4)
}
someInternalFunc(Conf)
rintcius
  • 3,283
  • 2
  • 19
  • 16
  • 6
    Scallop pwns the rest hands down in terms of features. Shame the usual SO trend of "first answer wins" has pushed this down the list :( – samthebest Aug 16 '15 at 19:31
  • I agree. Leaving a comment here just incase @Eugene Yokota missed to take a note. Check this blog out [scallop](http://blog.rogach.org/2012/04/better-cli-option-parsing-in-scala.html) – Pramit Jan 04 '16 at 17:04
  • 1
    The problem it menthions with scopt is "It looks good, but is unable to parse options, which take a list of arguments (i.e. -a 1 2 3). And you have no way to extend it to get those lists (except forking the lib)." but this is no longer true, see https://github.com/scopt/scopt#options. – Alexey Romanov Jun 30 '16 at 09:54
  • 4
    this is more intuitive and less boilerplate than scopt. no more `(x, c) => c.copy(xyz = x) ` in scopt – WeiChing 林煒清 Feb 02 '17 at 14:34
58

I like sliding over arguments for relatively simple configurations.

var name = ""
var port = 0
var ip = ""
args.sliding(2, 2).toList.collect {
  case Array("--ip", argIP: String) => ip = argIP
  case Array("--port", argPort: String) => port = argPort.toInt
  case Array("--name", argName: String) => name = argName
}
joslinm
  • 7,845
  • 6
  • 49
  • 72
19

Command Line Interface Scala Toolkit (CLIST)

here is mine too! (a bit late in the game though)

https://github.com/backuity/clist

As opposed to scopt it is entirely mutable... but wait! That gives us a pretty nice syntax:

class Cat extends Command(description = "concatenate files and print on the standard output") {

  // type-safety: members are typed! so showAll is a Boolean
  var showAll        = opt[Boolean](abbrev = "A", description = "equivalent to -vET")
  var numberNonblank = opt[Boolean](abbrev = "b", description = "number nonempty output lines, overrides -n")

  // files is a Seq[File]
  var files          = args[Seq[File]](description = "files to concat")
}

And a simple way to run it:

Cli.parse(args).withCommand(new Cat) { case cat =>
    println(cat.files)
}

You can do a lot more of course (multi-commands, many configuration options, ...) and has no dependency.

I'll finish with a kind of distinctive feature, the default usage (quite often neglected for multi commands): clist

Bruno Bieth
  • 2,317
  • 20
  • 31
  • Does it have validation? – K F Nov 14 '17 at 02:13
  • Yes it does (see https://github.com/backuity/clist/blob/master/demo/src/main/scala/PuppetModuleInstaller.scala#L18 for an example). It's not documented though... PR? :) – Bruno Bieth Nov 14 '17 at 07:24
  • Tried it, pretty convenient. I used scopt before, I'm still not get used to add validations together, but not just in each parameter's definition. But it works fine with me. And defining different parameters and validations in different traits, then combining them in different cases, that's real helpful. I suffered a lot in scopt when it's not convenient to reuse parameters. Thanks for reply! – K F Nov 14 '17 at 09:28
  • Most validations are performed during the deserialization of the command line parameters (see [Read](https://github.com/backuity/clist/blob/master/core/src/main/scala/org/backuity/clist/util/Read.scala)), so if you can define your validation constraints through types (i.e `Password`, `Hex`, ...), then you can leverage this. – Bruno Bieth Nov 14 '17 at 18:55
14

How to parse parameters without an external dependency. Great question! You may be interested in picocli.

Picocli is specifically designed to solve the problem asked in the question: it is a command line parsing framework in a single file, so you can include it in source form. This lets users run picocli-based applications without requiring picocli as an external dependency.

It works by annotating fields so you write very little code. Quick summary:

  • Strongly typed everything - command line options as well as positional parameters
  • Support for POSIX clustered short options (so it handles <command> -xvfInputFile as well as <command> -x -v -f InputFile)
  • An arity model that allows a minimum, maximum and variable number of parameters, e.g, "1..*", "3..5"
  • Fluent and compact API to minimize boilerplate client code
  • Subcommands
  • Usage help with ANSI colors

The usage help message is easy to customize with annotations (without programming). For example:

Extended usage help message (source)

I couldn't resist adding one more screenshot to show what kind of usage help messages are possible. Usage help is the face of your application, so be creative and have fun!

picocli demo

Disclaimer: I created picocli. Feedback or questions very welcome. It is written in java, but let me know if there is any issue using it in scala and I'll try to address it.

Remko Popma
  • 35,130
  • 11
  • 92
  • 114
13

This is largely a shameless clone of my answer to the Java question of the same topic. It turns out that JewelCLI is Scala-friendly in that it doesn't require JavaBean style methods to get automatic argument naming.

JewelCLI is a Scala-friendly Java library for command-line parsing that yields clean code. It uses Proxied Interfaces Configured with Annotations to dynamically build a type-safe API for your command-line parameters.

An example parameter interface Person.scala:

import uk.co.flamingpenguin.jewel.cli.Option

trait Person {
  @Option def name: String
  @Option def times: Int
}

An example usage of the parameter interface Hello.scala:

import uk.co.flamingpenguin.jewel.cli.CliFactory.parseArguments
import uk.co.flamingpenguin.jewel.cli.ArgumentValidationException

object Hello {
  def main(args: Array[String]) {
    try {
      val person = parseArguments(classOf[Person], args:_*)
      for (i <- 1 to (person times))
        println("Hello " + (person name))
    } catch {
      case e: ArgumentValidationException => println(e getMessage)
    }
  }
}

Save copies of the files above to a single directory and download the JewelCLI 0.6 JAR to that directory as well.

Compile and run the example in Bash on Linux/Mac OS X/etc.:

scalac -cp jewelcli-0.6.jar:. Person.scala Hello.scala
scala -cp jewelcli-0.6.jar:. Hello --name="John Doe" --times=3

Compile and run the example in the Windows Command Prompt:

scalac -cp jewelcli-0.6.jar;. Person.scala Hello.scala
scala -cp jewelcli-0.6.jar;. Hello --name="John Doe" --times=3

Running the example should yield the following output:

Hello John Doe
Hello John Doe
Hello John Doe
Community
  • 1
  • 1
Alain O'Dea
  • 21,033
  • 1
  • 58
  • 84
  • One piece of fun in this you may notice is the (args : _*). Calling Java varargs methods from Scala requires this. This is a solution I learned from http://daily-scala.blogspot.com/2009/11/varargs.html on Jesse Eichar's excellent Daily Scala blog. I highly recommend Daily Scala :) – Alain O'Dea Jul 26 '10 at 23:44
13

I am from Java world, I like args4j because its simple, specification is more readable( thanks to annotations) and produces nicely formatted output.

Here is my example snippet:

Specification

import org.kohsuke.args4j.{CmdLineException, CmdLineParser, Option}

object CliArgs {

  @Option(name = "-list", required = true,
    usage = "List of Nutch Segment(s) Part(s)")
  var pathsList: String = null

  @Option(name = "-workdir", required = true,
    usage = "Work directory.")
  var workDir: String = null

  @Option(name = "-master",
    usage = "Spark master url")
  var masterUrl: String = "local[2]"

}

Parse

//var args = "-listt in.txt -workdir out-2".split(" ")
val parser = new CmdLineParser(CliArgs)
try {
  parser.parseArgument(args.toList.asJava)
} catch {
  case e: CmdLineException =>
    print(s"Error:${e.getMessage}\n Usage:\n")
    parser.printUsage(System.out)
    System.exit(1)
}
println("workDir  :" + CliArgs.workDir)
println("listFile :" + CliArgs.pathsList)
println("master   :" + CliArgs.masterUrl)

On invalid arguments

Error:Option "-list" is required
 Usage:
 -list VAL    : List of Nutch Segment(s) Part(s)
 -master VAL  : Spark master url (default: local[2])
 -workdir VAL : Work directory.
Thamme Gowda
  • 11,249
  • 5
  • 50
  • 57
11

scala-optparse-applicative

I think scala-optparse-applicative is the most functional command line parser library in Scala.

https://github.com/bmjames/scala-optparse-applicative

Kenji Yoshida
  • 3,108
  • 24
  • 39
9

I liked the slide() approach of joslinm just not the mutable vars ;) So here's an immutable way to that approach:

case class AppArgs(
              seed1: String,
              seed2: String,
              ip: String,
              port: Int
              )
object AppArgs {
  def empty = new AppArgs("", "", "", 0)
}

val args = Array[String](
  "--seed1", "akka.tcp://seed1",
  "--seed2", "akka.tcp://seed2",
  "--nodeip", "192.167.1.1",
  "--nodeport", "2551"
)

val argsInstance = args.sliding(2, 1).toList.foldLeft(AppArgs.empty) { case (accumArgs, currArgs) => currArgs match {
    case Array("--seed1", seed1) => accumArgs.copy(seed1 = seed1)
    case Array("--seed2", seed2) => accumArgs.copy(seed2 = seed2)
    case Array("--nodeip", ip) => accumArgs.copy(ip = ip)
    case Array("--nodeport", port) => accumArgs.copy(port = port.toInt)
    case unknownArg => accumArgs // Do whatever you want for this case
  }
}
haggy
  • 733
  • 6
  • 11
8

There's also JCommander (disclaimer: I created it):

object Main {
  object Args {
    @Parameter(
      names = Array("-f", "--file"),
      description = "File to load. Can be specified multiple times.")
    var file: java.util.List[String] = null
  }

  def main(args: Array[String]): Unit = {
    new JCommander(Args, args.toArray: _*)
    for (filename <- Args.file) {
      val f = new File(filename)
      printf("file: %s\n", f.getName)
    }
  }
}
Cedric Beust
  • 15,480
  • 2
  • 55
  • 55
  • 2
    i like this one. those 'pure scala' parsers lack a clean syntax – tactoth Jul 07 '15 at 08:14
  • @tactoth check this one, it has a clear syntax: http://stackoverflow.com/questions/2315912/scala-best-way-to-parse-command-line-parameters-cli/34748336#34748336 – Bruno Bieth Jan 12 '16 at 16:11
3

I've attempted generalize @pjotrp's solution by taking in a list of required positional key symbols, a map of flag -> key symbol and default options:

def parseOptions(args: List[String], required: List[Symbol], optional: Map[String, Symbol], options: Map[Symbol, String]): Map[Symbol, String] = {
  args match {
    // Empty list
    case Nil => options

    // Keyword arguments
    case key :: value :: tail if optional.get(key) != None =>
      parseOptions(tail, required, optional, options ++ Map(optional(key) -> value))

    // Positional arguments
    case value :: tail if required != Nil =>
      parseOptions(tail, required.tail, optional, options ++ Map(required.head -> value))

    // Exit if an unknown argument is received
    case _ =>
      printf("unknown argument(s): %s\n", args.mkString(", "))
      sys.exit(1)
  }
}

def main(sysargs Array[String]) {
  // Required positional arguments by key in options
  val required = List('arg1, 'arg2)

  // Optional arguments by flag which map to a key in options
  val optional = Map("--flag1" -> 'flag1, "--flag2" -> 'flag2)

  // Default options that are passed in
  var defaultOptions = Map()

  // Parse options based on the command line args
  val options = parseOptions(sysargs.toList, required, optional, defaultOptions)
}
Byron Ruth
  • 927
  • 6
  • 15
  • I updated this piece of code to handle flags (not just options with values) and also to mandle defining the option/flag with short and long forms. e.g. `-f|--flags`. Take a look at https://gist.github.com/DavidGamba/b3287d40b019e498982c and feel free to update the answer if you like it. I will probably will make every Map and option so you can only pass what you will need with named arguments. – DavidGamba May 22 '14 at 05:16
3

I have never liked ruby like option parsers. Most developers that used them never write a proper man page for their scripts and end up with pages long options not organized in a proper way because of their parser.

I have always preferred Perl's way of doing things with Perl's Getopt::Long.

I am working on a scala implementation of it. The early API looks something like this:

def print_version() = () => println("version is 0.2")

def main(args: Array[String]) {
  val (options, remaining) = OptionParser.getOptions(args,
    Map(
      "-f|--flag"       -> 'flag,
      "-s|--string=s"   -> 'string,
      "-i|--int=i"      -> 'int,
      "-f|--float=f"    -> 'double,
      "-p|-procedure=p" -> { () => println("higher order function" }
      "-h=p"            -> { () => print_synopsis() }
      "--help|--man=p"  -> { () => launch_manpage() },
      "--version=p"     -> print_version,
    ))

So calling script like this:

$ script hello -f --string=mystring -i 7 --float 3.14 --p --version world -- --nothing

Would print:

higher order function
version is 0.2

And return:

remaining = Array("hello", "world", "--nothing")

options = Map('flag   -> true,
              'string -> "mystring",
              'int    -> 7,
              'double -> 3.14)

The project is hosted in github scala-getoptions.

DavidGamba
  • 3,503
  • 2
  • 30
  • 46
3

I'd suggest to use http://docopt.org/. There's a scala-port but the Java implementation https://github.com/docopt/docopt.java works just fine and seems to be better maintained. Here's an example:

import org.docopt.Docopt

import scala.collection.JavaConversions._
import scala.collection.JavaConverters._

val doc =
"""
Usage: my_program [options] <input>

Options:
 --sorted   fancy sorting
""".stripMargin.trim

//def args = "--sorted test.dat".split(" ").toList
var results = new Docopt(doc).
  parse(args()).
  map {case(key, value)=>key ->value.toString}

val inputFile = new File(results("<input>"))
val sorted = results("--sorted").toBoolean
Holger Brandl
  • 10,634
  • 3
  • 64
  • 63
3

This is what I cooked. It returns a tuple of a map and a list. List is for input, like input file names. Map is for switches/options.

val args = "--sw1 1 input_1 --sw2 --sw3 2 input_2 --sw4".split(" ")
val (options, inputs) = OptParser.parse(args)

will return

options: Map[Symbol,Any] = Map('sw1 -> 1, 'sw2 -> true, 'sw3 -> 2, 'sw4 -> true)
inputs: List[Symbol] = List('input_1, 'input_2)

Switches can be "--t" which x will be set to true, or "--x 10" which x will be set to "10". Everything else will end up in list.

object OptParser {
  val map: Map[Symbol, Any] = Map()
  val list: List[Symbol] = List()

  def parse(args: Array[String]): (Map[Symbol, Any], List[Symbol]) = _parse(map, list, args.toList)

  private [this] def _parse(map: Map[Symbol, Any], list: List[Symbol], args: List[String]): (Map[Symbol, Any], List[Symbol]) = {
    args match {
      case Nil => (map, list)
      case arg :: value :: tail if (arg.startsWith("--") && !value.startsWith("--")) => _parse(map ++ Map(Symbol(arg.substring(2)) -> value), list, tail)
      case arg :: tail if (arg.startsWith("--")) => _parse(map ++ Map(Symbol(arg.substring(2)) -> true), list, tail)
      case opt :: tail => _parse(map, list :+ Symbol(opt), tail)
    }
  }
}
auselen
  • 27,577
  • 7
  • 73
  • 114
3

I've just found an extensive command line parsing library in scalac's scala.tools.cmd package.

See http://www.assembla.com/code/scala-eclipse-toolchain/git/nodes/src/compiler/scala/tools/cmd?rev=f59940622e32384b1e08939effd24e924a8ba8db

Pablo Lalloni
  • 2,615
  • 19
  • 20
3

I based my approach on the top answer (from dave4420), and tried to improve it by making it more general-purpose.

It returns a Map[String,String] of all command line parameters You can query this for the specific parameters you want (eg using .contains) or convert the values into the types you want (eg using toInt).

def argsToOptionMap(args:Array[String]):Map[String,String]= {
  def nextOption(
      argList:List[String], 
      map:Map[String, String]
    ) : Map[String, String] = {
    val pattern       = "--(\\w+)".r // Selects Arg from --Arg
    val patternSwitch = "-(\\w+)".r  // Selects Arg from -Arg
    argList match {
      case Nil => map
      case pattern(opt)       :: value  :: tail => nextOption( tail, map ++ Map(opt->value) )
      case patternSwitch(opt) :: tail => nextOption( tail, map ++ Map(opt->null) )
      case string             :: Nil  => map ++ Map(string->null)
      case option             :: tail => {
        println("Unknown option:"+option) 
        sys.exit(1)
      }
    }
  }
  nextOption(args.toList,Map())
}

Example:

val args=Array("--testing1","testing1","-a","-b","--c","d","test2")
argsToOptionMap( args  )

Gives:

res0: Map[String,String] = Map(testing1 -> testing1, a -> null, b -> null, c -> d, test2 -> null)
bjorno
  • 51
  • 4
2

I like the clean look of this code... gleaned from a discussion here: http://www.scala-lang.org/old/node/4380

object ArgParser {
  val usage = """
Usage: parser [-v] [-f file] [-s sopt] ...
Where: -v   Run verbosely
       -f F Set input file to F
       -s S Set Show option to S
"""

  var filename: String = ""
  var showme: String = ""
  var debug: Boolean = false
  val unknown = "(^-[^\\s])".r

  val pf: PartialFunction[List[String], List[String]] = {
    case "-v" :: tail => debug = true; tail
    case "-f" :: (arg: String) :: tail => filename = arg; tail
    case "-s" :: (arg: String) :: tail => showme = arg; tail
    case unknown(bad) :: tail => die("unknown argument " + bad + "\n" + usage)
  }

  def main(args: Array[String]) {
    // if there are required args:
    if (args.length == 0) die()
    val arglist = args.toList
    val remainingopts = parseArgs(arglist,pf)

    println("debug=" + debug)
    println("showme=" + showme)
    println("filename=" + filename)
    println("remainingopts=" + remainingopts)
  }

  def parseArgs(args: List[String], pf: PartialFunction[List[String], List[String]]): List[String] = args match {
    case Nil => Nil
    case _ => if (pf isDefinedAt args) parseArgs(pf(args),pf) else args.head :: parseArgs(args.tail,pf)
  }

  def die(msg: String = usage) = {
    println(msg)
    sys.exit(1)
  }

}
Alan Jurgensen
  • 813
  • 11
  • 20
2

I just created my simple enumeration

val args: Array[String] = "-silent -samples 100 -silent".split(" +").toArray
                                              //> args  : Array[String] = Array(-silent, -samples, 100, -silent)
object Opts extends Enumeration {

    class OptVal extends Val {
        override def toString = "-" + super.toString
    }

    val nopar, silent = new OptVal() { // boolean options
        def apply(): Boolean = args.contains(toString)
    }

    val samples, maxgen = new OptVal() { // integer options
        def apply(default: Int) = { val i = args.indexOf(toString) ;  if (i == -1) default else args(i+1).toInt}
        def apply(): Int = apply(-1)
    }
}

Opts.nopar()                              //> res0: Boolean = false
Opts.silent()                             //> res1: Boolean = true
Opts.samples()                            //> res2: Int = 100
Opts.maxgen()                             //> res3: Int = -1

I understand that solution has two major flaws that may distract you: It eliminates the freedom (i.e. the dependence on other libraries, that you value so much) and redundancy (the DRY principle, you do type the option name only once, as Scala program variable and eliminate it second time typed as command line text).

Community
  • 1
  • 1
Val
  • 1
  • 8
  • 40
  • 64
2

another library: scarg

Anonymous
  • 21
  • 1
2

Here's a scala command line parser that is easy to use. It automatically formats help text, and it converts switch arguments to your desired type. Both short POSIX, and long GNU style switches are supported. Supports switches with required arguments, optional arguments, and multiple value arguments. You can even specify the finite list of acceptable values for a particular switch. Long switch names can be abbreviated on the command line for convenience. Similar to the option parser in the Ruby standard library.

sellmerfud
  • 340
  • 2
  • 10
1

As everyone posted it's own solution here is mine, cause I wanted something easier to write for the user : https://gist.github.com/gwenzek/78355526e476e08bb34d

The gist contains a code file, plus a test file and a short example copied here:

import ***.ArgsOps._


object Example {
    val parser = ArgsOpsParser("--someInt|-i" -> 4, "--someFlag|-f", "--someWord" -> "hello")

    def main(args: Array[String]){
        val argsOps = parser <<| args
        val someInt : Int = argsOps("--someInt")
        val someFlag : Boolean = argsOps("--someFlag")
        val someWord : String = argsOps("--someWord")
        val otherArgs = argsOps.args

        foo(someWord, someInt, someFlag)
    }
}

There is not fancy options to force a variable to be in some bounds, cause I don't feel that the parser is the best place to do so.

Note : you can have as much alias as you want for a given variable.

gwenzek
  • 2,816
  • 21
  • 21
1

I'm going to pile on. I solved this with a simple line of code. My command line arguments look like this:

input--hdfs:/path/to/myData/part-00199.avro output--hdfs:/path/toWrite/Data fileFormat--avro option1--5

This creates an array via Scala's native command line functionality (from either App or a main method):

Array("input--hdfs:/path/to/myData/part-00199.avro", "output--hdfs:/path/toWrite/Data","fileFormat--avro","option1--5")

I can then use this line to parse out the default args array:

val nArgs = args.map(x=>x.split("--")).map(y=>(y(0),y(1))).toMap

Which creates a map with names associated with the command line values:

Map(input -> hdfs:/path/to/myData/part-00199.avro, output -> hdfs:/path/toWrite/Data, fileFormat -> avro, option1 -> 5)

I can then access the values of named parameters in my code and the order they appear on the command line is no longer relevant. I realize this is fairly simple and doesn't have all the advanced functionality mentioned above but seems to be sufficient in most cases, only needs one line of code, and doesn't involve external dependencies.

J Calbreath
  • 2,665
  • 4
  • 22
  • 31
1

Here is mine 1-liner

    def optArg(prefix: String) = args.drop(3).find { _.startsWith(prefix) }.map{_.replaceFirst(prefix, "")}
    def optSpecified(prefix: String) = optArg(prefix) != None
    def optInt(prefix: String, default: Int) = optArg(prefix).map(_.toInt).getOrElse(default)

It drops 3 mandatory arguments and gives out the options. Integers are specified like notorious -Xmx<size> java option, jointly with the prefix. You can parse binaries and integers as simple as

val cacheEnabled = optSpecified("cacheOff")
val memSize = optInt("-Xmx", 1000)

No need to import anything.

0

Poor man's quick-and-dirty one-liner for parsing key=value pairs:

def main(args: Array[String]) {
    val cli = args.map(_.split("=") match { case Array(k, v) => k->v } ).toMap
    val saveAs = cli("saveAs")
    println(saveAs)
}
botkop
  • 934
  • 1
  • 8
  • 17
0

freecli

package freecli
package examples
package command

import java.io.File

import freecli.core.all._
import freecli.config.all._
import freecli.command.all._

object Git extends App {

  case class CommitConfig(all: Boolean, message: String)
  val commitCommand =
    cmd("commit") {
      takesG[CommitConfig] {
        O.help --"help" ::
        flag --"all" -'a' -~ des("Add changes from all known files") ::
        O.string -'m' -~ req -~ des("Commit message")
      } ::
      runs[CommitConfig] { config =>
        if (config.all) {
          println(s"Commited all ${config.message}!")
        } else {
          println(s"Commited ${config.message}!")
        }
      }
    }

  val rmCommand =
    cmd("rm") {
      takesG[File] {
        O.help --"help" ::
        file -~ des("File to remove from git")
      } ::
      runs[File] { f =>
        println(s"Removed file ${f.getAbsolutePath} from git")
      }
    }

  val remoteCommand =
   cmd("remote") {
     takes(O.help --"help") ::
     cmd("add") {
       takesT {
         O.help --"help" ::
         string -~ des("Remote name") ::
         string -~ des("Remote url")
       } ::
       runs[(String, String)] {
         case (s, u) => println(s"Remote $s $u added")
       }
     } ::
     cmd("rm") {
       takesG[String] {
         O.help --"help" ::
         string -~ des("Remote name")
       } ::
       runs[String] { s =>
         println(s"Remote $s removed")
       }
     }
   }

  val git =
    cmd("git", des("Version control system")) {
      takes(help --"help" :: version --"version" -~ value("v1.0")) ::
      commitCommand ::
      rmCommand ::
      remoteCommand
    }

  val res = runCommandOrFail(git)(args).run
}

This will generate the following usage:

Usage

pavlosgi
  • 9
  • 4