Melhor maneira de analisar parâmetros de linha de comando? [fechadas]


237

Qual é a melhor maneira de analisar parâmetros de linha de comando no Scala? Pessoalmente, prefiro algo leve que não requer jar externo.

Palavras-chave:

Respostas:


228

Na maioria dos casos, você não precisa de um analisador externo. A correspondência de padrões do Scala permite consumir argumentos em um estilo funcional. Por exemplo:

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

Map('infile -> test/data/paml-aln1.phy, 'maxsize -> 4, 'minsize -> 2)

Esta versão leva apenas um infile. Fácil de melhorar (usando uma Lista).

Observe também que essa abordagem permite concatenação de vários argumentos de linha de comando - até mais de dois!


4
isSwitch simplesmente verifica para o primeiro caractere sendo um traço '-'
pjotrp

6
nextOptionnão é um bom nome para a função. É uma função que retorna um mapa - o fato de ser recursivo é um detalhe da implementação. É como escrever uma maxfunção para uma coleção e chamá-la nextMaxsimplesmente porque você a escreveu com recursão explícita. Por que não chamar optionMap?
itsbruce

4
@itsbruce Eu só quero adicionar / modificar seu ponto - seria mais "adequado" de uma legibilidade / manutenção definir listToOptionMap(lst:List[String])com a função nextOptiondefinida dentro disso, com uma linha final dizendo return nextOption(Map(), lst). Dito isto, devo confessar que fiz atalhos muito mais flagrantes no meu tempo do que o desta resposta.
tresbot

6
@theMadKing no código acima exit(1)pode precisar sersys.exit(1)
tresbot

3
Eu gosto da sua solução. Aqui está a modificação de lidar com vários fileparâ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). O mapa também precisa de um valor padrão de Nil, ie val options = nextOption(Map() withDefaultValue Nil, args.toList). O que eu não gosto é ter que recorrer asInstanceOf, devido aos OptionMapvalores serem do tipo Any. Existe uma solução melhor?
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
}

O acima gera o seguinte 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

É isso que eu uso atualmente. Uso limpo sem muita bagagem. (Isenção de responsabilidade: agora mantenho este projeto)


6
Gosto muito do DSL do padrão do construtor, porque permite a delegação da construção de parâmetros nos módulos.
Daniel C. Sobral

3
Nota: ao contrário do mostrado, o scopt não precisa de muitas anotações de tipo.
Blaisorblade

9
Se você estiver usando isso para analisar argumentos para um trabalho de faísca, saiba que eles não funcionam bem juntos. Literalmente nada Tentei poderia começar faísca a apresentar ao trabalho com scopt :-(
jbrown

4
@BirdJaguarIV Se o spark usa o scopt, esse provavelmente foi o problema - versões conflitantes no jar ou algo assim. Eu uso vieiras com trabalhos de faísca e não tive nenhum problema.
usar o seguinte comando

12
Ironicamente, embora essa biblioteca gere automaticamente boa documentação da CLI, o código parece um pouco melhor que o brainf * ck.
22615 Jonathan Neufeld

57

Sei que a pergunta foi feita há algum tempo, mas achei que poderia ajudar algumas pessoas, que estão pesquisando no Google (como eu), e acessaram esta página.

Vieiras parece bastante promissor também.

Recursos (citação da página do github vinculada):

  • opções de sinalizador, valor único e vários valores
  • Nomes curtos de opção no estilo POSIX (-a) com agrupamento (-abc)
  • Nomes de opções longas no estilo GNU (--opt)
  • Argumentos de propriedade (-Dkey = valor, -D chave1 = valor chave2 = valor)
  • Tipos não sequenciais de opções e valores de propriedades (com conversores extensíveis)
  • Correspondência poderosa em argumentos à direita
  • Subcomandos

E algum código de exemplo (também dessa página do 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)

4
Vieira vira o resto com as mãos para baixo em termos de recursos. Pena que a tendência SO usual de "primeira resposta vença" empurrou esta lista para baixo :(
samthebest

Concordo. Deixando um comentário aqui, basta que @Eugene Yokota não tome nota. Confira este blog vieiras
Pramit

1
O problema mencionado no scopt é "Parece bom, mas não é possível analisar as opções, que recebem uma lista de argumentos (ou seja, -a 1 2 3). E você não tem como estendê-lo para obter essas listas (exceto lib). " mas isso não é mais verdade, consulte github.com/scopt/scopt#options .
Alexey Romanov

2
isso é mais intuitivo e menos clichê do que o scopt. não mais (x, c) => c.copy(xyz = x) no scopt
WeiChing,

43

Eu gosto de deslizar sobre argumentos para configurações 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
}

2
Inteligente. Só funciona se todo argumento também especificar um valor, certo?
Brent Faust

2
Não deveria ser args.sliding(2, 2)?
m01

1
Não deveria ser var port = 0?
swdev

17

Interface de linha de comando Scala Toolkit (CLIST)

aqui é meu também! (um pouco tarde no jogo)

https://github.com/backuity/clist

Ao contrário scopt, é totalmente mutável ... mas espere! Isso nos dá uma sintaxe bastante agradável:

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

E uma maneira simples de executá-lo:

Cli.parse(args).withCommand(new Cat) { case cat =>
    println(cat.files)
}

Você pode fazer muito mais, é claro (multi-comandos, muitas opções de configuração, ...) e não tem dependência.

Terminarei com um tipo de recurso distinto, o uso padrão (muitas vezes negligenciado por vários comandos): clist


Possui validação?
KF

Sim (consulte github.com/backuity/clist/blob/master/demo/src/main/scala/… para obter um exemplo). Ainda não está documentado ... Relações Públicas? :)
Bruno Bieth 14/11

