在Scala中解析命令行参数的最佳方法是什么? 我个人更喜欢一些不需要外部罐子的轻量级的东西。
相关:
我如何解析Java中的命令行参数? c++有哪些参数解析器库? 在c#中解析命令行参数的最佳方法
在Scala中解析命令行参数的最佳方法是什么? 我个人更喜欢一些不需要外部罐子的轻量级的东西。
相关:
我如何解析Java中的命令行参数? c++有哪些参数解析器库? 在c#中解析命令行参数的最佳方法
当前回答
如何在没有外部依赖的情况下解析参数。好问题!你可能会对picocli感兴趣。
Picocli是专门为解决问题而设计的:它是一个文件中的命令行解析框架,因此可以以源代码形式包含它。这允许用户运行基于picocli的应用程序,而不需要将picocli作为外部依赖项。
它通过注释字段来工作,因此您只需编写很少的代码。快速总结:
强类型的一切-命令行选项以及位置参数 支持POSIX集群短选项(因此它处理<命令> -xvfInputFile以及<命令> -x -v -f InputFile) 一个允许最小、最大和可变数量参数的arity模型,例如,“1..3 . . *”、“5” 流畅和紧凑的API,以尽量减少样板客户端代码 子命令 使用ANSI颜色的帮助
使用帮助消息很容易使用注释进行定制(无需编程)。例如:
(源)
我忍不住又加了一张截图来展示使用帮助信息的类型。使用帮助是应用程序的门面,所以要有创意,玩得开心!
声明:我创建了picocli。欢迎反馈或提问。它是用java编写的,但如果在scala中使用它有任何问题,请告诉我,我会尝试解决它。
其他回答
这是我的一行字
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)
它删除了3个强制参数,并给出了选项。整数被指定为臭名昭著的-Xmx<size> java选项,加上前缀。您可以像这样简单地解析二进制和整数
val cacheEnabled = optSpecified("cacheOff")
val memSize = optInt("-Xmx", 1000)
不需要进口任何东西。
对于大多数情况,您不需要外部解析器。Scala的模式匹配允许以函数式风格使用参数。例如:
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)
}
}
将打印,例如:
Map('infile -> test/data/paml-aln1.phy, 'maxsize -> 4, 'minsize -> 2)
这个版本只需要一个文件。很容易改进(通过使用列表)。
还要注意,这种方法允许连接多个命令行参数——甚至超过两个!
斯科普特/斯科普特
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
}
上面生成了以下用法文本:
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
这是我目前使用的。使用干净,没有太多包袱。 (免责声明:我现在维护这个项目)
我只是创建了简单的枚举
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
我知道这个解决方案有两个主要的缺陷可能会让你分心:它消除了自由(即对其他库的依赖,你非常重视)和冗余(DRY原则,你只输入一次选项名称,作为Scala程序变量,并在第二次输入命令行文本时消除它)。
命令行接口Scala Toolkit (CLIST)
这也是我的!(虽然有点晚了)
https://github.com/backuity/clist
与scopt相反,它是完全可变的…但是等等!这给了我们一个很好的语法:
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")
}
还有一个简单的运行方法:
Cli.parse(args).withCommand(new Cat) { case cat =>
println(cat.files)
}
当然,您可以做更多的事情(多命令,许多配置选项,……),并且没有依赖性。
我将以一种独特的特性来结束,默认用法(对于多个命令经常被忽略):