La meilleure façon d'analyser les paramètres de ligne de commande? [fermé]

237

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:

Eugene Yokota
la source

Réponses:

228

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!

pjotrp
la source
4
isSwitch vérifie simplement que le premier caractère est un tiret '-'
pjotrp
6
nextOptionn'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 maxfonction pour une collection et l'appeler nextMaxsimplement parce que vous l'avez écrite avec une récursivité explicite. Pourquoi ne pas simplement l'appeler optionMap?
itsbruce
4
@itsbruce Je veux juste ajouter / modifier votre point - il serait plus "approprié" d'une lisibilité / maintenabilité de définir listToOptionMap(lst:List[String])avec la fonction nextOptiondé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.
tresbot
6
@theMadKing dans le code ci exit(1)- dessus peut devoir êtresys.exit(1)
tresbot
3
J'aime ta solution. Voici la modification de gérer plusieurs fileparamè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 OptionMapvaleurs sont de type Any. Y a-t-il une meilleure solution?
Mauro Lacy
196

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

C'est ce que j'utilise actuellement. Utilisation propre sans trop de bagages. (Avertissement: je maintiens maintenant ce projet)

Eugene Yokota
la source
6
J'aime beaucoup le modèle de générateur DSL, car il permet la délégation de la construction des paramètres aux modules.
Daniel C.Sobral
3
Remarque: contrairement à ce qui est illustré, scopt n'a pas besoin d'autant d'annotations de type.
Blaisorblade
9
Si vous l'utilisez pour analyser des arguments pour un travail d'étincelle, soyez averti qu'ils ne jouent pas bien ensemble. Littéralement, rien de ce que j'ai essayé n'a pu faire fonctionner spark-submit avec scopt :-(
jbrown
4
@BirdJaguarIV Si spark utilise scopt, c'était probablement le problème - des versions conflictuelles dans le pot ou quelque chose. J'utilise plutôt du pétoncle avec des étincelles et je n'ai eu aucun problème.
jbrown
12
Ironiquement, bien que cette bibliothèque génère automatiquement une bonne documentation CLI, le code ne semble guère mieux que brainf * ck.
Jonathan Neufeld
58

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)
rintcius
la source
4
Scallop pwns le reste haut la main en termes de fonctionnalités. Dommage que la tendance SO habituelle de «première réponse l'emporte» ait poussé cela vers le bas de la liste :(
samthebest
Je suis d'accord. Laisser un commentaire ici au cas où @Eugene Yokota a manqué de prendre une note. Consultez ce blog sur le pétoncle
Pramit
1
Le problème qu'il mentionne avec scopt est "Il a l'air bien, mais il est incapable d'analyser les options, qui prennent une liste d'arguments (c'est-à-dire -a 1 2 3). Et vous n'avez aucun moyen de l'étendre pour obtenir ces listes (sauf bifurquer le lib). " mais ce n'est plus vrai, voir github.com/scopt/scopt#options .
Alexey Romanov
2
c'est plus intuitif et moins passe-partout que scopt. pas plus (x, c) => c.copy(xyz = x) dans scopt
WeiChing 林 煒 清
43

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
}
joslinm
la source
2
Intelligent. Ne fonctionne que si chaque argument spécifie également une valeur, non?
Brent Faust
2
Ne devrait-il pas en être ainsi args.sliding(2, 2)?
m01
1
Ne devrait-il pas en être ainsi var port = 0?
swdev
17

Interface de ligne de commande Scala Toolkit (CLIST)

voici la mienne aussi! (un peu tard dans le jeu cependant)

https://github.com/backuity/clist

Par opposition, scoptil 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): clist

