Najlepszy sposób na analizę parametrów wiersza poleceń? [Zamknięte]

237

Jaki jest najlepszy sposób na analizę parametrów wiersza polecenia w Scali? Osobiście wolę coś lekkiego, który nie wymaga zewnętrznego słoika.

Związane z:

Eugene Yokota
źródło

Odpowiedzi:

228

W większości przypadków nie potrzebujesz zewnętrznego analizatora składni. Dopasowywanie wzorów Scali pozwala na spożywanie argumentów w funkcjonalnym stylu. Na przykład:

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

wydrukuje na przykład:

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

Ta wersja zajmuje tylko jeden plik infile. Łatwe do ulepszenia (przy użyciu listy).

Zauważ też, że takie podejście pozwala na połączenie wielu argumentów wiersza poleceń - nawet więcej niż dwóch!

pjotrp
źródło
4
isSwitch po prostu sprawdza, czy pierwsza postać jest myślnikiem „-”
pjotrp
6
nextOptionnie jest dobrą nazwą dla tej funkcji. Jest to funkcja, która zwraca mapę - fakt, że jest ona rekurencyjna, jest szczegółem implementacji. To tak, jakby napisać maxfunkcję dla kolekcji i nazwać ją nextMaxpo prostu dlatego, że napisałeś ją z jawną rekurencją. Dlaczego nie po prostu to nazwać optionMap?
itsbruce
4
@itsbruce Chcę tylko dodać / zmodyfikować twój punkt - byłoby to najbardziej „właściwe” z czytelności / łatwości konserwacji, aby zdefiniować listToOptionMap(lst:List[String])za pomocą nextOptionzdefiniowanej w tym celu funkcji , z końcowym wierszem return nextOption(Map(), lst). To powiedziawszy, muszę wyznać, że w swoim czasie poczyniłem o wiele bardziej skandaliczne skróty niż w tej odpowiedzi.
tresbot,
6
@MadKing w powyższym kodzie exit(1)może wymagaćsys.exit(1)
tresbot
3
Podoba mi się twoje rozwiązanie. Oto modyfikacja do obsługi wielu fileparametrów: 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). Mapa potrzebuje również wartości domyślnej Nil, tj val options = nextOption(Map() withDefaultValue Nil, args.toList). Nie podoba mi się to, że muszę się uciekać asInstanceOf, ponieważ OptionMapwartości są typowe Any. Czy jest lepsze rozwiązanie?
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
}

Powyższe generuje następujący tekst użycia:

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

Tego właśnie używam. Czyste użytkowanie bez nadmiernego bagażu. (Oświadczenie: Teraz utrzymuję ten projekt)

Eugene Yokota
źródło
6
Znacznie bardziej podoba mi się wzorzec konstruktora DSL, ponieważ umożliwia on delegowanie konstrukcji parametrów do modułów.
Daniel C. Sobral
3
Uwaga: inaczej niż pokazano, scopt nie potrzebuje tylu adnotacji typu.
Blaisorblade
9
Jeśli używasz tego do analizowania argumentów w celu uzyskania iskry, pamiętaj, że nie grają razem dobrze. Dosłownie nic, czego próbowałem, nie mogło uzyskać iskry-poddania się pracy z scopt :-(
jbrown
4
@BirdJaguarIV Jeśli iskra używa scopt, prawdopodobnie był to problem - sprzeczne wersje w słoiku lub coś takiego. Zamiast tego używam przegrzebków z iskrami i nie miałem żadnych problemów.
jbrown
12
Jak na ironię, choć ta biblioteka automatycznie generuje dobrą dokumentację CLI, kod wygląda trochę lepiej niż brainf * ck.
Jonathan Neufeld,
58

Zdaję sobie sprawę, że pytanie zostało zadane jakiś czas temu, ale pomyślałem, że może to pomóc niektórym osobom, które googlują wokół (jak ja), i wejść na tę stronę.

Przegrzebek wygląda również dość obiecująco.

Funkcje (cytat z połączonej strony github):

  • opcje flagi, pojedynczej wartości i wielu wartości
  • Krótkie nazwy opcji w stylu POSIX (-a) z grupowaniem (-abc)
  • Długie nazwy opcji w stylu GNU (--opt)
  • Argumenty właściwości (-Dkey = wartość, -D klucz1 = wartość klucz2 = wartość)
  • Niesznurkowe typy wartości opcji i właściwości (z konwerterami rozszerzalnymi)
  • Mocne dopasowanie na końcowych argumentach
  • Komendy

I przykładowy kod (również z tej strony 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
źródło
4
Przegrzebek rozdaje resztę pod względem funkcji. Wstyd zwykły SO trend „wygrywa pierwsza odpowiedź” zepchnął to z listy :(
samthebest
Zgadzam się. Zostawiając komentarz tutaj, po prostu wstaw @Eugene Yokota spóźniła się, aby zanotować. Sprawdź tego bloga przegrzebek
Pramit
1
Problem, o którym wspomina z scopt, brzmi: „Wygląda dobrze, ale nie jest w stanie przeanalizować opcji, które pobierają listę argumentów (tj. -A 1 2 3). I nie ma możliwości, aby ją rozszerzyć, aby uzyskać te listy (z wyjątkiem rozwidlenia lib). ” ale to już nie jest prawda, patrz github.com/scopt/scopt#options .
Aleksiej Romanow
2
jest to bardziej intuicyjne i mniej bojowe niż scopt. nie więcej (x, c) => c.copy(xyz = x) w scopt
WeiChing 清 煒 清
43

Lubię przesuwać argumenty w przypadku stosunkowo prostych konfiguracji.

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
źródło
2
Sprytny. Działa tylko wtedy, gdy każdy argument również podaje wartość, prawda?
Brent Faust
2
Nie powinno tak być args.sliding(2, 2)?
m01
1
Nie powinno tak być var port = 0?
swdev
17

Interfejs wiersza polecenia Scala Toolkit (CLIST)

tu też jest moje! (nieco później w grze)

https://github.com/backuity/clist

W przeciwieństwie do scopttego jest całkowicie zmienny ... ale czekaj! To daje nam całkiem niezłą składnię:

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

I prosty sposób na uruchomienie:

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

Możesz oczywiście zrobić znacznie więcej (wiele poleceń, wiele opcji konfiguracji, ...) i nie ma zależności.

Zakończę pewnego rodzaju charakterystyczną funkcją, domyślnym użyciem (dość często zaniedbywanym w przypadku wielu poleceń): klist

Bruno Bieth
źródło
Czy to ma walidację?
KF
Tak, tak (patrz przykład github.com/backuity/clist/blob/master/demo/src/main/scala/ ...) Nie jest to jednak udokumentowane ... PR? :)
Bruno Bieth,
Próbowałem, całkiem wygodne. Użyłem scopt wcześniej, wciąż nie przyzwyczajam się do dodawania walidacji razem, ale nie tylko w definicji każdego parametru. Ale to działa dobrze ze mną. Naprawdę pomocne jest zdefiniowanie różnych parametrów i walidacji w różnych cechach, a następnie połączenie ich w różnych przypadkach. Bardzo cierpiałem w scopt, gdy ponowne użycie parametrów nie jest wygodne. Dzięki za odpowiedź!
KF
Większość walidacje wykonywane są podczas deserializacji parametrów wiersza poleceń (patrz Read ), więc jeśli można zdefiniować ograniczenia walidacji poprzez typów (tj Password, Hex...), a następnie można wykorzystać to.
Bruno Bieth,
13

Jest to w dużej mierze bezwstydny klon mojej odpowiedzi na pytanie Java na ten sam temat . Okazuje się, że JewelCLI jest przyjazny dla Scali, ponieważ nie wymaga metod JavaBean do automatycznego nazywania argumentów.

JewelCLI to przyjazna dla Scali biblioteka Java do analizowania wiersza poleceń, która daje czysty kod . Wykorzystuje interfejsy proxy skonfigurowane z adnotacjami, aby dynamicznie budować bezpieczny interfejs API typu dla parametrów wiersza poleceń.

Przykładowy interfejs parametrów Person.scala:

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

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

Przykładowe użycie interfejsu parametrów 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)
    }
  }
}

Zapisz kopie powyższych plików w jednym katalogu i pobierz również JAR JewelCLI 0.6 .

Skompiluj i uruchom przykład w Bash na 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

Skompiluj i uruchom przykład w wierszu polecenia systemu Windows:

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

Uruchomienie przykładu powinno dać następujące wyniki:

Hello John Doe
Hello John Doe
Hello John Doe
Alain O'Dea
źródło
Jedną z zabawnych rzeczy, którą możesz zauważyć, jest (args: _ *). Wywoływanie metod Java varargs ze Scali wymaga tego. Jest to rozwiązanie, którego nauczyłem się od daily-scala.blogspot.com/2009/11/varargs.html na doskonałym blogu Jesse Eichar Daily Scala. Bardzo polecam Daily Scala :)
Alain O'Dea
12

Jak parsować parametry bez zewnętrznej zależności. Świetne pytanie! Możesz być zainteresowany Picocli .

Picocli jest specjalnie zaprojektowany, aby rozwiązać problem zadany w pytaniu: jest strukturą parsowania wiersza poleceń w jednym pliku, więc możesz dołączyć go w formie źródłowej . Dzięki temu użytkownicy mogą uruchamiać aplikacje oparte na Picocli, nie wymagając Picocli jako zewnętrznej zależności .

