¿La mejor manera de analizar los parámetros de la línea de comandos? [cerrado]

237

¿Cuál es la mejor manera de analizar los parámetros de la línea de comandos en Scala? Personalmente prefiero algo liviano que no requiera una jarra externa.

Relacionado:

Eugene Yokota
fuente

Respuestas:

228

Para la mayoría de los casos, no necesita un analizador externo. La coincidencia de patrones de Scala permite consumir args en un estilo funcional. Por ejemplo:

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

imprimirá, por ejemplo:

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

Esta versión solo toma un infile. Fácil de mejorar (mediante el uso de una lista).

Tenga en cuenta también que este enfoque permite la concatenación de múltiples argumentos de línea de comandos, ¡incluso más de dos!

pjotrp
fuente
44
isSwitch simplemente comprueba si el primer personaje es un guión '-'
pjotrp
66
nextOptionNo es un buen nombre para la función. Es una función que devuelve un mapa; el hecho de que sea recursivo es un detalle de implementación. Es como escribir una maxfunción para una colección y llamarla nextMaxsimplemente porque la escribió con recursividad explícita. ¿Por qué no solo llamarlo optionMap?
itsbruce
44
@itsbruce Solo quiero agregar / modificar su punto: sería más "apropiado" desde una legibilidad / mantenibilidad definir listToOptionMap(lst:List[String])con la función nextOptiondefinida dentro de eso, con una línea final que diga return nextOption(Map(), lst). Dicho esto, tengo que confesar que he hecho atajos mucho más atroces en mi tiempo que el de esta respuesta.
tresbot
66
@theMadKing en el código anterior exit(1)puede ser necesariosys.exit(1)
tresbot
3
Me gusta tu solución Aquí está la modificación de manejar múltiples fileparámetros: 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). El mapa también necesita un valor predeterminado de Nil, es decir val options = nextOption(Map() withDefaultValue Nil, args.toList). Lo que no me gusta es tener que recurrir asInstanceOf, debido a que los OptionMapvalores son de tipo Any. ¿Hay una mejor solución?
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
}

Lo anterior genera el siguiente texto de uso:

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

Esto es lo que uso actualmente. Uso limpio sin demasiado equipaje. (Descargo de responsabilidad: ahora mantengo este proyecto)

