Quelle est la meilleure façon d'analyser les paramètres de ligne de commande en Scala ? Je préfère personnellement quelque chose de léger qui ne nécessite pas de jar externe.
En rapport :
Quelle est la meilleure façon d'analyser les paramètres de ligne de commande en Scala ? Je préfère personnellement quelque chose de léger qui ne nécessite pas de jar externe.
En rapport :
Dans la plupart des cas, vous n'avez pas besoin d'un analyseur externe. Le filtrage de Scala permet de consommer les 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)
}
}
s'imprimera, par exemple :
Map('infile -> test/data/paml-aln1.phy, 'maxsize -> 4, 'minsize -> 2)
Cette version ne prend qu'un seul fichier d'entrée. 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 !
Très bien. Je me demande à quoi sert exactement la vérification "isSwitch" et pourquoi elle est utilisée dans la quatrième déclaration de cas ? Merci pour cet excellent exemple !
J'obtiens un message : cannot resolve symbol exit. Quelqu'un peut-il m'aider à comprendre pourquoi cela se produit ?
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
Voici ce que j'utilise actuellement. Une utilisation propre sans trop d'inconvénients. (Disclaimer : Je maintiens maintenant ce projet)
J'aime beaucoup mieux le patron constructeur DSL, car il permet de déléguer la construction des paramètres aux modules.
Note : contrairement à ce qui est montré, scopt n'a pas besoin de tant d'annotations de type.
Scopt et scallop sont disponibles dans Maven Central ; les autres ne semblent pas l'être (je n'ai pas pu les trouver). De plus, il semble que paulp/optional soit devenu alexy/optional.
Je me rends compte que la question a été posée il y a un certain temps, mais j'ai pensé que cela pourrait aider certaines personnes qui, en cherchant sur Internet (comme moi), sont tombées sur cette page.
Coquille Saint-Jacques semble également très prometteur.
Caractéristiques (citation de la page github liée) :
- drapeau, options à valeur unique et à 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 de valeurs d'options et de propriétés autres que des chaînes de caractères (avec convertisseurs extensibles)
- Correspondance puissante sur les arguments de fin de ligne
- Sous-commandes
Et quelques exemples de code (également issus 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)
La coquille Saint-Jacques l'emporte haut la main sur les autres en termes de fonctionnalités. Dommage que la tendance habituelle de l'OS "la première réponse gagne" l'ait fait descendre dans la liste :(
Je suis d'accord. Je laisse un commentaire ici juste au cas où @Eugene Yokota aurait oublié de prendre note. Consultez ce blog coquille Saint-Jacques
Le problème qu'il décrit avec scopt est "Il semble bon, mais il est incapable d'analyser les options, qui prennent une liste d'arguments (i.e. -a 1 2 3). Et vous n'avez aucun moyen de l'étendre pour obtenir ces listes (à part forker la librairie)" mais ce n'est plus vrai, voir github.com/scopt/scopt#options .
Une publicité éhontée, éhontée : Argot
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 sens qu'il ne nécessite pas de méthodes de type JavaBean pour obtenir un nommage automatique des arguments.
JewelCLI est un Bibliothèque Java adaptée à Scala pour l'analyse syntaxique des lignes de commande et produisant un code propre . Il utilise des interfaces proxy configurées avec des annotations pour construire dynamiquement une API sécurisée 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)
}
}
}
Sauvegardez des copies des fichiers ci-dessus dans un seul répertoire et téléchargez l'application JewelCLI 0.6 JAR dans ce répertoire également.
Compilez et exécutez l'exemple en Bash sur 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 commande 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 le résultat suivant :
Hello John Doe
Hello John Doe
Hello John Doe
Un élément amusant que vous pouvez remarquer est le (args : _*). L'appel des méthodes varargs de Java à partir de Scala nécessite ce paramètre. C'est une solution que j'ai apprise de daily-scala.blogspot.com/2009/11/varargs.html sur l'excellent blog Daily Scala de Jesse Eichar. Je recommande vivement Daily Scala :)
Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.