¿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:
¿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:
Respuestas:
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!
nextOption
No 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 max
función para una colección y llamarla nextMax
simplemente porque la escribió con recursividad explícita. ¿Por qué no solo llamarlo optionMap
?
listToOptionMap(lst:List[String])
con la función nextOption
definida 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.
exit(1)
puede ser necesariosys.exit(1)
file
pará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 OptionMap
valores son de tipo Any
. ¿Hay una mejor solución?
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)
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)
(x, c) => c.copy(xyz = x)
en alcance
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
}
args.sliding(2, 2)
?
var port = 0
?
¡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):
Password
, Hex
, ...), entonces se puede aprovechar esto.
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
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:
<command> -xvfInputFile
tan bien como <command> -x -v -f InputFile
)"1..*"
,"3..5"
El mensaje de ayuda de uso es fácil de personalizar con anotaciones (sin programación). Por ejemplo:
( 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!
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.
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:
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]"
}
//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)
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.
Creo que scala-optparse-Applicative es la biblioteca de analizador de línea de comandos más funcional de Scala.
examples
código de prueba
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)
}
}
}
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
}
}
Acabo de encontrar una extensa biblioteca de análisis de línea de comandos en el paquete scala.tools.cmd de scalac.
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)
}
-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.
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)
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.
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 script
así:
$ 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 .
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).
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
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)
}
}
}
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)
}
}
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.
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.
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.
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: