Der beste Weg, um Befehlszeilenparameter zu analysieren? [geschlossen]


237

Wie lassen sich Befehlszeilenparameter in Scala am besten analysieren? Ich persönlich bevorzuge etwas Leichtes, das kein externes Glas erfordert.

Verbunden:

Antworten:


228

In den meisten Fällen benötigen Sie keinen externen Parser. Der Mustervergleich von Scala ermöglicht das Konsumieren von Argumenten in einem funktionalen Stil. Beispielsweise:

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

druckt zum Beispiel:

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

Diese Version benötigt nur eine Infile. Einfach zu verbessern (mithilfe einer Liste).

Beachten Sie auch, dass dieser Ansatz die Verkettung mehrerer Befehlszeilenargumente ermöglicht - sogar mehr als zwei!


4
isSwitch prüft einfach, ob das erste Zeichen ein Strich ist '-'
pjotrp

6
nextOptionist kein guter Name für die Funktion. Es ist eine Funktion, die eine Karte zurückgibt - die Tatsache, dass sie rekursiv ist, ist ein Implementierungsdetail. Es ist, als würde man eine maxFunktion für eine Sammlung schreiben und sie nextMaxeinfach aufrufen, weil man sie mit expliziter Rekursion geschrieben hat. Warum nennst optionMapdu es nicht einfach ?
Itsbruce

4
@itsbruce Ich möchte nur Ihren Punkt ergänzen / ändern - es wäre aus "Lesbarkeit / Wartbarkeit" am "richtigsten", listToOptionMap(lst:List[String])mit der darin nextOptiondefinierten Funktion zu definieren, wobei eine letzte Zeile sagt return nextOption(Map(), lst). Trotzdem muss ich gestehen, dass ich in meiner Zeit viel ungeheuerlichere Abkürzungen gemacht habe als in dieser Antwort.
Tresbot

6
@theMadKing im obigen Code muss exit(1)möglicherweise seinsys.exit(1)
tresbot

3
Ich mag deine Lösung. Hier ist die Änderung, um mehrere fileParameter zu behandeln : 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). Die Karte benötigt auch einen Standardwert von Nil, dh val options = nextOption(Map() withDefaultValue Nil, args.toList). Was ich nicht mag, ist, dass ich zurückgreifen muss asInstanceOf, weil die OptionMapWerte vom Typ sind Any. Gibt es eine bessere Lösung?
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
}

Das Obige generiert den folgenden Verwendungstext:

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

Dies ist, was ich derzeit benutze. Saubere Nutzung ohne zu viel Gepäck. (Haftungsausschluss: Ich pflege dieses Projekt jetzt)


6
Ich mag das Builder-Muster DSL viel besser, weil es die Delegierung der Parameterkonstruktion an Module ermöglicht.
Daniel C. Sobral

3
Hinweis: Im Gegensatz zu gezeigt benötigt scopt nicht so viele Typanmerkungen.
Blaisorblade

9
Wenn Sie dies zum Parsen von Argumenten für einen Spark-Job verwenden, sollten Sie gewarnt sein, dass sie nicht gut zusammenspielen. Im wahrsten Sinne des Wortes konnte nichts, was ich versuchte, dazu gebracht werden, mit scopt zu arbeiten :-(
jbrown

4
@BirdJaguarIV Wenn spark scopt verwendet, war dies wahrscheinlich das Problem - widersprüchliche Versionen im Glas oder so. Ich benutze stattdessen Jakobsmuschel mit Funkenjobs und hatte keine Probleme.
Jbrown

12
Obwohl diese Bibliothek automatisch eine gute CLI-Dokumentation generiert, sieht der Code ironischerweise kaum besser aus als brainf * ck.
Jonathan Neufeld

57

Mir ist klar, dass die Frage vor einiger Zeit gestellt wurde, aber ich dachte, sie könnte einigen Leuten helfen, die herum googeln (wie ich), und diese Seite aufrufen.

Jakobsmuschel sieht auch ziemlich vielversprechend aus.

Features (Zitat von der verlinkten Github-Seite):

  • Flag-, Einzelwert- und Mehrfachwertoptionen
  • Kurze Optionsnamen im POSIX-Stil (-a) mit Gruppierung (-abc)
  • Lange Optionsnamen im GNU-Stil (--opt)
  • Eigenschaftsargumente (-Dkey = Wert, -D Schlüssel1 = Wert Schlüssel2 = Wert)
  • Nicht-String-Typen von Optionen und Eigenschaftswerten (mit erweiterbaren Konvertern)
  • Leistungsstarkes Matching für nachfolgende Argumente
  • Unterbefehle

Und ein Beispielcode (auch von dieser Github-Seite):

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
Jakobsmuschel pwns den Rest zweifellos in Bezug auf Eigenschaften. Schade, dass der übliche SO-Trend von "First Answer Wins" dies auf die Liste gebracht hat :(
Samthebest

Genau. Hinterlasse hier einen Kommentar, falls @Eugene Yokota es versäumt hat, eine Notiz zu machen. Überprüfen Sie diesen Blog aus Jakobsmuschel
Pramit

1
Das Problem, das es mit scopt erwähnt, ist: "Es sieht gut aus, kann jedoch keine Optionen analysieren, die eine Liste von Argumenten enthalten (dh -a 1 2 3). Und Sie haben keine Möglichkeit, es zu erweitern, um diese Listen zu erhalten (außer das Verzweigen der." lib). " Dies ist jedoch nicht mehr der Fall, siehe github.com/scopt/scopt#options .
Alexey Romanov

2
Dies ist intuitiver und weniger kochend als scopt. nicht mehr (x, c) => c.copy(xyz = x) in scopt
WeiChing

43

Ich mag es, über Argumente für relativ einfache Konfigurationen zu gleiten .

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
Klug. Funktioniert nur, wenn jedes Argument auch einen Wert angibt, oder?
Brent Faust

2
Sollte es nicht sein args.sliding(2, 2)?
m01

1
Sollte es nicht sein var port = 0?
Swdev

17

Befehlszeilenschnittstelle Scala Toolkit (CLIST)

hier ist auch meins! (allerdings etwas spät im Spiel)

https://github.com/backuity/clist

Im Gegensatz dazu ist scoptes völlig veränderlich ... aber warte! Das gibt uns eine ziemlich schöne Syntax:

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

Und eine einfache Möglichkeit, es auszuführen:

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

Sie können natürlich viel mehr tun (Mehrfachbefehle, viele Konfigurationsoptionen, ...) und haben keine Abhängigkeit.

Ich werde mit einer Art Besonderheit abschließen, der Standardverwendung (die bei mehreren Befehlen häufig vernachlässigt wird): Clist


Hat es eine Validierung?
KF

Ja, dies ist der Fall ( ein Beispiel finden Sie unter github.com/backuity/clist/blob/master/demo/src/main/scala/… ). Es ist jedoch nicht dokumentiert ... PR? :)
Bruno Bieth

Versuchte es, ziemlich praktisch. Ich habe vorher scopt verwendet, ich bin immer noch nicht daran gewöhnt, Validierungen zusammenzufügen, sondern nicht nur in der Definition jedes Parameters. Aber es funktioniert gut mit mir. Es ist sehr hilfreich, verschiedene Parameter und Validierungen in verschiedenen Merkmalen zu definieren und sie dann in verschiedenen Fällen zu kombinieren. Ich habe viel unter scopt gelitten, wenn es nicht bequem ist, Parameter wiederzuverwenden. Danke für die Antwort!
KF

Die meisten Validierungen während der Deserialisierung der Befehlszeilenparameter durchgeführt werden (siehe Read ), so dass , wenn Sie Ihre Validierung Einschränkungen durch Typen definieren können (dh Password, Hex...), dann können Sie diese nutzen können .
Bruno Bieth

13

Dies ist größtenteils ein schamloser Klon meiner Antwort auf die Java-Frage zum gleichen Thema . Es stellt sich heraus, dass JewelCLI Scala-freundlich ist, da keine JavaBean-Methoden erforderlich sind, um eine automatische Argumentbenennung zu erhalten.

JewelCLI ist eine Scala-freundliche Java-Bibliothek für die Befehlszeilenanalyse, die sauberen Code liefert . Es verwendet Proxied Interfaces Configured with Annotations, um dynamisch eine typsichere API für Ihre Befehlszeilenparameter zu erstellen.

Ein Beispiel für eine Parameterschnittstelle Person.scala:

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

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

Ein Beispiel für die Verwendung der Parameterschnittstelle 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)
    }
  }
}

Speichern Sie Kopien der oben genannten Dateien in einem einzigen Verzeichnis und laden Sie die JewelCLI 0.6-JAR ebenfalls in dieses Verzeichnis herunter .

Kompilieren Sie das Beispiel und führen Sie es in Bash unter Linux / Mac OS X / etc. Aus:

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

Kompilieren Sie das Beispiel und führen Sie es in der Windows-Eingabeaufforderung aus:

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

Das Ausführen des Beispiels sollte die folgende Ausgabe ergeben:

Hello John Doe
Hello John Doe
Hello John Doe

Ein Stück Spaß dabei ist vielleicht das (args: _ *). Das Aufrufen von Java-Varargs-Methoden von Scala aus erfordert dies. Dies ist eine Lösung, die ich von daily-scala.blogspot.com/2009/11/varargs.html in Jesse Eichars ausgezeichnetem Daily Scala-Blog gelernt habe . Ich kann Daily Scala nur empfehlen :)
Alain O'Dea

12