Działa poprzez adnotacje pól, dzięki czemu piszesz bardzo mało kodu. Szybkie podsumowanie:

  • Mocno wpisane wszystko - opcje wiersza poleceń, a także parametry pozycyjne
  • Obsługa krótkich opcji klastrowych POSIX (więc obsługuje, <command> -xvfInputFilea także <command> -x -v -f InputFile)
  • Model arity który umożliwia minimalną, maksymalną i zmiennej liczby parametrów, na przykład "1..*","3..5"
  • Płynny i kompaktowy interfejs API w celu zminimalizowania kodu klocka klienta
  • Komendy
  • Pomoc w użyciu z kolorami ANSI

Komunikat pomocy dotyczącej użytkowania można łatwo dostosować za pomocą adnotacji (bez programowania). Na przykład:

Komunikat pomocy dotyczącej rozszerzonego użytkowania( źródło )

Nie mogłem się powstrzymać przed dodaniem jeszcze jednego zrzutu ekranu, aby pokazać, jakie komunikaty pomocy dotyczące użytkowania są możliwe. Pomoc dotycząca użytkowania jest obliczem Twojej aplikacji, więc bądź kreatywny i baw się dobrze!

demo picocli

Uwaga: Stworzyłem picocli. Informacje zwrotne lub pytania bardzo mile widziane. Jest napisany w Javie, ale daj mi znać, jeśli jest jakiś problem z użyciem go w scali, a ja postaram się rozwiązać.

Remko Popma
źródło
1
Dlaczego głosowanie negatywne? Jest to jedyna znana mi biblioteka, która została zaprojektowana specjalnie w celu rozwiązania problemu wspomnianego w PO: jak uniknąć dodania zależności.
Remko Popma
„zachęcaj autorów aplikacji do włączenia go”. Dobra robota.
keos
czy masz przykłady Scala?
CruncherBigData,
1
Zacząłem tworzyć przykłady dla innych języków JVM: github.com/remkop/picocli/issues/183 Witamy i komentarze mile widziane!
Remko Popma,
11

Jestem ze świata Java, lubię args4j, ponieważ jego prosta specyfikacja jest bardziej czytelna (dzięki adnotacjom) i zapewnia ładnie sformatowane dane wyjściowe.

Oto mój przykładowy fragment:

Specyfikacja

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

}

Analizować

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

W przypadku nieprawidłowych argumentów

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
źródło
10

Scala-Optparse-Applative

Myślę, że scala-optparse-application jest najbardziej funkcjonalną biblioteką analizatora składni wiersza poleceń w Scali.

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

Kenji Yoshida
źródło
czy oprócz tego, co jest w README, ma jakieś przykłady / dokumenty?
Erik Kaplun
1
Tak, sprawdź exampleskod testowy
gpampara,
8

Jest też JCommander (oświadczenie: ja go stworzyłem):

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)
    }
  }
}
Pył Cedrowy
źródło
2
podoba mi się ten tym
parserom
@tactoth sprawdź ten, ma jasną składnię: stackoverflow.com/questions/2315912/…
Bruno Bieth 12.01.16
6

Podobało mi się podejście Slide () joslinm, a nie zmienne zmienne;) Oto niezmienny sposób na to podejście:

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
  }
}
targowany
źródło
3

Podjąłem próbę uogólnienia rozwiązania @ pjotrp, biorąc listę wymaganych symboli kluczy pozycyjnych, mapę flagi -> symbol klucza i domyślne opcje:

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
źródło
Zaktualizowałem ten fragment kodu, aby obsługiwał flagi (nie tylko opcje z wartościami), a także aby definiować opcję / flagę za pomocą krótkich i długich formularzy. np -f|--flags. Spójrz na gist.github.com/DavidGamba/b3287d40b019e498982c i prosimy o aktualizację odpowiedzi, jeśli Ci się spodoba. Prawdopodobnie wykonam każdą mapę i opcję, abyś mógł przekazać tylko to, czego potrzebujesz, z nazwanymi argumentami.
DavidG
3

Oparłem swoje podejście na najlepszej odpowiedzi (z dave4420) i próbowałem je poprawić, czyniąc je bardziej ogólnym.

Zwraca jeden Map[String,String]ze wszystkich parametrów wiersza poleceń. Możesz zapytać o określone parametry, które chcesz (np. Używając .contains) lub przekonwertować wartości na żądane typy (np. Używając 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())
}

Przykład:

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

Daje:

res0: Map[String,String] = Map(testing1 -> testing1, a -> null, b -> null, c -> d, test2 -> null)
bjorno
źródło
2

