What's the best way to parse command-line parameters in Scala? I personally prefer something lightweight that does not require external jar.
Related:
What's the best way to parse command-line parameters in Scala? I personally prefer something lightweight that does not require external jar.
Related:
Mailing list thread Scala CLI Library? lists
By using reflection it automatically calls the main with optional argument:
object MyAwesomeCommandLineTool extends optional.Application {
// for instance...
def main(count: Option[Int], file: Option[java.io.File], arg1: String) {
[...]
}
}
In terms of the usage code, this is the most elegant, but it looks like it has a dependency on paranamer-1.3.jar. Also it requires inheriting from optional.Application
.
val parser = new OptionParser {
intOpt("f", "foo", "foo is an integer property", {v: Int => config.foo = v})
opt("b", "bar", "bar is a string property", {v: String => config.bar = v})
booleanOpt("x", "xyz", "xyz is a boolean property", {v: Boolean => config.xyz = v})
arg("whatnot", "some argument", {v: String => config.whatnot = v})
}
if (parser.parse(args)) {
// do stuff
}
else {
// arguments are bad, usage message will have been displayed
}
This one looks the most promising to me. Clean usage without too much baggage. (Disclaimer: I now contribute to this project)
And there's also
def main(args: Array[String]) = {
val helpString = " -p1 out.txt -p2 22 [ -p3 100 -p4 1200 ] "
val pp = new ParseParms( helpString )
pp.parm("-p1", "output.txt").rex("^.*\\.txt$").req(true) // required
.parm("-p2", "22","^\\d{2}$",true) // alternate form, required
.parm("-p3","100").rex("^\\d{3}$") // optional
.parm("-p4","1200").rex("^\\d{4}$").req(false) // optional
val result = pp.validate( args.toList )
println( if( result._1 ) result._3 else result._2 )
// result is a tuple (Boolean, String, Map)
// ._1 Boolean; false: error String contained in ._2, Map in ._3 is empty
// true: successful, Map of parsed & merged parms in ._3
System.exit(0)
}
Single file. Very lightweight, but I don't like the whole builder pattern DSL thing compared to scopt.
I think this is for OSGi shells, but jstrachan told me about cool attribute notations by Apache Felix Karaf.
import org.apache.felix.gogo.commands.{Action, Option => option, Argument => argument, Command => command}
import org.osgi.service.command.CommandSession
@command(scope = "scalate", name = "jsp2ssp", description = "Converts JSP files to SSP files")
class JspConvert extends Runnable with Action {
@argument(index = 0, name = "dir", description = "Root of the directory containing the JSP files.")
var dir: File = new File(".")
@option(name = "--extension", description = "Extension for output files")
var outputExtension = ".ssp"
@option(name = "--recursion", description = "The number of directroy levels to recusively scan file input files.")
var recursionDepth = -1
}
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!
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
I've just found an extensive command line parsing library in scalac's scala.tools.cmd package.