So analysieren Sie Parameter ohne externe Abhängigkeit. Gute Frage! Sie könnten an Picocli interessiert sein .

Picocli wurde speziell entwickelt, um das in der Frage gestellte Problem zu lösen: Es handelt sich um ein Befehlszeilen-Parsing-Framework in einer einzelnen Datei, sodass Sie es in Quellform aufnehmen können . Auf diese Weise können Benutzer Picocli-basierte Anwendungen ausführen, ohne Picocli als externe Abhängigkeit zu benötigen .

Es funktioniert durch Annotieren von Feldern, so dass Sie sehr wenig Code schreiben. Kurze Zusammenfassung:

  • Alles stark getippt - Befehlszeilenoptionen sowie Positionsparameter
  • Unterstützung für POSIX-Clustered-Short-Optionen (so funktioniert es <command> -xvfInputFileauch <command> -x -v -f InputFile)
  • Ein Aritätsmodell, das eine minimale, maximale und variable Anzahl von Parametern zulässt, z "1..*"."3..5"
  • Fließende und kompakte API zur Minimierung des Boilerplate-Client-Codes
  • Unterbefehle
  • Verwendungshilfe bei ANSI-Farben

Die Verwendungshilfemeldung kann einfach mit Anmerkungen angepasst werden (ohne Programmierung). Beispielsweise:

Hilfemeldung zur erweiterten Verwendung( Quelle )

Ich konnte nicht widerstehen, einen weiteren Screenshot hinzuzufügen, um zu zeigen, welche Art von Verwendungshilfemeldungen möglich sind. Die Hilfe zur Verwendung ist das Gesicht Ihrer Anwendung. Seien Sie also kreativ und haben Sie Spaß!

Picocli-Demo

Haftungsausschluss: Ich habe picocli erstellt. Feedback oder Fragen sind sehr willkommen. Es ist in Java geschrieben, aber lassen Sie mich wissen, wenn es Probleme bei der Verwendung in Scala gibt, und ich werde versuchen, es zu beheben.


1
Warum das Downvote? Dies ist die einzige mir bekannte Bibliothek, die speziell für das im OP erwähnte Problem entwickelt wurde: So vermeiden Sie das Hinzufügen einer Abhängigkeit.
Remko Popma

"Ermutigen Sie die Autoren der Anwendung, sie aufzunehmen". Gute Arbeit.
Keos

Hast du Scala-Beispiele?
CruncherBigData

1
Ich habe begonnen, Beispiele für andere JVM-Sprachen zu erstellen: github.com/remkop/picocli/issues/183 Feedback und Beiträge sind willkommen!
Remko Popma

11

Ich komme aus der Java-Welt und mag args4j, weil seine einfache Spezifikation (dank Anmerkungen) besser lesbar ist und eine gut formatierte Ausgabe erzeugt.

Hier ist mein Beispielausschnitt:

Spezifikation

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

}

Analysieren

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

Bei ungültigen Argumenten

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

Es gibt auch JCommander (Haftungsausschluss: Ich habe es erstellt):

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
Ich mag diesen. diesen 'reinen Scala'-Parsern fehlt eine saubere Syntax
Taktoth

@tactoth überprüfen Sie dieses, es hat eine klare Syntax: stackoverflow.com/questions/2315912/…
Bruno Bieth

6

Ich mochte den slide () -Ansatz von joslinm, nur nicht die veränderlichen vars;) Hier ist ein unveränderlicher Weg zu diesem Ansatz:

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

Ich habe versucht, die Lösung von @ pjotrp zu verallgemeinern, indem ich eine Liste der erforderlichen Positionsschlüsselsymbole, eine Karte mit Flag -> Schlüsselsymbol und Standardoptionen aufgenommen habe:

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

Ich habe diesen Code aktualisiert, um Flags (nicht nur Optionen mit Werten) zu verarbeiten und um die Option / das Flag mit kurzen und langen Formularen zu definieren. zB -f|--flags. Schauen Sie sich gist.github.com/DavidGamba/b3287d40b019e498982c an und aktualisieren Sie die Antwort, wenn Sie möchten . Ich werde wahrscheinlich jede Map und Option erstellen, damit Sie nur das, was Sie benötigen, mit benannten Argumenten übergeben können.
DavidG

3

Ich habe meinen Ansatz auf die Top-Antwort (von dave4420) gestützt und versucht, sie zu verbessern, indem ich sie allgemeiner gemacht habe.

Es gibt einen Map[String,String]aller Befehlszeilenparameter zurück. Sie können diesen nach den gewünschten Parametern abfragen (z. B. using .contains) oder die Werte in die gewünschten Typen konvertieren (z toInt. B. using ).

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

Beispiel:

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

Gibt:

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


2