Tentei, bastante conveniente. Eu usei o scopt antes, ainda não estou acostumado a adicionar validações, mas não apenas na definição de cada parâmetro. Mas funciona bem comigo. E definir parâmetros e validações diferentes em características diferentes, combinando-os em casos diferentes, é realmente útil. Sofri muito quando não é conveniente reutilizar parâmetros. Obrigado pela resposta!
KF

A maioria das validações são realizadas durante a desserialização dos parâmetros de linha de comando (consulte Ler ), então se você pode definir suas restrições de validação através de tipos (ou seja Password, Hex...), então você pode aproveitar isso.
Bruno Bieth 14/11/19

13

Esse é em grande parte um clone vergonhoso da minha resposta à pergunta Java do mesmo tópico . Acontece que o JewelCLI é compatível com Scala, pois não requer métodos no estilo JavaBean para obter a nomeação automática de argumentos.

O JewelCLI é uma biblioteca Java compatível com Scala para análise de linha de comando que gera código limpo . Ele usa interfaces proxy configuradas com anotações para criar dinamicamente uma API de tipo seguro para seus parâmetros de linha de comando.

Um exemplo de interface de parâmetro Person.scala:

import uk.co.flamingpenguin.jewel.cli.Option

trait Person {
  @Option def name: String
  @Option def times: Int
}

Um exemplo de uso da interface de parâmetro 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)
    }
  }
}

Salve cópias dos arquivos acima em um único diretório e faça o download do JewelCLI 0.6 JAR nesse diretório também.

Compile e execute o exemplo no Bash no 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 e execute o exemplo no prompt de comando do Windows:

scalac -cp jewelcli-0.6.jar;. Person.scala Hello.scala
scala -cp jewelcli-0.6.jar;. Hello --name="John Doe" --times=3

A execução do exemplo deve produzir a seguinte saída:

Hello John Doe
Hello John Doe
Hello John Doe

Uma parte divertida disso que você pode notar é a (args: _ *). A chamada de métodos varargs Java do Scala exige isso. Esta é uma solução que aprendi com daily-scala.blogspot.com/2009/11/varargs.html no excelente blog Daily Scala de Jesse Eichar. Eu recomendo diário Scala :)
Alain O'Dea

12

Como analisar parâmetros sem uma dependência externa. Ótima pergunta! Você pode estar interessado em picocli .

O Picocli foi projetado especificamente para resolver o problema da pergunta: é uma estrutura de análise de linha de comando em um único arquivo, para que você possa incluí-lo na forma de origem . Isso permite que os usuários executem aplicativos baseados em picocli sem exigir o picocli como uma dependência externa .