Eugene Yokota
fuente
66
Me gusta mucho más el DSL del patrón de creación, ya que permite la delegación de la construcción de parámetros a los módulos.
Daniel C. Sobral
3
Nota: a diferencia de lo que se muestra, scopt no necesita tantas anotaciones de tipo.
Blaisorblade
99
Si está usando esto para analizar args para un trabajo de chispa, tenga en cuenta que no juegan muy bien juntos. Literalmente, nada de lo que probé podría hacer que spark-submit funcione con scopt :-(
jbrown
44
@BirdJaguarIV Si spark usa scopt, ese fue probablemente el problema: versiones conflictivas en el jar o algo así. Utilizo vieiras con trabajos de chispa y no he tenido ningún problema.
jbrown
12
Irónicamente, aunque esta biblioteca genera automáticamente una buena documentación de CLI, el código se ve poco mejor que brainf * ck.
Jonathan Neufeld
58

Me di cuenta de que la pregunta se hizo hace algún tiempo, pero pensé que podría ayudar a algunas personas, que están buscando en Google (como yo), y accedí a esta página.

La vieira también parece bastante prometedora.

Características (cita de la página de github vinculada):

  • opciones de bandera, valor único y valor múltiple
  • Nombres de opciones cortas de estilo POSIX (-a) con agrupación (-abc)
  • Nombres largos de opciones de estilo GNU (--opt)
  • Argumentos de propiedad (-Dkey = valor, -D clave1 = valor clave2 = valor)
  • Tipos de opciones y valores de propiedades que no son cadenas (con convertidores extensibles)
  • Potente coincidencia en args finales
  • Subcomandos

Y algún código de ejemplo (también de esa página de 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)
rintcius
fuente
44
Vieira muestra el resto sin dudas en términos de características. Es una pena que la tendencia SO habitual de "la primera respuesta gana" ha empujado esto hacia abajo en la lista :(
samthebest
Estoy de acuerdo. Dejando un comentario aquí solo en caso de que @Eugene Yokota fallara en tomar una nota. Echa un vistazo a este blog vieira
Pramit
1
El problema que menciona con scopt es "Se ve bien, pero no puede analizar las opciones, que toman una lista de argumentos (es decir, -a 1 2 3). Y no tiene forma de extenderlo para obtener esas listas (excepto bifurcar lib) ". pero esto ya no es cierto, consulte github.com/scopt/scopt#options .
Alexey Romanov
2
Esto es más intuitivo y menos repetitivo que Scopt. no más (x, c) => c.copy(xyz = x) en alcance
WeiChing 林 煒 清
43

Me gusta deslizarme sobre argumentos para configuraciones relativamente simples.

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
}
joslinm
fuente
2
Inteligente. Sin embargo, solo funciona si cada argumento también especifica un valor, ¿verdad?
Brent Faust
2
¿No debería ser args.sliding(2, 2)?
m01
1
¿No debería ser var port = 0?
swdev
17

Interfaz de línea de comando Scala Toolkit (CLIST)

¡Aquí está el mío también! (aunque un poco tarde en el juego)

https://github.com/backuity/clist

Por el contrario scopt, es completamente mutable ... ¡pero espera! Eso nos da una sintaxis bastante buena:

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

Y una forma simple de ejecutarlo:

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

Puede hacer mucho más, por supuesto (comandos múltiples, muchas opciones de configuración, ...) y no tiene dependencia.

Terminaré con un tipo de característica distintiva, el uso predeterminado (a menudo descuidado para los comandos múltiples): clist

Bruno Bieth
fuente
¿Tiene validación?
KF
Sí, sí (vea github.com/backuity/clist/blob/master/demo/src/main/scala/… para ver un ejemplo). Sin embargo, no está documentado ... ¿PR? :)
Bruno Bieth
Probé, muy conveniente. Utilicé scopt antes, todavía no estoy acostumbrado a agregar validaciones juntas, pero no solo en la definición de cada parámetro. Pero funciona bien conmigo. Y definir diferentes parámetros y validaciones en diferentes rasgos, luego combinarlos en diferentes casos, es realmente útil. Sufrí mucho en scopt cuando no es conveniente reutilizar los parámetros. ¡Gracias por responder!
KF
La mayoría de las validaciones se realizan durante la deserialización de los parámetros de línea de comandos (ver Lee ), por lo que si se pueden definir restricciones de validación a través de tipos (es decir Password, Hex, ...), entonces se puede aprovechar esto.
Bruno Bieth
13

Este es en gran medida un clon descarado de mi respuesta a la pregunta de Java sobre el mismo tema . Resulta que JewelCLI es compatible con Scala, ya que no requiere métodos de estilo JavaBean para obtener nombres automáticos de argumentos.

JewelCLI es una biblioteca Java compatible con Scala para el análisis de la línea de comandos que produce un código limpio . Utiliza interfaces proxy configuradas con anotaciones para crear dinámicamente una API de tipo seguro para los parámetros de la línea de comandos.

Un ejemplo de interfaz de parámetros Person.scala:

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

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

Un ejemplo de uso de la interfaz de parámetros 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)
    }
  }
}

Guarde copias de los archivos anteriores en un solo directorio y descargue JewelCLI 0.6 JAR también en ese directorio.

Compile y ejecute el ejemplo en Bash en Linux / Mac OS X / etc .:

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

Compile y ejecute el ejemplo en el símbolo del sistema de Windows:

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

Ejecutar el ejemplo debería producir el siguiente resultado:

Hello John Doe
Hello John Doe
Hello John Doe
Alain O'Dea
fuente
Una parte divertida de esto que puede notar es el (args: _ *). Llamar a los métodos Java varargs desde Scala requiere esto. Esta es una solución que aprendí de daily-scala.blogspot.com/2009/11/varargs.html en el excelente blog Daily Scala de Jesse Eichar. Recomiendo encarecidamente Daily Scala :)
Alain O'Dea
12

Cómo analizar parámetros sin una dependencia externa. Gran pregunta! Te puede interesar picocli .

Picocli está específicamente diseñado para resolver el problema planteado en la pregunta: es un marco de análisis de línea de comando en un solo archivo, por lo que puede incluirlo en forma de código fuente . Esto permite a los usuarios ejecutar aplicaciones basadas en picocli sin requerir picocli como dependencia externa .