Oto prosty w użyciu parser linii poleceń scala . Automatycznie formatuje tekst pomocy i konwertuje argumenty przełącznika na pożądany typ. Obsługiwane są zarówno krótkie przełączniki POSIX, jak i długie przełączniki GNU. Obsługuje przełączniki z wymaganymi argumentami, argumentami opcjonalnymi i argumentami o wielu wartościach. Możesz nawet określić skończoną listę dopuszczalnych wartości dla konkretnego przełącznika. Długie nazwy przełączników można dla wygody skracać w wierszu poleceń. Podobne do parsera opcji w standardowej bibliotece Ruby.

sprzedawca
źródło
2

Nigdy nie lubiłem parserów opcji podobnych do ruby. Większość programistów, którzy ich używali, nigdy nie pisze odpowiedniej strony podręcznika użytkownika dla swoich skryptów i kończy się na tym, że opcje stron nie są odpowiednio zorganizowane z powodu ich parsera.

Zawsze preferowałem sposób, w jaki Perl robi rzeczy z Peropt 's Getopt :: Long .

Pracuję nad jego wdrożeniem scala. Wczesne API wygląda mniej więcej tak:

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

Więc dzwonienie w scriptten sposób:

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

Wydrukowałby:

higher order function
version is 0.2

I wróć:

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

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

Projekt jest hostowany w github scala-getoptions .

DavidG
źródło
2

Właśnie stworzyłem moje proste wyliczenie

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

Rozumiem, że rozwiązanie ma dwie główne wady, które mogą cię rozpraszać: eliminuje swobodę (tj. Zależność od innych bibliotek, które tak bardzo cenisz) i redundancję (zasada DRY, wpisujesz nazwę opcji tylko raz, jako program Scala zmienną i wyeliminuj ją po raz drugi wpisany jako tekst wiersza poleceń).

Val
źródło
2

Sugerowałbym użyć http://docopt.org/ . Istnieje port Scala, ale implementacja Java https://github.com/docopt/docopt.java działa dobrze i wydaje się być lepiej utrzymana. Oto przykład:

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
źródło
2

Właśnie to ugotowałem. Zwraca krotkę mapy i listy. Lista służy do wprowadzania, podobnie jak nazwy plików wejściowych. Mapa dotyczy przełączników / opcji.

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

wróci

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

Przełącznikami może być „--t”, dla którego x zostanie ustawione na true, lub „--x 10”, dla którego x zostanie ustawione na „10”. Cała reszta znajdzie się na liście.

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
źródło
1

Lubię czysty wygląd tego kodu ... zaczerpniętego z dyskusji tutaj: 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
źródło
1

Jak wszyscy pisali, tutaj jest moje własne rozwiązanie, ponieważ chciałem napisać coś łatwiejszego dla użytkownika: https://gist.github.com/gwenzek/78355526e476e08bb34d

Treść zawiera plik kodu oraz plik testowy i krótki przykład skopiowany tutaj:

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

Nie ma wymyślnych opcji, aby zmusić zmienną do pewnego ograniczenia, ponieważ nie uważam, że parser jest najlepszym miejscem do tego.

Uwaga: możesz mieć tyle aliasów, ile chcesz dla danej zmiennej.

gwenzek
źródło
1

Idę na stos. Rozwiązałem to za pomocą prostej linii kodu. Moje argumenty wiersza poleceń wyglądają tak:

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

To tworzy tablicę za pomocą natywnej funkcjonalności wiersza poleceń Scali (z aplikacji lub głównej metody):

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

Następnie mogę użyć tego wiersza do przeanalizowania domyślnej tablicy args:

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

Który tworzy mapę z nazwami powiązanymi z wartościami wiersza poleceń:

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

Następnie mogę uzyskać dostęp do wartości nazwanych parametrów w moim kodzie, a kolejność, w jakiej pojawiają się w wierszu poleceń, nie jest już istotna. Zdaję sobie sprawę, że jest to dość proste i nie ma wszystkich zaawansowanych funkcji wymienionych powyżej, ale wydaje się wystarczające w większości przypadków, potrzebuje tylko jednego wiersza kodu i nie wymaga zewnętrznych zależności.

J Calbreath
źródło
1

Oto mój 1-liniowy

    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)

Porzuca 3 obowiązkowe argumenty i podaje opcje. Liczby całkowite są określone podobnie jak notowana -Xmx<size>opcja java, łącznie z prefiksem. Możesz analizować pliki binarne i liczby całkowite tak proste jak

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

Nie musisz niczego importować.

Valentin Tihomirov
źródło
0

Szybka i brudna jedna linijka biednego człowieka do analizy klucza = pary wartości:

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
źródło
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
}

Spowoduje to wygenerowanie następującego użycia:

Stosowanie

pavlosgi
źródło