252 votes

Meilleure façon d'analyser les paramètres de la ligne de commande ?

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 :

236voto

pjotrp Points 791

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 !

0 votes

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 !

4 votes

IsSwitch vérifie simplement que le premier caractère est un tiret '-'.

0 votes

J'obtiens un message : cannot resolve symbol exit. Quelqu'un peut-il m'aider à comprendre pourquoi cela se produit ?

201voto

Eugene Yokota Points 43213

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
}

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)

6 votes

J'aime beaucoup mieux le patron constructeur DSL, car il permet de déléguer la construction des paramètres aux modules.

3 votes

Note : contrairement à ce qui est montré, scopt n'a pas besoin de tant d'annotations de type.

0 votes

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.

62voto

rintcius Points 1223

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)

5 votes

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

0 votes

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

1 votes

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 .

17voto

Brian Clapper Points 11222

Une publicité éhontée, éhontée : Argot

13voto

Alain O'Dea Points 6587

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

0 votes

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

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.

Powered by:

X