poligono pairs lineas linea grafico graficas graficar grafica funcion ejemplos diagramas scala command-line command-line-parsing

scala - lineas - pairs en r



¿La mejor manera de analizar los parámetros de la línea de comandos? (26)

Kit de herramientas Scala de interfaz de línea de comandos (CLIST)

¡Aquí está la mía 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 muy buena sintaxis:

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

Por supuesto, puede hacer mucho más (varios comandos, 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):

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

Relacionado:



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)


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 distraerle: elimina la libertad (es decir, la dependencia de otras bibliotecas, que tanto valora) y la redundancia (el principio DRY, usted escribe el nombre de la opción solo una vez, como el programa Scala variable y eliminarlo por segunda vez escrito como texto de línea de comandos).



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 la opción java -Xmx<size> notoria, junto con el prefijo. Puedes analizar binarios y enteros tan simples como

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

No hay necesidad de importar nada.


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 conmutación al tipo deseado. Se admiten tanto los interruptores cortos POSIX como los largos de estilo GNU. Admite conmutadores con argumentos requeridos, argumentos opcionales y argumentos de valor múltiple. 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.


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

Devuelve un Map[String,String] de todos los parámetros de la línea de comandos. Puede consultar los parámetros específicos que desee (p. Ej., .contains ) o convertir los valores a los tipos que desee (p. toInt ., 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)


Cómo analizar parámetros sin una dependencia externa. Gran pregunta Usted puede estar interesado en picocli .

Picocli está diseñado específicamente 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 el formato de origen . 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 código. Sumario rápido:

  • Se escribe todo con fuerza: opciones de línea de comando y parámetros posicionales
  • Compatibilidad con las opciones cortas agrupadas de POSIX (de modo que maneja <command> -xvfInputFile así 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.
  • Subcomandos
  • Uso de ayuda con colores ANSI.

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

( source )

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

Descargo de responsabilidad: he creado 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.


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

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 creo que el analizador sea el mejor lugar para hacerlo.

Nota: puede tener tantos alias como desee para una variable determinada.


Esto 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 de argumentos automáticos.

JewelCLI es una biblioteca de Java amigable con Scala para el análisis de línea de comandos que produce código limpio . Utiliza Interfaces Proxied configuradas con anotaciones para construir dinámicamente una API de tipo seguro para sus parámetros de 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 también el JewelCLI 0.6 JAR 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


Esto es lo que cociné. Devuelve una tupla de un mapa y una lista. La lista es para entrada, como nombres de 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" y x se configurará 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) } } }


He intentado generalizar la solución de @ pjotrp tomando una lista de los símbolos clave de posición requeridos, un mapa de bandera -> símbolo 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) }


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 golpean esta página.

Scallop parece bastante prometedor también.

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

  • opciones de bandera, valor único y valor múltiple
  • Nombres cortos de opciones de estilo POSIX (-a) con agrupación (-abc)
  • Nombres largos de opciones de estilo GNU (--opt)
  • Argumentos de propiedad (-Dkey = valor, -D key1 = value key2 = value)
  • Tipos de opciones y valores de propiedades sin cadena (con convertidores ampliables)
  • Emparejamiento de gran alcance 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)


Me gustó el enfoque slide () de joslinm, no los vars mutables;) Así que aquí hay una manera 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 } }


Me gusta sliding 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 }


Me gusta el aspecto limpio de este código ... obtenido 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) } }


Me voy a apilar. 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 comandos nativa de Scala (desde una 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")

Luego puedo usar esta línea para analizar la matriz args predeterminada:

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 comando:

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

Luego puedo acceder a los valores de los parámetros nombrados 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 implica dependencias externas.


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

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

Estoy trabajando en una implementación de la escala. 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, ))

Así que llamando script como este:

$ 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 se encuentra alojado en github scala-getoptions .


Para la mayoría de los casos no necesita un analizador externo. La coincidencia de patrones de Scala permite consumir argumentos 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 lleva 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!


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)

En 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.


También está 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) } } }


Una sola línea rápida y sucia de hombres pobres 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) }


Yo sugeriría usar http://docopt.org/ . Hay un puerto de escala pero la implementación de Java https://github.com/docopt/docopt.java funciona bien y parece que se mantiene mejor. 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



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:

Usage