Il modo migliore per analizzare i parametri della riga di comando? [chiuso]


237

Qual è il modo migliore per analizzare i parametri della riga di comando in Scala? Personalmente preferisco qualcosa di leggero che non richiede un vaso esterno.

Relazionato:

Risposte:


228

Nella maggior parte dei casi non è necessario un parser esterno. La corrispondenza del modello di Scala consente di consumare arg in uno stile funzionale. Per esempio:

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

stamperà, ad esempio:

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

Questa versione richiede solo un file. Facile da migliorare (utilizzando un elenco).

Si noti inoltre che questo approccio consente la concatenazione di più argomenti della riga di comando, anche più di due!


4
isSwitch verifica semplicemente che il primo carattere sia un trattino '-'
pjotrp il

6
nextOptionnon è un buon nome per la funzione. È una funzione che restituisce una mappa - il fatto che sia ricorsivo è un dettaglio di implementazione. È come scrivere una maxfunzione per una raccolta e chiamarla nextMaxsemplicemente perché l'hai scritta con ricorsione esplicita. Perché non chiamarlo optionMap?
itsbruce,

4
@itsbruce Voglio solo aggiungere / modificare il tuo punto - sarebbe più "corretto" da una leggibilità / manutenibilità definire listToOptionMap(lst:List[String])con la funzione nextOptiondefinita al suo interno, con un'ultima riga che dice return nextOption(Map(), lst). Detto questo, devo confessare di aver fatto scorciatoie molto più eclatanti nel mio tempo rispetto a quella in questa risposta.
tresbot,

6
@theMadKing nel codice sopra exit(1)potrebbe essere necessariosys.exit(1)
tresbot il

3
Mi piace la tua soluzione. Ecco la modifica di gestire più fileparametri: 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). La mappa richiede anche un valore predefinito Nil, ad es val options = nextOption(Map() withDefaultValue Nil, args.toList). Quello che non mi piace è dover ricorrere a asInstanceOf, poiché i OptionMapvalori sono di tipo Any. C'è una soluzione migliore?
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
}

Quanto sopra genera il seguente testo di utilizzo:

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

Questo è quello che attualmente uso. Uso pulito senza troppo bagaglio. (Dichiarazione di non responsabilità: ora mantengo questo progetto)


6
Mi piace molto meglio il modello di generatore DSL, perché consente la delega della costruzione di parametri ai moduli.
Daniel C. Sobral,

3
Nota: a differenza di quanto mostrato, scopt non ha bisogno di molte annotazioni di tipo.
Blaisorblade,