Funciona anotando campos para que você escreva muito pouco código. Resumo rápido:

  • Tudo fortemente digitado - opções de linha de comando e parâmetros posicionais
  • Suporte para opções curtas em cluster do POSIX (para que ele também lide <command> -xvfInputFilecom <command> -x -v -f InputFile)
  • Um modelo de aridade que permite um número mínimo, máximo e variável de parâmetros, por exemplo "1..*","3..5"
  • API fluente e compacta para minimizar o código do cliente padrão
  • Subcomandos
  • Ajuda de uso com cores ANSI

A mensagem de ajuda de uso é fácil de personalizar com anotações (sem programação). Por exemplo:

Mensagem de ajuda de uso estendido( fonte )

Não resisti em adicionar mais uma captura de tela para mostrar que tipo de uso as mensagens de ajuda são possíveis. A ajuda de uso é o rosto do seu aplicativo, portanto, seja criativo e divirta-se!

demonstração picocli

Disclaimer: Eu criei picocli. Comentários ou perguntas muito bem-vindos. Está escrito em java, mas deixe-me saber se há algum problema usando-o no scala e tentarei solucioná-lo.


1
Por que o voto negativo? Esta é a única biblioteca que conheço que foi projetada especificamente para solucionar o problema mencionado no OP: como evitar a adição de uma dependência.
Remko Popma

"incentivar os autores da aplicação a incluí-lo". Bom trabalho.
KEOS

você tem exemplos de scala?
CruncherBigData

1
Comecei a criar exemplos para outros idiomas da JVM: github.com/remkop/picocli/issues/183 Comentários e contribuições são bem-vindos!
Remko Popma 12/09

11

Eu sou do mundo Java, gosto do args4j porque sua especificação simples é mais legível (graças às anotações) e produz uma saída bem formatada.

Aqui está o meu exemplo de trecho:

Especificação

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

}

Analisar

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

Em argumentos inválidos

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.


8

Há também o JCommander (aviso: eu o criei):

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

2
eu gosto deste. esses analisadores 'puro scala' falta uma sintaxe limpo
tactoth

@tactoth verificar este, tem uma sintaxe clara: stackoverflow.com/questions/2315912/...
de Bruno Bieth

6

Gostei da abordagem slide () do joslinm, não dos vars mutáveis;) Então, aqui está uma maneira imutável dessa abordagem:

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


3

Tentei generalizar a solução do @ pjotrp ao incluir uma lista dos símbolos de chave posicional necessários, um mapa da bandeira -> símbolo da chave e opções padrão:

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

Atualizei esse código para manipular sinalizadores (não apenas opções com valores) e também para definir a opção / sinalizador com formas curtas e longas. por exemplo -f|--flags. Dê uma olhada em gist.github.com/DavidGamba/b3287d40b019e498982c e fique à vontade para atualizar a resposta, se quiser. Provavelmente farei todos os mapas e opções para que você possa passar apenas o que precisará com argumentos nomeados.
DavidG

3

Baseei minha abordagem na resposta principal (de dave4420) e tentei aprimorá-la, tornando-a de uso geral.

Retorna um Map[String,String]dos parâmetros de linha de comando Você pode consultar os parâmetros específicos que deseja (por exemplo, usar .contains) ou converter os valores nos tipos que deseja (por exemplo, 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())
}

Exemplo:

val args=Array("--testing1","testing1","-a","-b","--c","d","test2")
argsToOptionMap( args  )

Dá:

res0: Map[String,String] = Map(testing1 -> testing1, a -> null, b -> null, c -> d, test2 -> null)


2

Aqui está um analisador de linha de comando scala que é fácil de usar. Ele formata automaticamente o texto de ajuda e converte os argumentos da opção no tipo desejado. Os switches curtos POSIX e GNU longo são suportados. Suporta comutadores com argumentos obrigatórios, argumentos opcionais e argumentos de múltiplos valores. Você pode até especificar a lista finita de valores aceitáveis ​​para uma opção específica. Nomes longos de comutadores podem ser abreviados na linha de comando por conveniência. Semelhante ao analisador de opções na biblioteca padrão do Ruby.


2

Eu nunca gostei de ruby ​​como analisadores de opções. A maioria dos desenvolvedores que os utilizou nunca escreve uma página de manual adequada para seus scripts e termina com opções longas de páginas não organizadas de maneira adequada por causa de seu analisador.

Eu sempre preferi o jeito de Perl fazer as coisas com o Getopt :: Long do Perl .

Estou trabalhando em uma implementação scala dele. A API inicial é mais ou menos assim:

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

Então, chamando scriptassim:

$ script hello -f --string=mystring -i 7 --float 3.14 --p --version world -- --nothing

Imprimiria:

higher order function
version is 0.2

E retorno:

remaining = Array("hello", "world", "--nothing")

options = Map('flag   -> true,
              'string -> "mystring",
              'int    -> 7,
              'double -> 3.14)

O projeto está hospedado no github scala-getoptions .


2

Acabei de criar minha enumeração simples

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

Entendo que a solução tem duas falhas principais que podem distraí-lo: elimina a liberdade (ou seja, a dependência de outras bibliotecas, que você valoriza muito) e a redundância (o princípio DRY, você digita o nome da opção apenas uma vez, como programa Scala variável e elimine-a pela segunda vez digitada como texto da linha de comando).


2

Eu sugiro usar http://docopt.org/ . Há uma porta scala-port, mas a implementação Java https://github.com/docopt/docopt.java funciona muito bem e parece ser melhor mantida. Aqui está um exemplo:

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

2

Isto é o que eu cozinhei. Retorna uma tupla de um mapa e uma lista. Lista é para entrada, como nomes de arquivo de entrada. Mapa é para opções / opções.

val args = "--sw1 1 input_1 --sw2 --sw3 2 input_2 --sw4".split(" ")
val (options, inputs) = OptParser.parse(args)

retornará

options: Map[Symbol,Any] = Map('sw1 -> 1, 'sw2 -> true, 'sw3 -> 2, 'sw4 -> true)
inputs: List[Symbol] = List('input_1, 'input_2)

Os comutadores podem ser "--t", que x será definido como true ou "--x 10", que x será definido como "10". Tudo o resto vai acabar na 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)
    }
  }
}

1

Eu gosto da aparência limpa desse código ... colhida em uma discussão aqui: 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)
  }

}

1

Como todo mundo postou sua própria solução aqui é minha, porque eu queria algo mais fácil de escrever para o usuário: https://gist.github.com/gwenzek/78355526e476e08bb34d

A essência contém um arquivo de código, além de um arquivo de teste e um pequeno exemplo copiado aqui:

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

Não há opções sofisticadas para forçar uma variável a estar em alguns limites, porque eu não acho que o analisador seja o melhor lugar para fazer isso.

Nota: você pode ter o alias desejado para uma determinada variável.


1

Eu vou empilhar. Eu resolvi isso com uma linha de código simples. Meus argumentos de linha de comando são assim:

input--hdfs:/path/to/myData/part-00199.avro output--hdfs:/path/toWrite/Data fileFormat--avro option1--5

Isso cria uma matriz através da funcionalidade de linha de comando nativa do Scala (do App ou de um método principal):

Array("input--hdfs:/path/to/myData/part-00199.avro", "output--hdfs:/path/toWrite/Data","fileFormat--avro","option1--5")

Em seguida, posso usar esta linha para analisar a matriz args padrão:

val nArgs = args.map(x=>x.split("--")).map(y=>(y(0),y(1))).toMap

O que cria um mapa com nomes associados aos valores da linha de comando:

Map(input -> hdfs:/path/to/myData/part-00199.avro, output -> hdfs:/path/toWrite/Data, fileFormat -> avro, option1 -> 5)

Posso acessar os valores dos parâmetros nomeados no meu código e a ordem em que eles aparecem na linha de comando não é mais relevante. Sei que isso é bastante simples e não possui toda a funcionalidade avançada mencionada acima, mas parece ser suficiente na maioria dos casos, precisa apenas de uma linha de código e não envolve dependências externas.


1

Aqui está o meu 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)

Ele elimina 3 argumentos obrigatórios e fornece as opções. Inteiros são especificados como a -Xmx<size>opção java notória , juntamente com o prefixo. Você pode analisar binários e números inteiros tão simples quanto

val cacheEnabled = optSpecified("cacheOff")
val memSize = optInt("-Xmx", 1000)

Não há necessidade de importar nada.


0

Uma linha rápida e suja do pobre homem para analisar os pares chave = valor:

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

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
}

Isso irá gerar o seguinte uso:

Uso

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.