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


¿Cuál es la mejor manera de analizar parámetros de línea de comandos en Scala? Personalmente prefiero algo ligero que no requiere tarro externo.

Relacionado:

Author: Community, 2010-02-23

26 answers

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

Se 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 la línea de comandos, ¡incluso más de dos!

 202
Author: pjotrp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-21 20:47:30

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)

 186
Author: Eugene Yokota,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-10 15:58:44

Me doy 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 golpear esta página.

Vieira parece bastante prometedor también.

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

  • indicador, opciones de valor único y valor múltiple
  • Nombres de opción cortos de estilo POSIX (- a) con agrupación (- abc)
  • Nombres largos de opciones al estilo GNU (opt opt)
  • Argumentos de propiedad (- Dkey=valor, - D key1=valor key2=valor)
  • Tipos de opciones y valores de propiedades sin cadena (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)
 42
Author: rintcius,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-11-11 23:18:35

Me gusta deslizar 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
}
 24
Author: joslinm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-29 17:55:03

Esto es en gran parte un clon descarado de mi respuesta a la pregunta de Java del 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 línea de comandos que produce código limpio . Utiliza Interfaces Proxy Configuradas con Anotaciones para crear dinámicamente una API segura para sus parámetros de línea de comandos.

Un ejemplo 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 el JewelCLI 0.6 JAR en ese directorio también.

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

Al ejecutar el ejemplo se debe obtener la siguiente salida:

Hello John Doe
Hello John Doe
Hello John Doe
 13
Author: Alain O'Dea,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:02:56

Interfaz de Línea de comandos Scala Toolkit (CLIST)

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

Https://github.com/backuity/clist

A diferencia de scopt es completamente mutable... pero espera! Eso nos da una sintaxis bastante agradable:

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 sencilla de ejecutarlo:

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

Usted puede hacer mucho más por supuesto (multi-comandos, muchas opciones de configuración,...) y no tiene ninguna dependencia.

Terminaré con una especie de rasgo distintivo, el uso predeterminado (muy a menudo descuidado para comandos múltiples): clist

 12
Author: Bruno Bieth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-13 08:15:14

Soy de Java world, me gusta args4j porque su simple especificación es más legible( gracias a las anotaciones) y produce una salida muy bien formateada.

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

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

En argumentos no vá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.
 9
Author: Thamme Gowda,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-25 23:12:24

También hay JCommander (descargo de responsabilidad: Yo 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)
    }
  }
}
 8
Author: Cedric Beust,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-16 17:33:15

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

 8
Author: Kenji Yoshida,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-30 15:57:21

Me gustó el enfoque slide() de joslinm, pero no los var 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
  }
}
 4
Author: haggy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-06 15:03:09

Acabo de encontrar una extensa biblioteca de análisis de línea de comandos en scala de scalac.herramienta.paquete cmd.

Véase http://www.assembla.com/code/scala-eclipse-toolchain/git/nodes/src/compiler/scala/tools/cmd?rev=f59940622e32384b1e08939effd24e924a8ba8db

 3
Author: Pablo Lalloni,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-26 17:17:12

He intentado generalizar la solución de @pjotrp tomando una lista de símbolos de clave posicionales requeridos, un mapa de flag - > símbolo de clave 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)
}
 3
Author: Byron Ruth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-27 17:24:26

Basé mi enfoque en la respuesta principal (de dave4420), e intenté mejorarla haciéndola más de propósito general.

Devuelve un 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, usando .contains) o convertir los valores en los tipos que desee (por ejemplo, usando 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)
 3
Author: bjorno,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-02 08:59:14

Otra biblioteca: scarg

 2
Author: Anonymous,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-11-21 00:14:11

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

 2
Author: sellmerfud,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-30 17:53:58

Acabo de crear mi simple enumeración

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 tanto valora) y la redundancia (el principio SECO, escribe el nombre de la opción solo una vez, como variable del programa Scala y lo elimina por segunda vez como texto de línea de comandos).

 2
Author: Val,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:33

Sugiero usar http://docopt.org / . Hay un puerto scala pero la implementación de Java https://github.com/docopt/docopt.java funciona muy bien y parece estar mejor mantenido. He aquí 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
 2
Author: Holger Brandl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-11 13:41:15

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 comandos en un solo archivo, por lo que puede incluirlo en forma de fuente. Esto permite a los usuarios ejecutar aplicaciones basadas en picocli sin requerir picocli como una dependencia externa.

Funciona anotando campos para que escribas muy poco codificar. Resumen rápido:

  • Todo fuertemente escrito-opciones de línea de comandos, así como parámetros posicionales
  • Soporte para opciones cortas agrupadas POSIX (por lo que maneja <command> -xvfInputFile así como <command> -x -v -f InputFile)
  • Un modelo de arity 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 de cliente repetitivo
  • Subcomandos
  • Ayuda de uso con colores ANSI

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

Mensaje de ayuda de uso extendido (source)

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

demostración de picocli

Descargo de responsabilidad: Creé picocli. Comentarios o preguntas muy bienvenidos. Está escrito en Java, pero avísame si hay algún problema al usarlo en scala y yo trataremos de resolverlo.

 2
Author: Remko Popma,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-05 11:21:09

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

}
 1
Author: Alan Jurgensen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-02 20:01:45

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

Siempre he preferido la forma de Perl de hacer las cosas con Perl Getopt::Long .

Estoy trabajando en una implementación de scala. La API temprana se ve algo como 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,
    ))

Llamando así a script así:

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

Haría print:

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.

 1
Author: DavidG,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-11 03:37:59

Como todo el mundo publicó 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

El gist 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é en algunos límites, porque no siento que el analizador sintáctico sea el mejor lugar para hacerlo.

Nota: puede tener tanto alias como desee para un determinado variable.

 1
Author: gwenzek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-16 16:52:37

Voy a acumular. Resolví esto con una simple línea de código. Mis argumentos de la línea de comandos 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 comandos 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 el array args predeterminado:

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

Que crea un mapa con nombres asociados con los valores de la línea de comandos:

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 nombrados en mi código y el orden aparecen en la línea de comandos ya no es relevante. Me doy cuenta de que esto es bastante simple y no tiene toda la funcionalidad avanzada mencionada 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.

 1
Author: J Calbreath,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-30 13:47:46

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)

Elimina 3 argumentos obligatorios y da las opciones. Los enteros se especifican como notorious -Xmx<size> opción java, junto con el prefijo. Puede analizar binarios e enteros tan simple como

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

No hay necesidad de importar nada.

 1
Author: Valentin Tihomirov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-07 21:28:18

Esto es lo que cociné. Devuelve una tupla de un mapa y una lista. La lista es para la entrada, como los nombres de archivo de entrada. El mapa es para conmutadores / 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 conmutadores pueden ser "t t", que x se establecerá en true, o "true 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)
    }
  }
}
 1
Author: auselen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-17 15:17:49

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

 1
Author: pavlosgi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-27 12:22:32

Poor man's quick-and-dirty one-liner para analizar clave = pares de valores:

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
Author: botkop,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-29 18:20:44