9
Se lo stai usando per analizzare gli arg per un lavoro spark, tieni presente che non giocano bene insieme. Letteralmente nulla di ciò che ho provato è riuscito a far funzionare spark-submit con scopt :-(
jbrown

4
@BirdJaguarIV Se spark utilizza scopt questo era probabilmente il problema: versioni in conflitto nel vaso o qualcosa del genere. Uso invece scaloppina con i lavori spark e non ho avuto problemi.
jbrown,

12
Ironia della sorte, sebbene questa libreria generi automaticamente una buona documentazione della CLI, il codice sembra un po 'meglio di brainf * ck.
Jonathan Neufeld,

58

Mi rendo conto che la domanda è stata posta qualche tempo fa, ma ho pensato che potesse aiutare alcune persone, che stanno cercando su Google (come me), e andare su questa pagina.

Pettine sembra anche abbastanza promettente.

Caratteristiche (citazione dalla pagina github collegata):

  • flag, valore singolo e opzioni a valore multiplo
  • Nomi di opzioni brevi in ​​stile POSIX (-a) con raggruppamento (-abc)
  • Nomi di opzioni lunghi in stile GNU (--opt)
  • Argomenti delle proprietà (-Dkey = valore, -D key1 = valore key2 = valore)
  • Tipi di stringhe di opzioni e valori di proprietà (con convertitori estensibili)
  • Potente corrispondenza su argomenti finali
  • sottocomandi

E qualche codice di esempio (anche da quella pagina di 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)

4
Capesante mette il resto a mani basse in termini di funzionalità. Peccato che la solita tendenza SO delle "vittorie della prima risposta" abbia spinto questo verso il basso nell'elenco :(
samthebest

Sono d'accordo. Lasciando un commento qui in caso di @Eugene Yokota non ha preso nota. Controllare questo blog out capesante
Pramit

1
Il problema che menziona con scopt è "Sembra buono, ma non è in grado di analizzare le opzioni, che accettano un elenco di argomenti (cioè -a 1 2 3). E non hai modo di estenderlo per ottenere quegli elenchi (eccetto il fork del lib)." ma questo non è più vero, vedi github.com/scopt/scopt#options .
Alexey Romanov,

2
questo è più intuitivo e meno calorico di scopt. non più (x, c) => c.copy(xyz = x) in scopt
WeiChing 林 煒 清

43

Mi piace scorrere argomenti per configurazioni relativamente semplici.

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
Intelligente. Funziona solo se ogni argomento specifica anche un valore, giusto?
Brent Faust,

2
Non dovrebbe essere args.sliding(2, 2)?
m01

1
Non dovrebbe essere var port = 0?
swdev,

17

Command Line Interface Scala Toolkit (CLIST)

anche qui è mio! (un po 'tardi nel gioco però)

https://github.com/backuity/clist

Al contrario scopt, è del tutto mutevole ... ma aspetta! Questo ci dà una sintassi abbastanza bella:

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

E un modo semplice per eseguirlo:

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

Naturalmente puoi fare molto di più (multi-comandi, molte opzioni di configurazione, ...) e non ha dipendenze.

Finirò con una sorta di caratteristica distintiva, l'uso predefinito (abbastanza spesso trascurato per più comandi): CLIST


Ha una convalida?
KF

Sì sì (vedi github.com/backuity/clist/blob/master/demo/src/main/scala/… per un esempio). Non è documentato però ... PR? :)
Bruno Bieth,

Provato, abbastanza conveniente. Ho usato scopt prima, non mi abituo ancora ad aggiungere convalide insieme, ma non solo nella definizione di ciascun parametro. Ma funziona bene con me. E definire diversi parametri e validazioni in tratti diversi, quindi combinarli in casi diversi, è davvero utile. Ho sofferto molto in scopt quando non è conveniente riutilizzare i parametri. Grazie per la risposta!
KF

La maggior parte delle convalide vengono eseguite durante la deserializzazione dei parametri della riga di comando (vedi Read ), quindi se si può definire i vincoli di validazione attraverso i tipi (vale a dire Password, Hex, ...), allora si può sfruttare questo.
Bruno Bieth,

13

Questo è in gran parte un clone spudorato della mia risposta alla domanda Java sullo stesso argomento . Si scopre che JewelCLI è compatibile con Scala in quanto non richiede metodi in stile JavaBean per ottenere la denominazione automatica degli argomenti.

JewelCLI è una libreria Java compatibile con Scala per l'analisi da riga di comando che produce codice pulito . Utilizza interfacce proxy configurate con annotazioni per creare dinamicamente un'API di tipo sicuro per i parametri della riga di comando.

Un'interfaccia di parametro di esempio Person.scala:

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

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

Un esempio di utilizzo dell'interfaccia parametri 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)
    }
  }
}

Salvare copie dei file sopra in un'unica directory e scaricare il JAR JewelCLI 0.6 anche in quella directory.

Compilare ed eseguire l'esempio in Bash su Linux / Mac OS X / ecc .:

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

Compilare ed eseguire l'esempio nel prompt dei comandi di Windows:

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

L'esecuzione dell'esempio dovrebbe produrre il seguente output:

Hello John Doe
Hello John Doe
Hello John Doe

Un aspetto divertente in questo che potresti notare è il (args: _ *). Richiamare i metodi varargs Java da Scala richiede questo. Questa è una soluzione che ho imparato da daily-scala.blogspot.com/2009/11/varargs.html sull'eccellente blog Daily Scala di Jesse Eichar . Consiglio vivamente Daily Scala :)
Alain O'Dea,