Hier ist ein Scala-Befehlszeilenparser , der einfach zu verwenden ist. Der Hilfetext wird automatisch formatiert und die Switch-Argumente werden in den gewünschten Typ konvertiert. Es werden sowohl kurze POSIX- als auch lange GNU-Switches unterstützt. Unterstützt Switches mit erforderlichen Argumenten, optionalen Argumenten und Argumenten mit mehreren Werten. Sie können sogar die endliche Liste akzeptabler Werte für einen bestimmten Switch angeben. Lange Schalternamen können der Einfachheit halber in der Befehlszeile abgekürzt werden. Ähnlich dem Optionsparser in der Ruby-Standardbibliothek.


2

Ich habe Rubin-ähnliche Optionsparser noch nie gemocht. Die meisten Entwickler, die sie verwendet haben, schreiben nie eine richtige Manpage für ihre Skripte und haben am Ende Seitenoptionen, die aufgrund ihres Parsers nicht richtig organisiert sind.

Ich habe Perls Art, Dinge mit Perls Getopt :: Long zu tun, immer bevorzugt .

Ich arbeite an einer Scala-Implementierung. Die frühe API sieht ungefähr so ​​aus:

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

Also so anrufen script:

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

Würde drucken:

higher order function
version is 0.2

Und zurück:

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

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

Das Projekt wird in Github Scala-Getoptions gehostet .


2

Ich habe gerade meine einfache Aufzählung erstellt

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

Ich verstehe, dass die Lösung zwei Hauptmängel aufweist, die Sie ablenken können: Sie beseitigt die Freiheit (dh die Abhängigkeit von anderen Bibliotheken, die Sie so sehr schätzen) und die Redundanz (das DRY-Prinzip, Sie geben den Optionsnamen nur einmal als Scala-Programm ein Variable und entfernen Sie es zum zweiten Mal als Befehlszeilentext eingegeben).


2

Ich würde vorschlagen, http://docopt.org/ zu verwenden . Es gibt einen Scala-Port, aber die Java-Implementierung https://github.com/docopt/docopt.java funktioniert einwandfrei und scheint besser gewartet zu werden. Hier ist ein Beispiel:

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

Das habe ich gekocht. Es gibt ein Tupel einer Karte und eine Liste zurück. Die Liste dient zur Eingabe, ebenso wie die Namen der Eingabedateien. Karte ist für Schalter / Optionen.

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

wird zurückkehren

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

Schalter können "--t" sein, wobei x auf true gesetzt wird, oder "--x 10", wobei x auf "10" gesetzt wird. Alles andere wird in der Liste landen.

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

Ich mag das saubere Aussehen dieses Codes ... aus einer Diskussion hier: 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

Da jeder hier seine eigene Lösung gepostet hat, gehört sie mir, weil ich etwas wollte, das für den Benutzer einfacher zu schreiben ist: https://gist.github.com/gwenzek/78355526e476e08bb34d

Das Wesentliche enthält eine Codedatei sowie eine Testdatei und ein kurzes Beispiel, das hier kopiert wurde:

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

Es gibt keine ausgefallenen Optionen, um eine Variable in bestimmten Grenzen zu erzwingen, da ich nicht der Meinung bin, dass der Parser der beste Ort dafür ist.

Hinweis: Sie können für eine bestimmte Variable so viel Alias ​​haben, wie Sie möchten.


1

Ich werde mich anhäufen. Ich habe dies mit einer einfachen Codezeile gelöst. Meine Befehlszeilenargumente sehen folgendermaßen aus:

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

Dadurch wird ein Array über die native Befehlszeilenfunktionalität von Scala erstellt (entweder über eine App oder eine Hauptmethode):

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

Ich kann dann diese Zeile verwenden, um das Standardargumentarray zu analysieren:

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

Dadurch wird eine Karte mit Namen erstellt, die den Befehlszeilenwerten zugeordnet sind:

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

Ich kann dann auf die Werte der benannten Parameter in meinem Code zugreifen und die Reihenfolge, in der sie in der Befehlszeile angezeigt werden, ist nicht mehr relevant. Mir ist klar, dass dies ziemlich einfach ist und nicht alle oben genannten erweiterten Funktionen bietet, aber in den meisten Fällen ausreichend zu sein scheint, nur eine Codezeile benötigt und keine externen Abhängigkeiten beinhaltet.


1

Hier ist mein 1-Liner

    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)

Es werden 3 obligatorische Argumente gelöscht und die Optionen angegeben. Ganzzahlen werden wie die berüchtigte -Xmx<size>Java-Option zusammen mit dem Präfix angegeben. Sie können Binärdateien und Ganzzahlen so einfach wie analysieren

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

Sie müssen nichts importieren.


0

Der schnelle und schmutzige Einzeiler des armen Mannes zum Parsen von Schlüssel-Wert-Paaren:

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
}

Dies erzeugt die folgende Verwendung:

Verwendung

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.