Komut satırı parametrelerini ayrıştırmanın en iyi yolu? [kapalı]


237

Scala'da komut satırı parametrelerini ayrıştırmanın en iyi yolu nedir? Şahsen, harici kavanoz gerektirmeyen hafif bir şey tercih ederim.

İlişkili:

Yanıtlar:


228

Çoğu durumda harici bir ayrıştırıcıya ihtiyacınız yoktur. Scala'nın desen eşleşmesi, işlevsel bir tarzda argümanların tüketilmesini sağlar. Örneğin:

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)
  }
}

yazdırılacak, örneğin:

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

Bu sürüm yalnızca bir dosya alır. Geliştirmek kolay (bir Liste kullanarak).

Ayrıca bu yaklaşımın birden fazla komut satırı argümanının birleştirilmesine izin verdiğini unutmayın - ikiden fazla bile!


4
isSwitch sadece bir karakterin ilk karakterini kontrol eder '-'
pjotrp

6
nextOptionişlevi için iyi bir ad değildir. Bu, haritayı döndüren bir işlevdir - özyinelemeli olması bir uygulama ayrıntısıdır. Bu max, bir koleksiyon için bir işlev yazmak ve çağırmak gibidir, nextMaxçünkü açık bir özyineleme ile yazdınız. Neden sadece onu aramıyorsun optionMap?
itsbruce

4
@itsbruce Ben sadece noktanıza eklemek / değiştirmek istiyorum - bir son satır söyleyerek, içinde tanımlanan listToOptionMap(lst:List[String])işlev ile tanımlamak için bir okunabilirlik / sürdürülebilirlik en "uygun" olacaktır . İtiraf etmeliyim ki, zamanımda bu cevabınkinden çok daha korkunç kısayollar yaptığımı itiraf etmeliyim. nextOptionreturn nextOption(Map(), lst)
tresbot

6
@theMadKing yukarıdaki kodda exit(1)olabilirsys.exit(1)
tresbot

3
Çözümünü beğendim. İşte birden işlemek için modifikasyon var fileparametreleri: 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). Harita da varsayılan değeri ihtiyacı Nilyani val options = nextOption(Map() withDefaultValue Nil, args.toList). Sevmediğim asInstanceOf, OptionMapdeğerlerin tür olması nedeniyle başvurmak zorunda Any. Daha iyi bir çözüm var mı?
Mauro Lacy

196

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
}

Yukarıdakiler aşağıdaki kullanım metnini oluşturur:

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

Şu anda bunu kullanıyorum. Çok fazla bagaj olmadan temiz kullanım. (Feragat: Şimdi bu projeyi sürdürüyorum)


6
DSL model oluşturucu modelini çok daha çok seviyorum çünkü parametre yapısının modüllere devredilmesini sağlıyor.
Daniel C. Sobral

3
Not: gösterildiği gibi, scopt bu tip açıklamalara ihtiyaç duymaz.
Blaisorblade

9
Bunu bir kıvılcım işi için argümanları ayrıştırmak için kullanıyorsanız, birlikte iyi oynamadıklarına dikkat edin. Kelimenin tam anlamıyla denedim hiçbir şey scopt ile çalışmak için kıvılcım-alamadım :-(
jbrown

4
@BirdJaguarIV Kıvılcım muhtemelen sorun olan scopt kullanıyorsa - kavanozdaki çatışan sürümler veya başka bir şey. Bunun yerine kıvılcım işlerinde tarak kullanıyorum ve herhangi bir sorun yaşamadım.
jbrown

12
İronik olarak, bu kütüphane otomatik olarak iyi CLI belgeleri oluştursa da, kod brainf * ck'den biraz daha iyi görünüyor.
Jonathan Neufeld

58

Sorunun bir süre önce sorulduğunu fark ettim, ancak bunun etrafta dolaşan (benim gibi) bazı insanlara yardımcı olabileceğini ve bu sayfaya çarptığını düşündüm.

Tarak da oldukça umut verici görünüyor.

Özellikler (bağlantılı github sayfasından alıntı):

  • bayrak, tek değer ve çoklu değer seçenekleri
  • POSIX stili kısa seçenek adları (-a) gruplama (-abc)
  • GNU tarzı uzun seçenek adları (--opt)
  • Özellik bağımsız değişkenleri (-Dkey = değer, -D anahtar1 = değer anahtar2 = değer)
  • Dize dışı seçenek ve özellik değerleri türleri (genişletilebilir dönüştürücülerle)
  • Sondaki argümanlarda güçlü eşleme
  • alt komutları

Ve bazı örnek kodlar (o Github sayfasından da):

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)