Funciona anotando campos para que escriba muy poco código. Sumario rápido:

  • Escriba todo con firmeza: opciones de línea de comando y parámetros posicionales
  • Compatibilidad con las opciones cortas agrupadas POSIX (por lo que se maneja <command> -xvfInputFiletan bien como <command> -x -v -f InputFile)
  • Un modelo de aridad que permite un número mínimo, máximo y variable de parámetros, por ejemplo "1..*","3..5"
  • API fluida y compacta para minimizar el código del cliente repetitivo
  • Subcomandos
  • Ayuda de uso con colores ANSI

El mensaje de ayuda de uso es fácil de personalizar con anotaciones (sin programación). Por ejemplo:

Mensaje de ayuda de uso extendido( fuente )

No pude resistirme a agregar una captura de pantalla más para mostrar qué tipo de mensajes de ayuda de uso son posibles. La ayuda de uso es la cara de su aplicación, ¡así que sea creativo y diviértase!

demo picocli

Descargo de responsabilidad: creé picocli. Comentarios o preguntas muy bienvenidos. Está escrito en Java, pero avíseme si hay algún problema al usarlo en Scala e intentaré solucionarlo.

Remko Popma
fuente
1
¿Por qué el voto negativo? Esta es la única biblioteca que conozco que está específicamente diseñada para abordar el problema mencionado en el OP: cómo evitar agregar una dependencia.
Remko Popma
"animar a los autores de aplicaciones a incluirlo". Buen trabajo.
keos
¿tienes ejemplos de scala?
CruncherBigData
1
He empezado a crear ejemplos para otros lenguajes de JVM: github.com/remkop/picocli/issues/183 ¡Bienvenidos comentarios y contribuciones!
Remko Popma
11

Soy del mundo Java, me gusta args4j porque su especificación simple es más legible (gracias a las anotaciones) y produce una salida con un formato agradable.

Aquí está mi fragmento de ejemplo:

Especificación

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

}

Analizar gramaticalmente

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

Sobre argumentos inválidos

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.
Thamme Gowda
fuente
10

scala-optparse-aplicativo

Creo que scala-optparse-Applicative es la biblioteca de analizador de línea de comandos más funcional de Scala.

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

Kenji Yoshida
fuente
¿Tiene algún ejemplo / documento además de lo que hay en el archivo README?
Erik Kaplun
1
Lo hace, verifique el examplescódigo de prueba
gpampara
8

También hay JCommander (descargo de responsabilidad: lo creé):

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)
    }
  }
}
Cedric Beust
fuente
2
me gusta este. los analizadores 'pura' Scala carecen de una sintaxis limpia
tactoth
@tactoth revisa este, tiene una sintaxis clara: stackoverflow.com/questions/2315912/…
Bruno Bieth
6

Me gustó el enfoque slide () de joslinm simplemente no los vars mutables;) Así que aquí hay una forma inmutable de ese enfoque:

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
  }
}
haggy
fuente
3

Intenté generalizar la solución de @ pjotrp tomando una lista de los símbolos de teclas de posición requeridos, un mapa de bandera -> símbolo de tecla y opciones predeterminadas:

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)
}
Byron Ruth
fuente
Actualicé este código para manejar banderas (no solo opciones con valores) y también para mandlear la definición de la opción / bandera con formas cortas y largas. por ej -f|--flags. Eche un vistazo a gist.github.com/DavidGamba/b3287d40b019e498982c y no dude en actualizar la respuesta si lo desea. Probablemente haré cada mapa y opción para que solo pueda pasar lo que necesitará con argumentos con nombre.
DavidG
3

Basé mi enfoque en la respuesta principal (de dave4420) e intenté mejorarlo haciéndolo más general.

Devuelve uno Map[String,String]de todos los parámetros de la línea de comandos. Puede consultar esto para los parámetros específicos que desee (por ejemplo, usar .contains) o convertir los valores en los tipos que desee (por ejemplo, usar 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())
}

Ejemplo:

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

Da:

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