12

Come analizzare i parametri senza una dipendenza esterna. Ottima domanda! Potresti essere interessato a Picocli .

Picocli è progettato specificamente per risolvere il problema posto nella domanda: è un framework di analisi della riga di comando in un singolo file, quindi puoi includerlo nel formato sorgente . Ciò consente agli utenti di eseguire applicazioni basate su picocli senza richiedere picocli come dipendenza esterna .

Funziona annotando i campi in modo da scrivere pochissimo codice. Riepilogo rapido:

  • Tutto fortemente tipizzato: opzioni della riga di comando e parametri posizionali
  • Supporto per opzioni brevi cluster POSIX (in modo che gestisca <command> -xvfInputFilecosì come <command> -x -v -f InputFile)
  • Un modello arity che consente un numero minimo, massimo e variabile di parametri, ad es "1..*"."3..5"
  • API fluida e compatta per ridurre al minimo il codice client del plateplate
  • sottocomandi
  • Aiuto per l'uso con i colori ANSI

Il messaggio di aiuto per l'uso è facile da personalizzare con le annotazioni (senza programmazione). Per esempio:

Messaggio di aiuto per l'uso prolungato ( fonte )

Non ho resistito all'aggiunta di un altro screenshot per mostrare che tipo di messaggi di aiuto sull'utilizzo sono possibili. L'aiuto all'utilizzo è il volto della tua applicazione, quindi sii creativo e divertiti!

demo picocli

Disclaimer: ho creato picocli. Feedback o domande molto gradite. È scritto in Java, ma fammi sapere se c'è qualche problema nell'usarlo in Scala e proverò ad affrontarlo.


1
Perché il downvote? Questa è l'unica libreria di cui sono a conoscenza che è specificamente progettata per affrontare il problema menzionato nel PO: come evitare di aggiungere una dipendenza.
Remko Popma,

"incoraggia gli autori delle applicazioni a includerlo". Buon lavoro.
keos,

hai degli esempi di scala?
CruncherBigData

1
Ho iniziato a creare esempi per altre lingue JVM: github.com/remkop/picocli/issues/183 Feedback e contributi benvenuti!
Remko Popma,

11

Vengo dal mondo Java, mi piace args4j perché è semplice, le specifiche sono più leggibili (grazie alle annotazioni) e producono un output ben formattato.

Ecco il mio frammento di esempio:

specificazione

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

}

Parse

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

Su argomenti non validi

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

C'è anche JCommander (dichiarazione di non responsabilità: l'ho creata):

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
Mi piace questa. quei parser "scala pura" mancano di una sintassi pulita
tatto il

@tactoth controllare questo, si ha una chiara sintassi: stackoverflow.com/questions/2315912/...
Bruno Bieth

6

Mi è piaciuto l'approccio slide () di joslinm, non solo i mutabili;) Quindi ecco un modo immutabile per quell'approccio:

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

Ho provato a generalizzare la soluzione di @ pjotrp prendendo in un elenco di simboli chiave posizionali richiesti, una mappa di bandiera -> simbolo chiave e opzioni predefinite:

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

Ho aggiornato questo pezzo di codice per gestire i flag (non solo le opzioni con valori) e anche per definire le opzioni / flag con forme brevi e lunghe. es -f|--flags. Dai un'occhiata a gist.github.com/DavidGamba/b3287d40b019e498982c e sentiti libero di aggiornare la risposta se ti piace. Probabilmente farò tutte le mappe e le opzioni in modo da poter passare solo ciò di cui avrai bisogno con argomenti denominati.
DavidG,

3

Ho basato il mio approccio sulla risposta migliore (da dave4420) e ho cercato di migliorarlo rendendolo più generico.

Restituisce uno Map[String,String]di tutti i parametri della riga di comando. È possibile eseguire una query per i parametri specifici desiderati (ad es. Utilizzando .contains) o convertire i valori nei tipi desiderati (ad es. Utilizzando 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())
}