Bruno Bieth
la source
A-t-il une validation?
KF
Oui, c'est le cas (voir github.com/backuity/clist/blob/master/demo/src/main/scala/… pour un exemple). Ce n'est pas documenté cependant ... PR? :)
Bruno Bieth
Je l'ai essayé, assez pratique. J'ai utilisé scopt avant, je ne suis toujours pas habitué à ajouter des validations ensemble, mais pas seulement dans la définition de chaque paramètre. Mais ça marche bien avec moi. Et définir différents paramètres et validations dans différents traits, puis les combiner dans différents cas, c'est vraiment utile. J'ai beaucoup souffert dans scopt quand il n'est pas pratique de réutiliser les paramètres. Merci pour votre réponse!
KF
La plupart des validations sont effectuées lors de la désérialisation des paramètres de ligne de commande (voir Lecture ), donc si vous pouvez définir vos contraintes de validation par le biais de types (c'est Password-à- dire Hex, ...), vous pouvez en tirer parti.
Bruno Bieth
13

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
Alain O'Dea
la source
Un élément amusant dans ce que vous pouvez remarquer est le (arguments: _ *). L'appel de méthodes varargs Java à partir de Scala nécessite cela. 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 :)
Alain O'Dea
12

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:

  • Tout fortement saisi - options de ligne de commande ainsi que paramètres de position
  • Prise en charge des options courtes en cluster POSIX (donc il gère <command> -xvfInputFileaussi bien que <command> -x -v -f InputFile)
  • Un modèle d'arité qui permet un nombre minimum, maximum et variable de paramètres, par exemple "1..*","3..5"
  • API fluide et compacte pour minimiser le code client standard
  • Sous-commandes
  • Aide à l'utilisation des couleurs ANSI

Le message d'aide à l'utilisation est facile à personnaliser avec des annotations (sans programmation). Par exemple:

Message d'aide sur l'utilisation étendue( 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!

démo picocli

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.

Remko Popma
la source
1
Pourquoi le downvote? C'est la seule bibliothèque à ma connaissance qui est spécifiquement conçue pour résoudre le problème mentionné dans l'OP: comment éviter d'ajouter une dépendance.
Remko Popma
"encourager les auteurs d'applications à l'inclure". Bon travail.
keos
avez-vous des exemples de scala?
CruncherBigData
1
J'ai commencé à créer des exemples pour d'autres langages JVM: github.com/remkop/picocli/issues/183 Vos commentaires et contributions sont les bienvenus!
Remko Popma
11

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:

spécification

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]"

}

Analyser

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

Sur des arguments invalides

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.
Thamme Gowda
la source
10

scala-optparse-applicative

Je pense que scala-optparse-applicative est la bibliothèque d'analyseur de ligne de commande la plus fonctionnelle de Scala.

https://github.com/bmjames/scala-optparse-applicative

Kenji Yoshida
la source
a-t-il des exemples / doc en plus de ce qui est dans le README?
Erik Kaplun
1
Il le fait, vérifiez le examplesdans le code de test
gpampara
8

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)
    }
  }
}
Cedric Beust
la source
2
J'aime celui la. ces analyseurs 'pure scala' n'ont pas de syntaxe propre
tactoth
@tactoth vérifiez celui-ci, il a une syntaxe claire: stackoverflow.com/questions/2315912/…
Bruno Bieth
6

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
  }
}
haggy
la source
3

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)
}
Byron Ruth
la source
J'ai mis à jour ce morceau de code pour gérer les drapeaux (pas seulement les options avec des valeurs) et aussi pour mandle définir l'option / le drapeau avec des formes courtes et longues. par exemple -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.
DavidG
3

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)
bjorno
la source
2

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.

sellmerfud
la source
2

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

DavidG
la source
2

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

Val
la source
2

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
Holger Brandl
la source
2

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)
    }
  }
}
auselen
la source
1

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

}
Alan Jurgensen
la source
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.

gwenzek
la source
1

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.

J Calbreath
la source
1

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.

Valentin Tihomirov
la source
0

One-liner rapide et sale du pauvre pour l'analyse des paires clé = valeur:

def main(args: Array[String]) {
    val cli = args.map(_.split("=") match { case Array(k, v) => k->v } ).toMap
    val saveAs = cli("saveAs")
    println(saveAs)
}
botkop
la source
0

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
}

Cela générera l'utilisation suivante:

Usage

pavlosgi
la source