Aquí hay un analizador de línea de comando scala que es fácil de usar. Formatea automáticamente el texto de ayuda y convierte los argumentos de cambio al tipo deseado. Se admiten conmutadores de estilo de GNU cortos y largos de POSIX. Admite conmutadores con argumentos obligatorios, argumentos opcionales y argumentos de valores múltiples. Incluso puede especificar la lista finita de valores aceptables para un interruptor en particular. Los nombres largos de los interruptores se pueden abreviar en la línea de comandos para mayor comodidad. Similar al analizador de opciones en la biblioteca estándar de Ruby.

Sellmerfud
fuente
2

Nunca me han gustado los analizadores de rubíes como opción. La mayoría de los desarrolladores que los usaron nunca escriben una página de manual adecuada para sus scripts y terminan con opciones largas de páginas que no están organizadas de manera adecuada debido a su analizador.

Siempre he preferido la forma en que Perl hace las cosas con Perl's Getopt :: Long .

Estoy trabajando en una implementación scala de la misma. La primera API se parece a esto:

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

Entonces llamando scriptasí:

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

Imprimiría:

higher order function
version is 0.2

Y volver:

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

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

El proyecto está alojado en github scala-getoptions .

DavidG
fuente
2

Acabo de crear mi enumeración simple

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

Entiendo que la solución tiene dos fallas principales que pueden distraerlo: elimina la libertad (es decir, la dependencia de otras bibliotecas, que valora tanto) y la redundancia (el principio DRY, escribe el nombre de la opción solo una vez, como programa Scala variable y eliminarlo por segunda vez escrito como texto de línea de comando).

Val
fuente
2

Sugeriría usar http://docopt.org/ . Hay un puerto scala, pero la implementación de Java https://github.com/docopt/docopt.java funciona bien y parece estar mejor mantenida. Aquí hay un ejemplo:

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
Holger Brandl
fuente
2

Esto es lo que cociné. Devuelve una tupla de un mapa y una lista. La lista es para la entrada, como los nombres de los archivos de entrada. El mapa es para interruptores / opciones.

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

volverá

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

Los interruptores pueden ser "--t", que x se establecerá en verdadero, o "--x 10", que x se establecerá en "10". Todo lo demás terminará en la 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)
    }
  }
}
Auselen
fuente
1

Me gusta el aspecto limpio de este código ... extraído de una discusión aquí: 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)
  }

}
Alan Jurgensen
fuente
1

Como todos publicaron su propia solución aquí es mía, porque quería algo más fácil de escribir para el usuario: https://gist.github.com/gwenzek/78355526e476e08bb34d

La esencia contiene un archivo de código, más un archivo de prueba y un breve ejemplo copiado aquí:

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

No hay opciones sofisticadas para forzar que una variable esté dentro de ciertos límites, porque no creo que el analizador sea el mejor lugar para hacerlo.

Nota: puede tener tanto alias como desee para una variable dada.

gwenzek
fuente
1

Me voy a amontonar. Resolví esto con una simple línea de código. Mis argumentos de línea de comando se ven así:

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

Esto crea una matriz a través de la funcionalidad de línea de comando nativa de Scala (desde la aplicación o un método principal):

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

Entonces puedo usar esta línea para analizar la matriz de argumentos predeterminada:

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

Lo que crea un mapa con nombres asociados con los valores de la línea de comando:

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

Entonces puedo acceder a los valores de los parámetros con nombre en mi código y el orden en que aparecen en la línea de comandos ya no es relevante. Me doy cuenta de que esto es bastante simple y no tiene todas las funciones avanzadas mencionadas anteriormente, pero parece ser suficiente en la mayoría de los casos, solo necesita una línea de código y no involucra dependencias externas.

J Calbreath
fuente
1

Aquí está el mío 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)

Deja caer 3 argumentos obligatorios y da las opciones. Los enteros se especifican como una notoria -Xmx<size>opción de Java, conjuntamente con el prefijo. Puede analizar binarios y enteros tan simples como

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

No es necesario importar nada.

Valentin Tihomirov
fuente
0

Un trazo rápido y sucio del pobre para analizar pares clave = valor:

def main(args: Array[String]) {
    val cli = args.map(_.split("=") match { case Array(k, v) => k->v } ).toMap
    val saveAs = cli("saveAs")
    println(saveAs)
}
botkop
fuente
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
}

Esto generará el siguiente uso:

Uso

Pavlosgi
fuente