4
Tarak, geri kalan ellerini özellikler açısından aşağı doğru çevirir. Utanç "ilk cevap kazanır" genel SO eğilimi listeyi aşağı itti :(
samthebest

Katılıyorum. @Eugene Yokota burada bir yorum bırakarak not almak için kaçırdı. Tarak
Pramit

1
Scopt ile bahsettiği sorun, "İyi görünüyor, ancak bir argüman listesi alan seçenekleri ayrıştıramıyor (yani -a 1 2 3). Ve bu listeleri almak için genişletmek için bir yolunuz yok lib)." ancak bu artık doğru değil, bkz. github.com/scopt/scopt#options .
Alexey Romanov

2
bu scopt'tan daha sezgisel ve daha az kaynatma plakasıdır. artık (x, c) => c.copy(xyz = x) scopt içinde
WeiChing林煒清

43

Nispeten basit konfigürasyonlar için argümanlar üzerinde kaymayı seviyorum .

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
}

2
Zeki. Ancak her arg de bir değer belirtirse çalışır, değil mi?
Brent Faust

2
Olmamalı mı args.sliding(2, 2)?
m01

1
Olmamalı mı var port = 0?
swdev

17

Komut Satırı Arabirimi Scala Araç Seti (CLIST)

işte benim de! (oyunda biraz geç olsa da)

https://github.com/backuity/clist

Bunun aksine scopttamamen değişebilir ... ama bekleyin! Bu bize güzel bir sözdizimi verir:

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")
}

Ve çalıştırmanın basit bir yolu:

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

Elbette çok daha fazlasını yapabilirsiniz (çoklu komutlar, birçok yapılandırma seçeneği, ...) ve bağımlılığı yoktur.

Bir tür ayırt edici özellik, varsayılan kullanım (çoklu komutlar için genellikle ihmal edilir) ile bitireceğim: clist


Doğrulaması var mı?
KF

Evet öyle ( örnek için github.com/backuity/clist/blob/master/demo/src/main/scala/… adresine bakın ). Yine de belgelenmedi ... PR? :)
Bruno Bieth

Denedim, oldukça uygun. Daha önce scopt kullandım, hala birlikte doğrulama eklemek için alışkın değilim, sadece her parametrenin tanımında değil. Ama benimle iyi çalışıyor. Ve farklı özelliklerde farklı parametreleri ve doğrulamaları tanımlamak, daha sonra bunları farklı durumlarda birleştirmek, bu gerçekten yararlıdır. Parametreleri yeniden kullanmak uygun olmadığında scopt'ta çok acı çektim. Cevap için teşekkürler!
KF

Çoğu doğrulama, komut satırı parametrelerinin serileştirilmesi sırasında gerçekleştirilir (bkz. Okuma ), bu nedenle doğrulama kısıtlamalarınızı türler (örn Password. Hex, ...) aracılığıyla tanımlayabilirseniz , bundan yararlanabilirsiniz.
Bruno Bieth

13

Bu, aynı konunun Java sorusuna cevabımın büyük bir utanmaz klonu . JewelCLI'nin otomatik argüman adlandırması için JavaBean stil yöntemleri gerektirmediği için Scala dostu olduğu ortaya çıkıyor.

JewelCLI, temiz kod veren komut satırı ayrıştırma için Scala dostu bir Java kütüphanesidir . Komut satırı parametreleriniz için dinamik olarak güvenli bir API oluşturmak üzere Ek Açıklamalarla Yapılandırılmış Proxied Arabirimleri kullanır.

Örnek bir parametre arayüzü Person.scala:

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

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

Parametre arayüzünün örnek kullanımı 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)
    }
  }
}

Yukarıdaki dosyaların kopyalarını tek bir dizine kaydedin ve JewelCLI 0.6 JAR'ı da bu dizine indirin.

Linux / Mac OS X / etc'deki Bash'te örneği derleyin ve çalıştırın.

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

Windows Komut İstemi'nde örneği derleyin ve çalıştırın:

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

Örneği çalıştırmak aşağıdaki çıktıyı vermelidir:

Hello John Doe
Hello John Doe
Hello John Doe

Fark edebileceğiniz eğlenceli bir şey (args: _ *). Scala'dan Java varargs yöntemlerini çağırmak bunu gerektirir. Jesse Eichar'ın mükemmel Daily Scala blogunda daily-scala.blogspot.com/2009/11/varargs.html adresinden öğrendiğim bir çözüm . Daily Scala :)
Alain O'Dea

12

Harici bir bağımlılık olmadan parametrelerin ayrıştırılması. Harika bir soru! Picocli ile ilgileniyor olabilirsiniz .

Picocli, soruda sorulan sorunu çözmek için özel olarak tasarlanmıştır: tek bir dosyada bir komut satırı ayrıştırma çerçevesidir, böylece kaynak formuna ekleyebilirsiniz . Bu, kullanıcıların harici bir bağımlılık olarak picocli gerektirmeden picocli tabanlı uygulamaları çalıştırmasını sağlar .

Alanlara açıklama ekleyerek çalışır, böylece çok az kod yazarsınız. Hızlı özet:

  • Her şeyi güçlü bir şekilde yazdım - komut satırı seçenekleri ve konum parametreleri
  • POSIX desteği kısa seçenekleri kümelenmiş (o kolları böylece <command> -xvfInputFileyanı sıra <command> -x -v -f InputFile)
  • Parametrelerin minimum, maksimum ve değişken sayısını sağlayan bir Arity modeli, örneğin "1..*","3..5"
  • Ortak plaka istemci kodunu en aza indirmek için akıcı ve kompakt API
  • alt komutları
  • ANSI renkleri ile kullanım yardımı

Kullanım yardım mesajı ek açıklamalarla (programlama olmadan) kolayca özelleştirilebilir. Örneğin:

Genişletilmiş kullanım yardım mesajı( kaynak )

Ne tür kullanım yardım mesajlarının mümkün olduğunu göstermek için bir ekran görüntüsü daha eklemeye dayanamadım. Kullanım yardımı, uygulamanızın yüzüdür, bu yüzden yaratıcı olun ve eğlenin!

picocli demosu

Yasal Uyarı: Piknik oluşturdum. Geri bildirim veya sorular çok hoş geldiniz. Java ile yazılmıştır, ancak scala'da kullanırken herhangi bir sorun olup olmadığını bana bildirin ve ben de bunu çözmeye çalışacağım.


1
Neden inişli çıkışlı? Bu özellikle OP'de belirtilen sorunu çözmek için tasarlanmış farkında olduğum tek kütüphane : bağımlılık eklemekten nasıl kaçınılır.
Remko Popma

msgstr "uygulama yazarlarını dahil etmeye teşvik edin". İyi iş.
keos

scala örnekleriniz var mı?
CruncherBigData

1
Diğer JVM dilleri için örnekler oluşturmaya başladım: github.com/remkop/picocli/issues/183 Geri bildirim ve katkılar hoş geldiniz!
Remko Popma

11

Java dünyasındanım, args4j'yi seviyorum çünkü basit, özellikleri daha okunabilir (ek açıklamalar sayesinde) ve güzel biçimlendirilmiş çıktılar üretiyor.

İşte benim örnek snippet'im:

Şartname

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]"

}

Ayrıştırma

//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)

Geçersiz argümanlarda

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.


8

Ayrıca JCommander (feragatname: ben yarattım):

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)
    }
  }
}

2
bunu beğendim. bu 'saf scala' ayrıştırıcıları temiz bir sözdiziminden yoksun
tactoth

@tactoth bunu kontrol edin, açık bir sözdizimi vardır: stackoverflow.com/questions/2315912/…
Bruno Bieth

6

Ben sadece değişebilir vars değil joslinm slide () yaklaşımını sevdim;) İşte bu yaklaşımın değişmez bir yolu:

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
  }
}


3

Gerekli konumsal anahtar sembolleri, bayrak haritası -> anahtar sembolü ve varsayılan seçenekler listesini alarak @ pjotrp çözümünü genelleştirmeye çalıştım:

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)
}

Bu kod parçasını bayraklarla (yalnızca değerlerle seçenekler değil) işlemek ve ayrıca kısa ve uzun formlarla seçeneği / bayrağı tanımlamak için mandle güncelledim. örn -f|--flags. Gist.github.com/DavidGamba/b3287d40b019e498982c adresine bir göz atın ve isterseniz cevabı güncellemekten çekinmeyin. Muhtemelen her Harita ve seçeneği yapacağım, böylece sadece adlandırılmış argümanlarla ihtiyacınız olanı iletebilirsiniz.
DavidG

3

Yaklaşımımı en iyi cevaba dayandım (dave4420'den) ve daha genel amaçlı hale getirerek geliştirmeye çalıştım.

Map[String,String]Tüm komut satırı parametrelerinden birini döndürür Bunu istediğiniz belirli parametreler için sorgulayabilir (örneğin kullanarak .contains) veya değerleri istediğiniz türlere (örneğin kullanarak toInt) dönüştürebilirsiniz.

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())
}

Misal:

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

verir:

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


2

İşte kullanımı kolay bir scala komut satırı ayrıştırıcısı . Yardım metnini otomatik olarak formatlar ve anahtar argümanlarını istediğiniz türe dönüştürür. Hem kısa POSIX hem de uzun GNU stili anahtarlar desteklenir. Gerekli bağımsız değişkenleri, isteğe bağlı bağımsız değişkenleri ve birden çok değer bağımsız değişkenini içeren anahtarları destekler. Belirli bir anahtar için kabul edilebilir değerlerin sonlu listesini bile belirleyebilirsiniz. Uzun anahtar adları kolaylık sağlamak için komut satırında kısaltılabilir. Ruby standart kitaplığındaki seçenek ayrıştırıcısına benzer.


2

Ben seçenek ayrıştırıcıları gibi yakut sevmedim. Onları kullanan çoğu geliştirici asla uygun bir sayfa yazmaz komut dosyaları için ve ayrıştırıcıları nedeniyle uygun şekilde düzenlenmemiş uzun seçenekler ile sonuçlanır.

Her zaman Perl'in Getopt :: Long ile bir şeyler yapma şeklini tercih ettim .

Bunun bir scala uygulaması üzerinde çalışıyorum. İlk API şuna benzer:

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,
    ))

Yani şöyle çağırıyor script:

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

Yazdırır:

higher order function
version is 0.2

Ve dönüş:

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

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

Proje github scala-getoptions'ta barındırılmaktadır .


2

Basit numaralandırmamı yeni oluşturdum

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

Çözümün sizi rahatsız edebilecek iki ana kusuru olduğunu anlıyorum: Özgürlüğü (yani diğer kütüphanelere bağımlılık, çok değer verdiğiniz) ve artıklığı (DRY ilkesi, seçenek adını yalnızca bir kez yazıyorsunuz, Scala programı olarak değişkenini seçin ve komut satırı metni olarak yazıldığında ikinci kez eleyin).


2

Http://docopt.org/ kullanmanızı öneririm . Bir scala-port var ama Java uygulaması https://github.com/docopt/docopt.java gayet iyi çalışıyor ve daha iyi korunuyor gibi görünüyor. İşte bir örnek:

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

2

Pişirdiğim şey bu. Bir harita ve liste kümesi döndürür. Liste, girdi dosya adları gibi girdi içindir. Harita anahtarlar / seçenekler içindir.

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

dönecek

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

Anahtarlar x'in true olarak ayarlanacağı "--t" veya x'in "10" olarak ayarlanacağı "--x 10" olabilir. Diğer her şey listede olacak.

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

Bu kodun temiz görünümünü seviyorum ... burada bir tartışmadan temizlendi: 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)
  }

}

1

Herkes kendi çözüm burada yayınlanmıştır gibi, çünkü kullanıcı için yazmak daha kolay bir şey istedim: https://gist.github.com/gwenzek/78355526e476e08bb34d

Gist bir kod dosyası, bir test dosyası ve buraya kopyalanan kısa bir örnek içeriyor:

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)
    }
}

Bir değişkenin bazı sınırlarda olmaya zorlamak için süslü seçenekler yoktur, çünkü ayrıştırıcının bunu yapmak için en iyi yer olduğunu hissetmiyorum.

Not: belirli bir değişken için istediğiniz kadar takma adınız olabilir.


1

Yığacağım. Bunu basit bir kod satırı ile çözdüm. Komut satırı argümanlarım şöyle:

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

Bu, Scala'nın yerel komut satırı işlevselliği aracılığıyla bir dizi oluşturur (Uygulamadan veya ana yöntemden):

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

Sonra varsayılan args dizisini ayrıştırmak için bu satırı kullanabilirsiniz:

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

Hangi komut satırı değerleri ile ilişkili adları ile bir harita oluşturur:

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

Daha sonra kodumdaki adlandırılmış parametrelerin değerlerine erişebilir ve komut satırında göründükleri sıra artık geçerli değildir. Bunun oldukça basit olduğunu ve yukarıda belirtilen tüm gelişmiş işlevselliğe sahip olmadığını, ancak çoğu durumda yeterli göründüğünü, sadece bir kod satırına ihtiyaç duyduğunu ve dış bağımlılıkları içermediğini anlıyorum.


1

İşte benim 1-astar

    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 zorunlu argüman bırakır ve seçenekleri sunar. Tamsayılar -Xmx<size>önekle birlikte kötü şöhretli java seçeneği gibi belirtilir . İkili dosyaları ve tam sayıları istediğiniz kadar basit olarak ayrıştırabilirsiniz

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

Hiçbir şey ithal etmeye gerek yok.


0

Zavallı adamın hızlı ve kirli tek astarı, anahtar = değer çiftlerini ayrıştırmak için:

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

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
}

Bu, aşağıdaki kullanımı oluşturur:

kullanım

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.