zoukankan      html  css  js  c++  java
  • Scala scopt 命令行解析

    参考:https://github.com/scopt/scopt

    scopt is a little command line options parsing library.

    Sonatype

    libraryDependencies += "com.github.scopt" %% "scopt" % "X.Y.Z"

    See the Maven Central badge above.

    Usage

    scopt 4.x provides two styles of constructing a command line option parser: functional DSL and object-oriented DSL. Either case, first you need a case class that represents the configuration:

    import java.io.File
    case class Config(
        foo: Int = -1,
        out: File = new File("."),
        xyz: Boolean = false,
        libName: String = "",
        maxCount: Int = -1,
        verbose: Boolean = false,
        debug: Boolean = false,
        mode: String = "",
        files: Seq[File] = Seq(),
        keepalive: Boolean = false,
        jars: Seq[File] = Seq(),
        kwargs: Map[String, String] = Map())

    During the parsing process, a config object is passed around as an argument into action callbacks.

    Functional DSL

    Here's how you create a scopt.OParser[Config].

    import scopt.OParser
    val builder = OParser.builder[Config]
    val parser1 = {
      import builder._
      OParser.sequence(
        programName("scopt"),
        head("scopt", "4.x"),
        // option -f, --foo
        opt[Int]('f', "foo")
          .action((x, c) => c.copy(foo = x))
          .text("foo is an integer property"),
        // more options here...
      )
    }
    
    // OParser.parse returns Option[Config]
    OParser.parse(parser1, args, Config()) match {
      case Some(config) =>
        // do something
      case _ =>
        // arguments are bad, error message will have been displayed
    }

    See Scaladoc API and the rest of this page for the details on various builder methods.

    Full example

    import scopt.OParser
    val builder = OParser.builder[Config]
    val parser1 = {
      import builder._
      OParser.sequence(
        programName("scopt"),
        head("scopt", "4.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[Seq[File]]('j', "jars")
          .valueName("<jar1>,<jar2>...")
          .action((x, c) => c.copy(jars = x))
          .text("jars to include"),
        opt[Map[String, String]]("kwargs")
          .valueName("k1=v1,k2=v2...")
          .action((x, c) => c.copy(kwargs = x))
          .text("other arguments"),
        opt[Unit]("verbose")
          .action((_, c) => c.copy(verbose = true))
          .text("verbose is a flag"),
        opt[Unit]("debug")
          .hidden()
          .action((_, c) => c.copy(debug = true))
          .text("this option is hidden in the usage text"),
        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"),
        note("some notes." + sys.props("line.separator")),
        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"),
            opt[Unit]("debug-update")
              .hidden()
              .action((_, c) => c.copy(debug = true))
              .text("this option is hidden in the usage text"),
            checkConfig(
              c =>
                if (c.keepalive && c.xyz) failure("xyz cannot keep alive")
                else success)
          )
      )
    }
    
    // OParser.parse returns Option[Config]
    OParser.parse(parser1, args, Config()) match {
      case Some(config) =>
        // do something
      case _ =>
        // arguments are bad, error message will have been displayed
    }

    The above generates the following usage text:

    scopt 4.x
    Usage: scopt [update] [options] [<file>...]
    
      -f, --foo <value>        foo is an integer property
      -o, --out <file>         out is a required file property
      --max:<libname>=<max>    maximum count for <libname>
      -j, --jars <jar1>,<jar2>...
                               jars to include
      --kwargs k1=v1,k2=v2...  other arguments
      --verbose                verbose is a flag
      --help                   prints this usage text
      <file>...                optional unbounded args
    some notes.
    
    Command: update [options]
    update is a command.
      -nk, --not-keepalive     disable keepalive
      --xyz <value>            xyz is a boolean property
    

    Options

    Command line options are defined using opt[A]('f', "foo") or opt[A]("foo") where A is any type that is an instance of Read typeclass.

    • Unit works as a plain flag --foo or -f
    • IntLongDoubleStringBigIntBigDecimaljava.io.Filejava.net.URI, and java.net.InetAddressaccept a value like --foo 80 or --foo:80
    • Boolean accepts a value like --foo true or --foo:1
    • java.util.Calendar accepts a value like --foo 2000-12-01
    • scala.concurrent.duration.Duration accepts a value like --foo 30s
    • A pair of types like (String, Int) accept a key-value like --foo:k=1 or -f k=1
    • Seq[File] accepts a string containing comma-separated values such as --jars foo.jar,bar.jar
    • Map[String, String] accepts a string containing comma-separated pairs like --kwargs key1=val1,key2=val2

    This could be extended by defining Read instances in the scope. For example,

    object WeekDays extends Enumeration {
      type WeekDays = Value
      val Mon, Tue, Wed, Thur, Fri, Sat, Sun = Value
    }
    implicit val weekDaysRead: scopt.Read[WeekDays.Value] =
      scopt.Read.reads(WeekDays withName _)

    By default these options are optional.

    Short options

    For plain flags (opt[Unit]) short options can be grouped as -fb to mean --foo --bar.

    opt accepts only a single character, but using abbr("ab") a string can be used too:

    opt[Unit]("no-keepalive").abbr("nk").action( (x, c) => c.copy(keepalive = false) )

    Help, Version, and Notes

    There are special options with predefined action called help("help") and version("version"), which prints usage text and header text respectively. When help("help") is defined, parser will print out short error message when it fails instead of printing the entire usage text.

    note("...") is used add given string to the usage text.

    Arguments

    Command line arguments are defined using arg[A]("<file>"). It works similar to options, but instead it accepts values without -- or -. By default, arguments accept a single value and are required.

    arg[String]("<file>...")

    Occurrence

    Each opt/arg carries occurrence information minOccurs and maxOccursminOccurs specify at least how many times an opt/arg must appear, and maxOccurs specify at most how many times an opt/arg may appear.

    Occurrence can be set using the methods on the opt/arg:

    opt[String]('o', "out").required()
    opt[String]('o', "out").required().withFallback(() => "default value")
    opt[String]('o', "out").minOccurs(1) // same as above
    arg[String]("<mode>").optional()
    arg[String]("<mode>").minOccurs(0) // same as above
    arg[String]("<file>...").optional().unbounded()
    arg[String]("<file>...").minOccurs(0).maxOccurs(1024) // same as above

    Visibility

    Each opt/arg can be hidden from the usage text using hidden() method:

    opt[Unit]("debug")
      .hidden()
      .action( (_, c) => c.copy(debug = true) )
      .text("this option is hidden in the usage text")

    Validation

    Each opt/arg can carry multiple validation functions.

    opt[Int]('f', "foo")
      .action( (x, c) => c.copy(intValue = x) )
      .validate( x =>
        if (x > 0) success
        else failure("Option --foo must be >0") )
      .validate( x => failure("Just because") )

    The first function validates if the values are positive, and the second function always fails.

    Check configuration

    Consistency among the option values can be checked using checkConfig.

    checkConfig( c =>
      if (c.keepalive && c.xyz) failure("xyz cannot keep alive")
      else success )

    These are called at the end of parsing.

    Commands

    Commands may be defined using cmd("update"). Commands could be used to express git branch kind of argument, whose name means something. Using children method, a command may define child opts/args that get inserted in the presence of the command. To distinguish commands from arguments, they must appear in the first position within the level. It is generally recommended to avoid mixing args both in parent level and commands to avoid confusion.

    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"),
        checkConfig( c =>
          if (c.keepalive && c.xyz) failure("xyz cannot keep alive")
          else success )
      )

    In the above, update test.txt would trigger the update command, but test.txt update won't.

    Commands could be nested into another command as follows:

    cmd("backend")
      .text("commands to manipulate backends:
    ")
      .action( (x, c) => c.copy(flag = true) )
      .children(
        cmd("update").children(
          arg[String]("<a>").action( (x, c) => c.copy(a = x) )
        )
      )

    Object-oriented DSL, immutable parsing

    Here's the object-oriented DSL that's mostly source-compatible with scopt 3.x.

    Create a parser by extending scopt.OptionParser[Config]. See Scaladoc API for the details on various builder methods.

    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")
    }
    
    // parser.parse returns Option[C]
    parser.parse(args, Config()) match {
      case Some(config) =>
        // do stuff
    
      case None =>
        // arguments are bad, error message will have been displayed
    }

    Object-oriented DSL, mutable parsing

    Create a scopt.OptionParser[Unit] and customize it with the options you need, passing in functions to process each option or argument. Use foreach instead of action.

    val parser = new scopt.OptionParser[Unit]("scopt") {
      head("scopt", "3.x")
    
      opt[Int]('f', "foo")
        .foreach( x => c = c.copy(foo = x) )
        .text("foo is an integer property")
    
      opt[File]('o', "out")
        .required()
        .valueName("<file>")
        .foreach( x => c = c.copy(out = x) )
        .text("out is a required file property")
    }
    if (parser.parse(args)) {
      // do stuff
    }
    else {
      // arguments are bad, usage message will have been displayed
    }

    Advanced: showUsageOnError

    When help("help") is defined, parser will print out short error message when it fails instead of printing the entire usage text.

    This behavior could be changed by overriding showUsageOnError as follows:

    import scopt.{ OParserSetup, DefaultOParserSetup }
    val setup: OParserSetup = new DefaultOParserSetup {
      override def showUsageOnError = Some(true)
    }
    val result = OParser.parse(parser1, args, Config(), setup)

    Advanced: Termination handling

    By default, when the --help or --version are invoked, they call sys.exit(0) after printing the help or version information. If this is not desired (e.g. testing purposes), you can override the terminate(exitState: Either[String, Unit]) method:

    import scopt.{ OParserSetup, DefaultOParserSetup }
    val setup: OParserSetup = new DefaultOParserSetup {
      // Overriding the termination handler to no-op.
      override def terminate(exitState: Either[String, Unit]): Unit = ()
    }
    val result = OParser.parse(parser1, args, Config(), setup)

    Advanced: Captured output

    By default, scopt emits output when needed to stderr and stdout. This is expected behavior when using scopt to process arguments for your stand-alone application. However, if your application requires parsing arguments while not producing output directly, you may wish to capture stderr and stdout output rather than emit them directly. Redirecting Console in Scala can accomplish this in a thread-safe way, within a scope of your chosing, like this:

    val outCapture = new ByteArrayOutputStream
    val errCapture = new ByteArrayOutputStream
    
    Console.withOut(outCapture) {
      Console.withErr(errCapture) {
        val result = OParser.parse(parser1, args, Config())
      }
    }
    // Now stderr output from this block is in errCapture.toString, and stdout in outCapture.toString

    Advanced: Rendering mode

    scopt 3.5.0 introduced rendering mode, and adopted two-column rendeing of the usage text by default. To switch back to the older one-column rendering override the renderingMode method:

    import scopt.{ OParserSetup, DefaultOParserSetup }
    val setup: OParserSetup = new DefaultOParserSetup {
      override def renderingMode = scopt.RenderingMode.OneColumn
    }
    val result = OParser.parse(parser1, args, Config(), setup)
  • 相关阅读:
    实验0 了解和熟悉操作系统
    软件工程感想
    递归下降分析法--算数语法分析
    有限自动机的构造与识别
    评论
    文法解释
    【编译CEF3】2017-07 添加支持mp3 mp4的编译日记
    Visual Studio 2015/2013安装失败:Microsoft Visual Studio 2015 Shell (Minimum) Interop Assemblies 安装时发生严重错误
    [RAD Studio 10.2 Tokyo] Error:java.lang.UnsupportedClassVersionError: com/android/dx/command/Main : Unsupported major错误解决
    Delphi 编译出来的程序被小红伞报病毒 TR/Spy.Banker.Gen4 [trojan]
  • 原文地址:https://www.cnblogs.com/144823836yj/p/13711419.html
Copyright © 2011-2022 走看看