关于scala:解析命令行参数的最佳方法?

Best way to parse command-line parameters?

在scala中解析命令行参数的最佳方法是什么?我个人更喜欢不需要外部罐子的轻便的东西。

相关:

  • 如何解析Java中的命令行参数?
  • C++中有哪些参数解析器库?
  • 解析C中命令行参数的最佳方法#

你务必用例不需要外部的解析器。Scala的模式匹配的消费参数允许在一个功能性的风格。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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)
  }
}

将打印,例如:

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

这一版本只需要导入导出。容易提高在线(市采用A表)。

注意,这个方法也允许对多个命令行concatenation题元甚至超过两!


scopt / scopt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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.
"
)

  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
}

在以上generates使用下面的文字:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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

这是我目前使用的。使用干净的,没有太多的行李。 (disclaimer:保持在现在这个项目)


在意识到这是个问题,我问几小时前,但我想,它可能会帮助一些人,谁是在googling(像我)和点击本页。

看了很长promising扇贝为好。

特征("联quote github页):

  • 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

和一些实例代码(GitHub也从那一页):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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)


对于相对简单的配置,我喜欢在参数上滑动。

1
2
3
4
5
6
7
8
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
}


命令行界面scala工具包(clist)

这也是我的!(不过比赛有点晚)

https://github.com/backuity/clist

scopt相反,它是完全可变的…但是等等!这给了我们一个非常好的语法:

1
2
3
4
5
6
7
8
9
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")
}

以及一种简单的运行方法:

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

当然,您可以执行更多操作(多个命令、许多配置选项等),并且没有依赖关系。

最后,我将介绍一种独特的特性,即默认用法(对于多个命令,经常被忽略):clist


这是一个无耻的克隆largely我回答两个问题:《Java一样的话题。它会暂停,jewelcli Scala中冰友好的,它不需要JavaBeans风格的方法得到参数自动命名。

jewelcli Scala是一个友好的Java库为命令行解析,YIELDS干净的代码。它proxied辨别configured annotations接口与两个动态类型建立一个安全的API为你的命令行参数。

安:以Person.scala界面参数

1
2
3
4
5
6
import uk.co.flamingpenguin.jewel.cli.Option

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

西安example of the usage:Hello.scala界面参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)
    }
  }
}

保存文件副本的以上两个目录和下载一个单一的jewelcli 0.6罐,两个目录为好。

编译和运行的实例在bash在Linux / Mac OS X /等。

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

在编译和运行实例的Windows命令提示:

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

行走的收益应该以下面的输出:

1
2
3
Hello John Doe
Hello John Doe
Hello John Doe


scala optparse应用程序

我认为scala optparse applicative是scala中功能最强大的命令行解析器库。

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


我来自Java世界,我喜欢Ags4J,因为它的简单、规范更可读(感谢注释),并产生了良好的格式化输出。

下面是我的示例片段:

规范

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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]"

}

解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//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}
 Usage:
"
)
    parser.printUsage(System.out)
    System.exit(1)
}
println("workDir  :" + CliArgs.workDir)
println("listFile :" + CliArgs.pathsList)
println("master   :" + CliArgs.masterUrl)

关于无效参数

1
2
3
4
5
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.

我们的jcommander(也disclaimer:创造了它):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
"
, f.getName)
    }
  }
}


如何解析没有外部依赖关系的参数。好问题!你可能对皮科利感兴趣。

Picocli是专门为解决这个问题而设计的:它是一个命令行解析框架,位于单个文件中,因此您可以将其包含在源代码形式中。这允许用户运行基于Picocli的应用程序,而不需要将Picocli作为外部依赖项。

它通过注释字段来工作,所以您只需编写很少的代码。快速总结:

  • 强类型的所有内容-命令行选项和位置参数
  • 支持POSIX集群短选项(因此它处理 -xvfInputFile -x -v -f InputFile)
  • 一种arity模型,允许最小、最大和可变数量的参数,如"1..*""3..5"
  • 流畅紧凑的API,最大限度地减少样板客户机代码
  • 子命令
  • ANSI颜色的使用帮助

使用帮助消息很容易通过注释进行自定义(无需编程)。例如:

Extended usage help message(来源)

我忍不住再添加一个屏幕截图来显示什么样的使用帮助消息是可能的。使用帮助是你的应用程序的正面,所以要有创意,玩得开心!

picocli demo

免责声明:我创建了Picocli。欢迎反馈或提问。它是用Java编写的,但是让我知道在Scala中是否有使用它的问题,我会尝试解决它。


我喜欢joslim()的slide()方法,但不喜欢可变变量;)所以这里有一个不变的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
  }
}

在我们真的发现是游离在命令行解析图书馆的scala.tools.cmd scalac包。

看到http:/ / / /代码www.assembla.com斯卡拉日全食的Git工具链节点/ / / / / SRC Scala编译器/工具/命令吗?Rev = f59940622e32384b1e08939effd24e924a8ba8db


我把我的方法建立在最上面的答案上(来自Dave4420),并试图通过使其更通用来改进它。

它返回所有命令行参数的Map[String,String]。您可以为此查询所需的特定参数(例如使用.contains或将值转换为所需的类型(例如使用toInt)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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())
}

例子:

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

给予:

1
res0: Map[String,String] = Map(testing1 -> testing1, a -> null, b -> null, c -> d, test2 -> null)

在VC attempted pjotrp generalize"的解决方案,在一个城市,以列表的位置所需的关键符号,图(a ->关键标志符号和违约期权

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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
"
, 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)
}


另一个scarg图书馆


这里的Scala命令行解析器,这是很容易使用。自动文本信息的格式,它的帮助,你converts题元desired开关两种类型。两个短的和长的角马POSIX风格,是开关的负载。支开关与所需的题元,可选题元,题元和多重价值。你可以在有限specify平衡表上可接受的值(特别是开关。大开关与程序的名称可以在命令行的便利。两个相似的期权的Ruby解析器的标准库。


有没有喜欢的Ruby类期权的分析器。开发人员必须使用他们,不会写一页,你为自己的脚本和比IP与pages长键槽的期权在一个适当的组织方式,因为其语法分析器。

有总是优先在Perl的方式"做的事情和Perl的getopt:长。

我工作在一个Scala执行它。一些早期的API,这样的感觉:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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,
    ))

所以script这样的电话:

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

将打印:

1
2
higher order function
version is 0.2

鸭的回报:

1
2
3
4
5
6
remaining = Array("hello","world","--nothing")

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

《冰在GitHub项目主办getoptions斯卡拉。


我刚刚创建了简单的枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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

我知道解决方案有两个主要缺陷,可能会分散您的注意力:它消除了自由(即对其他库的依赖,您非常重视)和冗余(干燥原则,您只需键入一次选项名,作为scala程序变量,然后第二次将其作为命令行文本键入)。


我建议使用http://docopt.org/。有一个Scala端口,但是Java实现HTTPS://GITHUBCOM/Doopt/DooptJAVA工作得很好,并且看起来更好地保持了。下面是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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


喜欢清洁的面貌,本代码。从这里gleaned讨论: www.scala-lang.org http:/ / / / / 4380老节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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 +"
"
+ 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)
  }

}

我要继续干下去。我用一行简单的代码解决了这个问题。我的命令行参数如下:

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

这将通过scala的本机命令行功能(从app或main方法)创建一个数组:

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

然后,我可以使用这一行分析出默认的args数组:

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

它使用与命令行值关联的名称创建映射:

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

然后,我可以访问代码中命名参数的值,它们在命令行上的显示顺序不再相关。我意识到这相当简单,没有上面提到的所有高级功能,但在大多数情况下似乎足够,只需要一行代码,不涉及外部依赖。


这是我的单程客轮

1
2
3
    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个强制参数并给出了选项。整数被指定为臭名昭著的EDCOX1,0,Java选项,与前缀联合。您可以简单地分析二进制文件和整数

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

不需要导入任何内容。


这是我做的。它返回映射和列表的元组。列表用于输入,如输入文件名。地图用于开关/选项。

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

将返回

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

开关可以是"-t",哪个x将被设置为真,或者"-x 10",哪个x将被设置为"10"。其他的一切都会在名单上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)
    }
  }
}

弗雷克利

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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
}

这将生成以下用法:

用法


大家都在这里发布了自己的解决方案,因为我想为用户写些更容易的东西:https://gist.github.com/gwenzek/78355526e476e08bb34d

gist包含一个代码文件,加上一个测试文件和一个复制到这里的简短示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)
    }
}

不存在强制变量在某些边界内的奇特选项,因为我觉得解析器不是最好的选择。

注意:对于给定的变量,您可以有任意多的别名。


可怜的人快速和肮脏的一行分析键=值对:

1
2
3
4
5
def main(args: Array[String]) {
    val cli = args.map(_.split("=") match { case Array(k, v) => k->v } ).toMap
    val saveAs = cli("saveAs")
    println(saveAs)
}