Esempio:

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

dà:

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


2

Ecco un parser della riga di comando di scala che è facile da usare. Formatta automaticamente il testo di aiuto e converte gli argomenti switch nel tipo desiderato. Sono supportati sia switch POSIX brevi che GNU lunghi. Supporta switch con argomenti richiesti, argomenti opzionali e argomenti con più valori. È anche possibile specificare un elenco finito di valori accettabili per un particolare switch. I nomi di switch lunghi possono essere abbreviati sulla riga di comando per comodità. Simile al parser di opzioni nella libreria standard di Ruby.


2

Non mi è mai piaciuto il rubino come parser di opzioni. La maggior parte degli sviluppatori che li utilizzava non scrive mai una pagina man corretta per i propri script e finisce con pagine lunghe opzioni non organizzate in modo corretto a causa del loro parser.

Ho sempre preferito il modo di Perl di fare le cose con Perl's Getopt :: Long .

Sto lavorando su una sua implementazione. L'API iniziale è simile a questa:

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

Quindi chiamando scriptcosì:

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

Stampa:

higher order function
version is 0.2

E ritorno:

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

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

Il progetto è ospitato in scala-getoptions github .


2

Ho appena creato la mia semplice enumerazione

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

Capisco che la soluzione ha due principali difetti che potrebbero distrarti: elimina la libertà (cioè la dipendenza da altre librerie, che apprezzi così tanto) e la ridondanza (il principio DRY, digiti il ​​nome dell'opzione solo una volta, come programma Scala variabile ed eliminarlo una seconda volta digitata come testo da riga di comando).


2

Suggerirei di usare http://docopt.org/ . C'è uno scala-port ma l'implementazione Java https://github.com/docopt/docopt.java funziona bene e sembra essere meglio mantenuta. Ecco un esempio:

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

Questo è quello che ho cucinato. Restituisce una tupla di una mappa e un elenco. L'elenco è per l'input, come i nomi dei file di input. La mappa è per interruttori / opzioni.

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

sarà di ritorno

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

Gli switch possono essere "--t" quale x sarà impostato su vero, oppure "--x 10" quale x sarà impostato su "10". Tutto il resto finirà nella lista.

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

Mi piace l'aspetto pulito di questo codice ... ricavato da una discussione qui: 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

Come tutti hanno pubblicato la sua soluzione qui è la mia, perché volevo qualcosa di più facile da scrivere per l'utente: https://gist.github.com/gwenzek/78355526e476e08bb34d

L'essenza contiene un file di codice, oltre a un file di prova e un breve esempio copiato qui:

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

Non ci sono opzioni fantasiose per forzare una variabile in alcuni limiti, perché non credo che il parser sia il posto migliore per farlo.

Nota: puoi avere tutto l'alias che desideri per una determinata variabile.


1

Ho intenzione di accumulare. Ho risolto questo con una semplice riga di codice. I miei argomenti della riga di comando si presentano così:

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

Questo crea un array tramite la funzionalità della riga di comando nativa di Scala (dall'app o da un metodo principale):

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

Posso quindi utilizzare questa linea per analizzare l'array args predefinito:

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

Che crea una mappa con nomi associati ai valori della riga di comando:

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

Posso quindi accedere ai valori dei parametri nominati nel mio codice e l'ordine in cui compaiono sulla riga di comando non è più rilevante. Mi rendo conto che questo è abbastanza semplice e non ha tutte le funzionalità avanzate sopra menzionate, ma sembra essere sufficiente nella maggior parte dei casi, necessita solo di una riga di codice e non comporta dipendenze esterne.


1

Ecco il mio 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)

Elimina 3 argomenti obbligatori e fornisce le opzioni. I numeri interi sono specificati come famigerata -Xmx<size>opzione java, insieme al prefisso. Puoi analizzare binari e numeri interi come

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

Non è necessario importare nulla.


0

Una liner rapida e sporca per il povero per l'analisi di coppie chiave = valore:

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
}

Ciò genererà il seguente utilizzo:

uso

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.