Quelle est la meilleure façon d'analyser les paramètres de ligne de commande dans Scala? Personnellement, je préfère quelque chose de léger qui ne nécessite pas de pot externe.
En relation:
Quelle est la meilleure façon d'analyser les paramètres de ligne de commande dans Scala? Personnellement, je préfère quelque chose de léger qui ne nécessite pas de pot externe.
En relation:
Réponses:
Dans la plupart des cas, vous n'avez pas besoin d'un analyseur externe. La correspondance de motifs de Scala permet de consommer des arguments dans un style fonctionnel. Par exemple:
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)
}
}
imprimera, par exemple:
Map('infile -> test/data/paml-aln1.phy, 'maxsize -> 4, 'minsize -> 2)
Cette version ne prend qu'un infile. Facile à améliorer (en utilisant une liste).
Notez également que cette approche permet la concaténation de plusieurs arguments de ligne de commande - même plus de deux!
nextOption
n'est pas un bon nom pour la fonction. C'est une fonction qui renvoie une carte - le fait qu'elle soit récursive est un détail d'implémentation. C'est comme écrire une max
fonction pour une collection et l'appeler nextMax
simplement parce que vous l'avez écrite avec une récursivité explicite. Pourquoi ne pas simplement l'appeler optionMap
?
listToOptionMap(lst:List[String])
avec la fonction nextOption
définie à l'intérieur, avec une dernière ligne disant return nextOption(Map(), lst)
. Cela dit, je dois avouer que j'ai fait des raccourcis beaucoup plus flagrants en mon temps que celui de cette réponse.
exit(1)
- dessus peut devoir êtresys.exit(1)
file
paramètres: case string :: tail => { if (isSwitch(string)) { println("Unknown option: " + string) sys.exit(1) } else nextOption(map ++ Map('files -> (string :: map('files).asInstanceOf[List[String]])), tail)
. La carte a également besoin d'une valeur par défaut de Nil
, ie val options = nextOption(Map() withDefaultValue Nil, args.toList)
. Ce que je n'aime pas, c'est de devoir y recourir asInstanceOf
, car les OptionMap
valeurs sont de type Any
. Y a-t-il une meilleure solution?
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
}
Ce qui précède génère le texte d'utilisation suivant:
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
C'est ce que j'utilise actuellement. Utilisation propre sans trop de bagages. (Avertissement: je maintiens maintenant ce projet)
Je me rends compte que la question a été posée il y a quelque temps, mais j'ai pensé que cela pourrait aider certaines personnes, qui font des recherches sur Google (comme moi), et ont frappé cette page.
Le pétoncle semble également très prometteur.
Caractéristiques (citation de la page github liée):
- options de drapeau, de valeur unique et de valeurs multiples
- Noms d'options courts de style POSIX (-a) avec regroupement (-abc)
- Noms d'options longs de style GNU (--opt)
- Arguments de propriété (-Dkey = valeur, -D key1 = valeur key2 = valeur)
- Types d'options et valeurs de propriétés sans chaîne (avec convertisseurs extensibles)
- Correspondance puissante sur les arguments de fin
- Sous-commandes
Et un exemple de code (également à partir de cette page 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)
dans scopt
J'aime glisser sur des arguments pour des configurations relativement 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
?
voici la mienne aussi! (un peu tard dans le jeu cependant)
https://github.com/backuity/clist
Par opposition, scopt
il est entièrement mutable ... mais attendez! Cela nous donne une syntaxe assez sympa:
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")
}
Et un moyen simple de l'exécuter:
Cli.parse(args).withCommand(new Cat) { case cat =>
println(cat.files)
}
Vous pouvez bien sûr faire beaucoup plus (multi-commandes, nombreuses options de configuration, ...) et cela n'a aucune dépendance.
Je terminerai avec une sorte de particularité, l'utilisation par défaut (assez souvent négligée pour les commandes multiples):
Password
-à- dire Hex
, ...), vous pouvez en tirer parti.
Il s'agit en grande partie d'un clone éhonté de ma réponse à la question Java du même sujet . Il s'avère que JewelCLI est compatible avec Scala en ce qu'il ne nécessite pas de méthodes de style JavaBean pour obtenir la dénomination automatique des arguments.
JewelCLI est une bibliothèque Java compatible Scala pour l'analyse en ligne de commande qui produit un code propre . Il utilise des interfaces proxy configurées avec des annotations pour créer dynamiquement une API de type sécurisé pour vos paramètres de ligne de commande.
Un exemple d'interface de paramètres Person.scala
:
import uk.co.flamingpenguin.jewel.cli.Option
trait Person {
@Option def name: String
@Option def times: Int
}
Un exemple d'utilisation de l'interface des paramètres 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)
}
}
}
Enregistrez des copies des fichiers ci-dessus dans un seul répertoire et téléchargez également le JAR JewelCLI 0.6 dans ce répertoire.
Compilez et exécutez l'exemple dans Bash sous 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
Compilez et exécutez l'exemple dans l'invite de commandes Windows:
scalac -cp jewelcli-0.6.jar;. Person.scala Hello.scala
scala -cp jewelcli-0.6.jar;. Hello --name="John Doe" --times=3
L'exécution de l'exemple devrait produire la sortie suivante:
Hello John Doe
Hello John Doe
Hello John Doe
Comment analyser les paramètres sans dépendance externe. Grande question! Vous pouvez être intéressé par picocli .
Picocli est spécialement conçu pour résoudre le problème posé dans la question: il s'agit d'un framework d'analyse de ligne de commande dans un seul fichier, vous pouvez donc l' inclure sous forme source . Cela permet aux utilisateurs d'exécuter des applications basées sur picocli sans avoir besoin de picocli comme dépendance externe .
Cela fonctionne en annotant les champs pour que vous écriviez très peu de code. Résumé rapide:
<command> -xvfInputFile
aussi bien que <command> -x -v -f InputFile
)"1..*"
,"3..5"
Le message d'aide à l'utilisation est facile à personnaliser avec des annotations (sans programmation). Par exemple:
( source )
Je n'ai pas pu résister à l'ajout d'une capture d'écran supplémentaire pour montrer quel type de messages d'aide à l'utilisation sont possibles. L'aide à l'utilisation est le visage de votre application, alors soyez créatif et amusez-vous!
Avertissement: j'ai créé picocli. Commentaires ou questions très bienvenus. Il est écrit en Java, mais faites-moi savoir s'il y a un problème avec Scala et j'essaierai de le résoudre.
Je viens du monde Java, j'aime args4j parce que sa spécification simple est plus lisible (grâce aux annotations) et produit une sortie bien formatée.
Voici mon exemple d'extrait:
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.
Je pense que scala-optparse-applicative est la bibliothèque d'analyseur de ligne de commande la plus fonctionnelle de Scala.
examples
dans le code de test
Il y a aussi JCommander (avertissement: je l'ai créé):
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)
}
}
}
J'ai aimé l'approche slide () de joslinm tout simplement pas les vars mutables;) Alors, voici une façon immuable de cette approche:
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
}
}
Je viens de trouver une vaste bibliothèque d'analyse de ligne de commande dans le package scala.tools.cmd de scalac.
J'ai tenté de généraliser la solution de @ pjotrp en prenant une liste des symboles clés de position requis, une carte du drapeau -> symbole de clé et des options par défaut:
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
. Jetez un œil à gist.github.com/DavidGamba/b3287d40b019e498982c et n'hésitez pas à mettre à jour la réponse si vous l'aimez. Je ferai probablement toutes les cartes et options afin que vous ne puissiez transmettre que ce dont vous aurez besoin avec des arguments nommés.
J'ai basé mon approche sur la première réponse (de dave4420), et j'ai essayé de l'améliorer en la rendant plus polyvalente.
Il renvoie un Map[String,String]
de tous les paramètres de ligne de commande. Vous pouvez le rechercher pour les paramètres spécifiques que vous souhaitez (par exemple en utilisant .contains
) ou convertir les valeurs dans les types que vous souhaitez (par exemple en utilisant 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())
}
Exemple:
val args=Array("--testing1","testing1","-a","-b","--c","d","test2")
argsToOptionMap( args )
Donne:
res0: Map[String,String] = Map(testing1 -> testing1, a -> null, b -> null, c -> d, test2 -> null)
Voici un analyseur de ligne de commande scala facile à utiliser. Il formate automatiquement le texte d'aide et convertit les arguments de commutation en votre type souhaité. Les commutateurs de style POSIX court et long GNU sont pris en charge. Prend en charge les commutateurs avec les arguments requis, les arguments facultatifs et les arguments à valeurs multiples. Vous pouvez même spécifier la liste finie de valeurs acceptables pour un commutateur particulier. Les noms de commutateur longs peuvent être abrégés sur la ligne de commande pour plus de commodité. Similaire à l'analyseur d'options de la bibliothèque standard Ruby.
Je n'ai jamais aimé ruby comme les analyseurs d'options. La plupart des développeurs qui les ont utilisés n'écrivent jamais une page de manuel appropriée pour leurs scripts et se retrouvent avec des options de pages longues non organisées correctement en raison de leur analyseur.
J'ai toujours préféré la façon de faire de Perl avec Getopt :: Long de Perl .
Je travaille sur une implémentation scala de celui-ci. La première API ressemble à ceci:
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,
))
Appelant ainsi script
comme ceci:
$ script hello -f --string=mystring -i 7 --float 3.14 --p --version world -- --nothing
Imprime:
higher order function
version is 0.2
Et retour:
remaining = Array("hello", "world", "--nothing")
options = Map('flag -> true,
'string -> "mystring",
'int -> 7,
'double -> 3.14)
Le projet est hébergé dans github scala-getoptions .
Je viens de créer ma simple énumération
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
Je comprends que cette solution a deux défauts majeurs qui peuvent vous distraire: elle élimine la liberté (c'est-à-dire la dépendance à d'autres bibliothèques, que vous appréciez tant) et la redondance (le principe DRY, vous ne tapez le nom de l'option qu'une seule fois, comme programme Scala variable et supprimez-la une deuxième fois tapée comme texte de ligne de commande).
Je suggère d'utiliser http://docopt.org/ . Il y a un port scala mais l'implémentation Java https://github.com/docopt/docopt.java fonctionne très bien et semble être mieux entretenue. Voici un exemple:
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
C'est ce que j'ai cuisiné. Il renvoie un tuple d'une carte et une liste. La liste est pour l'entrée, comme les noms de fichiers d'entrée. La carte est pour les commutateurs / options.
val args = "--sw1 1 input_1 --sw2 --sw3 2 input_2 --sw4".split(" ")
val (options, inputs) = OptParser.parse(args)
reviendra
options: Map[Symbol,Any] = Map('sw1 -> 1, 'sw2 -> true, 'sw3 -> 2, 'sw4 -> true)
inputs: List[Symbol] = List('input_1, 'input_2)
Les commutateurs peuvent être «--t», où x sera défini sur vrai, ou «--x 10», où x sera défini sur «10». Tout le reste se retrouvera dans la liste.
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)
}
}
}
J'aime l'aspect épuré de ce code ... glané d'une discussion ici: 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)
}
}
Comme tout le monde a posté sa propre solution ici est la mienne, car je voulais quelque chose de plus facile à écrire pour l'utilisateur: https://gist.github.com/gwenzek/78355526e476e08bb34d
L'essentiel contient un fichier de code, plus un fichier de test et un court exemple copié ici:
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)
}
}
Il n'y a pas d'options sophistiquées pour forcer une variable à se trouver dans certaines limites, car je ne pense pas que l'analyseur soit le meilleur endroit pour le faire.
Remarque: vous pouvez avoir autant d'alias que vous le souhaitez pour une variable donnée.
Je vais m'empiler. J'ai résolu cela avec une simple ligne de code. Mes arguments de ligne de commande ressemblent à ceci:
input--hdfs:/path/to/myData/part-00199.avro output--hdfs:/path/toWrite/Data fileFormat--avro option1--5
Cela crée un tableau via la fonctionnalité de ligne de commande native de Scala (à partir de l'application ou d'une méthode principale):
Array("input--hdfs:/path/to/myData/part-00199.avro", "output--hdfs:/path/toWrite/Data","fileFormat--avro","option1--5")
Je peux ensuite utiliser cette ligne pour analyser le tableau d'arguments par défaut:
val nArgs = args.map(x=>x.split("--")).map(y=>(y(0),y(1))).toMap
Ce qui crée une carte avec des noms associés aux valeurs de ligne de commande:
Map(input -> hdfs:/path/to/myData/part-00199.avro, output -> hdfs:/path/toWrite/Data, fileFormat -> avro, option1 -> 5)
Je peux alors accéder aux valeurs des paramètres nommés dans mon code et l'ordre dans lequel ils apparaissent sur la ligne de commande n'est plus pertinent. Je me rends compte que c'est assez simple et n'a pas toutes les fonctionnalités avancées mentionnées ci-dessus, mais semble être suffisant dans la plupart des cas, n'a besoin que d'une ligne de code et n'implique pas de dépendances externes.
Voici le mien 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)
Il supprime 3 arguments obligatoires et donne les options. Les entiers sont spécifiés comme une -Xmx<size>
option java notoire , conjointement avec le préfixe. Vous pouvez analyser des binaires et des entiers aussi simples que
val cacheEnabled = optSpecified("cacheOff")
val memSize = optInt("-Xmx", 1000)
Pas besoin d'importer quoi que ce soit.
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
}
Cela générera l'utilisation suivante: