Cách tốt nhất để phân tích các tham số dòng lệnh trong Scala là gì? Cá nhân tôi thích một cái gì đó nhẹ mà không cần bình bên ngoài.
Liên quan:
Cách tốt nhất để phân tích các tham số dòng lệnh trong Scala là gì? Cá nhân tôi thích một cái gì đó nhẹ mà không cần bình bên ngoài.
Liên quan:
Câu trả lời:
Đối với hầu hết các trường hợp, bạn không cần một trình phân tích cú pháp bên ngoài. Kết hợp mẫu của Scala cho phép tiêu thụ args theo kiểu chức năng. Ví dụ:
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)
}
}
sẽ in, ví dụ:
Map('infile -> test/data/paml-aln1.phy, 'maxsize -> 4, 'minsize -> 2)
Phiên bản này chỉ mất một lần. Dễ dàng cải thiện (bằng cách sử dụng Danh sách).
Cũng lưu ý rằng phương pháp này cho phép ghép nhiều đối số dòng lệnh - thậm chí nhiều hơn hai!
nextOption
không phải là một tên tốt cho chức năng. Đây là một hàm trả về bản đồ - thực tế là nó đệ quy là một chi tiết triển khai. Nó giống như viết một max
hàm cho một bộ sưu tập và gọi nó nextMax
đơn giản là vì bạn đã viết nó với đệ quy rõ ràng. Tại sao không chỉ gọi nó optionMap
?
listToOptionMap(lst:List[String])
với chức năng nextOption
được xác định trong đó, với dòng cuối cùng return nextOption(Map(), lst)
. Điều đó nói rằng, tôi phải thú nhận rằng tôi đã tạo ra nhiều phím tắt lớn hơn trong thời gian của mình so với câu trả lời trong câu trả lời này.
exit(1)
có thể cần phải cósys.exit(1)
file
tham số : 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)
. Bản đồ cũng cần một giá trị mặc định là Nil
, tức là val options = nextOption(Map() withDefaultValue Nil, args.toList)
. Những gì tôi không thích là phải dùng đến asInstanceOf
, do các OptionMap
giá trị thuộc loại Any
. Có một giải pháp tốt hơn?
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
}
Ở trên tạo ra văn bản sử dụng sau đây:
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
Đây là những gì tôi hiện đang sử dụng. Sử dụng sạch mà không có quá nhiều hành lý. (Tuyên bố miễn trừ trách nhiệm: Bây giờ tôi duy trì dự án này)
Tôi nhận ra rằng câu hỏi đã được hỏi cách đây một thời gian, nhưng tôi nghĩ rằng nó có thể giúp một số người, những người đang đi vòng quanh (như tôi), và nhấn trang này.
Sò điệp trông cũng khá hứa hẹn.
Các tính năng (trích dẫn từ trang github được liên kết):
- tùy chọn cờ, giá trị đơn và nhiều giá trị
- Tên tùy chọn ngắn kiểu POSIX (-a) với nhóm (-abc)
- Tên tùy chọn dài kiểu GNU (--opt)
- Đối số thuộc tính (-Dkey = value, -D key1 = value key2 = value)
- Các loại tùy chọn và giá trị thuộc tính không phải chuỗi (với bộ chuyển đổi có thể mở rộng)
- Kết hợp mạnh mẽ trên trailing args
- Tiểu ban
Và một số mã ví dụ (cũng từ trang Github đó):
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)
(x, c) => c.copy(xyz = x)
trong scopt
Tôi thích trượt qua các đối số cho các cấu hình tương đối đơn giản.
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
}
args.sliding(2, 2)
?
var port = 0
?
đây cũng là của tôi (một chút muộn trong trò chơi)
https://github.com/backuity/clist
Trái ngược với scopt
nó là hoàn toàn có thể thay đổi ... nhưng chờ đã! Điều đó cho chúng ta một cú pháp khá hay:
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")
}
Và một cách đơn giản để chạy nó:
Cli.parse(args).withCommand(new Cat) { case cat =>
println(cat.files)
}
Tất nhiên bạn có thể làm nhiều hơn nữa (nhiều lệnh, nhiều tùy chọn cấu hình, ...) và không phụ thuộc.
Tôi sẽ kết thúc với một loại tính năng đặc biệt, cách sử dụng mặc định (thường bị bỏ qua cho nhiều lệnh):
Password
, Hex
...), sau đó bạn có thể tận này.
Đây phần lớn là một bản sao không biết xấu hổ trong câu trả lời của tôi cho câu hỏi Java cùng chủ đề . Hóa ra, JewelCLI thân thiện với Scala ở chỗ nó không yêu cầu các phương thức kiểu JavaBean để đặt tên đối số tự động.
JewelCLI là một thư viện Java thân thiện với Scala để phân tích cú pháp dòng lệnh mang lại mã sạch . Nó sử dụng Giao diện được ủy quyền được cấu hình với chú thích để tự động xây dựng API an toàn loại cho các tham số dòng lệnh của bạn.
Một giao diện tham số ví dụ Person.scala
:
import uk.co.flamingpenguin.jewel.cli.Option
trait Person {
@Option def name: String
@Option def times: Int
}
Một ví dụ sử dụng giao diện tham số 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)
}
}
}
Lưu các bản sao của các tệp ở trên vào một thư mục và tải xuống JAR JewelCLI 0.6 vào thư mục đó.
Biên dịch và chạy ví dụ trong Bash trên 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
Biên dịch và chạy ví dụ trong Dấu nhắc lệnh của Windows:
scalac -cp jewelcli-0.6.jar;. Person.scala Hello.scala
scala -cp jewelcli-0.6.jar;. Hello --name="John Doe" --times=3
Chạy ví dụ sẽ mang lại đầu ra sau:
Hello John Doe
Hello John Doe
Hello John Doe
Làm thế nào để phân tích các tham số mà không cần một phụ thuộc bên ngoài. Câu hỏi tuyệt vời! Bạn có thể quan tâm đến picocli .
Picocli được thiết kế đặc biệt để giải quyết vấn đề được hỏi trong câu hỏi: đó là khung phân tích cú pháp dòng lệnh trong một tệp duy nhất, vì vậy bạn có thể đưa nó vào dạng nguồn . Điều này cho phép người dùng chạy các ứng dụng dựa trên picocli mà không yêu cầu picocli như một phụ thuộc bên ngoài .
Nó hoạt động bằng cách chú thích các trường để bạn viết rất ít mã. Tóm tắt nhanh:
<command> -xvfInputFile
cũng như <command> -x -v -f InputFile
)"1..*"
,"3..5"
Thông báo trợ giúp sử dụng dễ dàng tùy chỉnh với các chú thích (không cần lập trình). Ví dụ:
( nguồn )
Tôi không thể cưỡng lại việc thêm một ảnh chụp màn hình để hiển thị loại thông báo trợ giúp sử dụng nào có thể. Trợ giúp sử dụng là bộ mặt của ứng dụng của bạn, vì vậy hãy sáng tạo và vui chơi!
Tuyên bố từ chối trách nhiệm: Tôi đã tạo ra picocli. Phản hồi hoặc câu hỏi rất hoan nghênh. Nó được viết bằng java, nhưng hãy cho tôi biết nếu có bất kỳ vấn đề nào khi sử dụng nó trong scala và tôi sẽ cố gắng giải quyết nó.
Tôi đến từ thế giới Java, tôi thích args4j vì đơn giản, đặc điểm kỹ thuật của nó dễ đọc hơn (nhờ các chú thích) và tạo ra đầu ra được định dạng độc đáo.
Đây là đoạn ví dụ của tôi:
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]"
}
//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)
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.
Tôi nghĩ scala-optparse-applicationative là thư viện trình phân tích cú pháp dòng lệnh có chức năng nhất trong Scala.
examples
tra mã kiểm tra
Ngoài ra còn có JCommander (từ chối trách nhiệm: Tôi đã tạo ra nó):
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)
}
}
}
Tôi thích cách tiếp cận slide () của joslinm chứ không phải là các vars có thể thay đổi;) Vì vậy, đây là một cách bất biến cho cách tiếp cận đó:
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
}
}
Tôi vừa tìm thấy một thư viện phân tích cú pháp dòng lệnh mở rộng trong gói scala.tools.cmd của scalac.
Tôi đã cố gắng khái quát hóa giải pháp của @ pjotrp bằng cách đưa vào danh sách các ký hiệu khóa vị trí bắt buộc, bản đồ cờ -> ký hiệu khóa và các tùy chọn mặc định:
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)
}
-f|--flags
. Hãy xem gist.github.com/DavidGamba/b3287d40b019e498982c và thoải mái cập nhật câu trả lời nếu bạn thích nó. Tôi có thể sẽ thực hiện mọi Bản đồ và tùy chọn để bạn chỉ có thể vượt qua những gì bạn cần với các đối số được đặt tên.
Tôi dựa trên cách tiếp cận của mình dựa trên câu trả lời hàng đầu (từ dave4420), và cố gắng cải thiện nó bằng cách làm cho nó có mục đích chung hơn.
Nó trả về một Map[String,String]
trong tất cả các tham số dòng lệnh Bạn có thể truy vấn điều này cho các tham số cụ thể mà bạn muốn (ví dụ: sử dụng .contains
) hoặc chuyển đổi các giá trị thành các loại bạn muốn (ví dụ: sử dụng 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())
}
Thí dụ:
val args=Array("--testing1","testing1","-a","-b","--c","d","test2")
argsToOptionMap( args )
Cung cấp:
res0: Map[String,String] = Map(testing1 -> testing1, a -> null, b -> null, c -> d, test2 -> null)
Đây là một trình phân tích cú pháp dòng lệnh scala dễ sử dụng. Nó tự động định dạng văn bản trợ giúp và nó chuyển đổi các đối số chuyển sang loại mong muốn của bạn. Cả POSIX ngắn và các công tắc kiểu GNU dài đều được hỗ trợ. Hỗ trợ chuyển đổi với các đối số cần thiết, đối số tùy chọn và nhiều đối số giá trị. Bạn thậm chí có thể chỉ định danh sách hữu hạn các giá trị được chấp nhận cho một công tắc cụ thể. Tên chuyển đổi dài có thể được viết tắt trên dòng lệnh cho thuận tiện. Tương tự như trình phân tích cú pháp tùy chọn trong thư viện chuẩn Ruby.
Tôi chưa bao giờ thích ruby như trình phân tích cú pháp tùy chọn. Hầu hết các nhà phát triển đã sử dụng chúng không bao giờ viết một trang man phù hợp cho tập lệnh của họ và kết thúc với các trang dài tùy chọn không được tổ chức theo cách thích hợp vì trình phân tích cú pháp của họ.
Tôi luôn thích cách làm việc của Perl với Getopt :: Long của Perl .
Tôi đang làm việc trên một thực hiện scala của nó. API ban đầu trông giống như thế này:
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,
))
Vì vậy, gọi script
như thế này:
$ script hello -f --string=mystring -i 7 --float 3.14 --p --version world -- --nothing
Sẽ in:
higher order function
version is 0.2
Và quay lại:
remaining = Array("hello", "world", "--nothing")
options = Map('flag -> true,
'string -> "mystring",
'int -> 7,
'double -> 3.14)
Dự án được lưu trữ trong github scala-getoptions .
Tôi chỉ tạo ra liệt kê đơn giản của tôi
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
Tôi hiểu rằng giải pháp đó có hai lỗ hổng lớn có thể khiến bạn mất tập trung: Nó loại bỏ sự tự do (tức là sự phụ thuộc vào các thư viện khác, mà bạn đánh giá rất cao) và dự phòng (nguyên tắc DRY, bạn chỉ gõ tên tùy chọn một lần, như chương trình Scala biến và loại bỏ nó lần thứ hai được gõ dưới dạng văn bản dòng lệnh).
Tôi khuyên bạn nên sử dụng http://docopt.org/ . Có một cổng scala nhưng việc triển khai Java https://github.com/docopt/docopt.java hoạt động tốt và dường như được duy trì tốt hơn. Đây là một ví dụ:
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
Đây là những gì tôi nấu. Nó trả về một tuple của bản đồ và một danh sách. Danh sách là cho đầu vào, như tên tập tin đầu vào. Bản đồ là dành cho thiết bị chuyển mạch / tùy chọn.
val args = "--sw1 1 input_1 --sw2 --sw3 2 input_2 --sw4".split(" ")
val (options, inputs) = OptParser.parse(args)
sẽ trở lại
options: Map[Symbol,Any] = Map('sw1 -> 1, 'sw2 -> true, 'sw3 -> 2, 'sw4 -> true)
inputs: List[Symbol] = List('input_1, 'input_2)
Các công tắc có thể là "--t" mà x sẽ được đặt thành true hoặc "--x 10" mà x sẽ được đặt thành "10". Mọi thứ khác sẽ kết thúc trong danh sách.
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)
}
}
}
Tôi thích giao diện rõ ràng của mã này ... lượm lặt từ một cuộc thảo luận ở đây: 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)
}
}
Như mọi người đã đăng giải pháp riêng của mình ở đây là của tôi, vì tôi muốn viết một cái gì đó dễ dàng hơn cho người dùng: https://gist.github.com/gwenzek/78355526e476e08bb34d
Các ý chính chứa một tệp mã, cộng với một tệp thử nghiệm và một ví dụ ngắn được sao chép ở đây:
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)
}
}
Không có tùy chọn ưa thích nào để buộc một biến nằm trong một số giới hạn, vì tôi không cảm thấy rằng trình phân tích cú pháp là nơi tốt nhất để làm như vậy.
Lưu ý: bạn có thể có nhiều bí danh như bạn muốn cho một biến đã cho.
Tôi sẽ chồng chất lên. Tôi đã giải quyết điều này với một dòng mã đơn giản. Đối số dòng lệnh của tôi trông như thế này:
input--hdfs:/path/to/myData/part-00199.avro output--hdfs:/path/toWrite/Data fileFormat--avro option1--5
Điều này tạo ra một mảng thông qua chức năng dòng lệnh gốc của Scala (từ Ứng dụng hoặc phương thức chính):
Array("input--hdfs:/path/to/myData/part-00199.avro", "output--hdfs:/path/toWrite/Data","fileFormat--avro","option1--5")
Sau đó tôi có thể sử dụng dòng này để phân tích mảng args mặc định:
val nArgs = args.map(x=>x.split("--")).map(y=>(y(0),y(1))).toMap
Tạo bản đồ với các tên được liên kết với các giá trị dòng lệnh:
Map(input -> hdfs:/path/to/myData/part-00199.avro, output -> hdfs:/path/toWrite/Data, fileFormat -> avro, option1 -> 5)
Sau đó tôi có thể truy cập các giá trị của các tham số được đặt tên trong mã của mình và thứ tự chúng xuất hiện trên dòng lệnh không còn phù hợp. Tôi nhận ra điều này khá đơn giản và không có tất cả các chức năng nâng cao được đề cập ở trên nhưng dường như là đủ trong hầu hết các trường hợp, chỉ cần một dòng mã và không liên quan đến các phụ thuộc bên ngoài.
Đây là của tôi 1-lót
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)
Nó giảm 3 đối số bắt buộc và đưa ra các tùy chọn. Các số nguyên được chỉ định như -Xmx<size>
tùy chọn java khét tiếng , cùng với tiền tố. Bạn có thể phân tích nhị phân và số nguyên đơn giản như
val cacheEnabled = optSpecified("cacheOff")
val memSize = optInt("-Xmx", 1000)
Không cần nhập gì cả.
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
}
Điều này sẽ tạo ra cách sử dụng sau: