All Downloads are FREE. Search and download functionalities are using the official Maven repository.

replpp.shaded.scopt.OParser.scala Maven / Gradle / Ivy

The newest version!
package replpp.shaded.scopt

import scala.collection.{ Seq => CSeq }

abstract class OParserBuilder[C] {
  def programName(x: String): OParser[Unit, C] =
    wrap(makeDef[Unit](OptionDefKind.ProgramName, "")).text(x)

  /** adds usage text. */
  def head(xs: String*): OParser[Unit, C] =
    wrap(makeDef[Unit](OptionDefKind.Head, "")).text(xs.mkString(" "))

  /** adds an option invoked by `--name x`.
   * @param name name of the option
   */
  def opt[A: Read](name: String): OParser[A, C] =
    wrap(makeDef[A](OptionDefKind.Opt, name))

  /** adds an option invoked by `-x value` or `--name value`.
   * @param x name of the short option
   * @param name name of the option
   */
  def opt[A: Read](x: Char, name: String): OParser[A, C] =
    opt[A](name).abbr(x.toString)

  /** adds usage text. */
  def note(x: String): OParser[Unit, C] =
    wrap(makeDef[Unit](OptionDefKind.Note, "").text(x))

  /** adds an argument invoked by an option without `-` or `--`.
   * @param name name in the usage text
   */
  def arg[A: Read](name: String): OParser[A, C] =
    wrap(makeDef(OptionDefKind.Arg, name)).required()

  /** adds a command invoked by an option without `-` or `--`.
   * @param name name of the command
   */
  def cmd(name: String): OParser[Unit, C] =
    wrap(makeDef[Unit](OptionDefKind.Cmd, name))

  /** adds final check. */
  def checkConfig(f: C => Either[String, Unit]): OParser[Unit, C] =
    wrap(makeDef[Unit](OptionDefKind.Check, "").validateConfig(f))

  /** adds an option invoked by `--name` that displays header text and exits.
   * @param name name of the option
   */
  def version(name: String): OParser[Unit, C] =
    wrap(makeDef[Unit](OptionDefKind.OptVersion, name))

  /** adds an option invoked by `-x` or `--name` that displays header text and exits.
   * @param x name of the short option
   * @param name name of the option
   */
  def version(x: Char, name: String): OParser[Unit, C] =
    version(name).abbr(x.toString)

  /** adds an option invoked by `--name` that displays usage text and exits.
   * @param name name of the option
   */
  def help(name: String): OParser[Unit, C] =
    wrap(makeDef[Unit](OptionDefKind.OptHelp, name))

  /** adds an option invoked by `-x` or `--name` that displays usage text and exits.
   * @param x name of the short option
   * @param name name of the option
   */
  def help(x: Char, name: String): OParser[Unit, C] =
    help(name).abbr(x.toString)

  /** call this to express success in custom validation. */
  def success: Either[String, Unit] = OptionDef.makeSuccess[String]

  /** call this to express failure in custom validation. */
  def failure(msg: String): Either[String, Unit] = Left(msg)

  protected def wrap[A](d: OptionDef[A, C]): OParser[A, C] =
    OParser(d, Nil)

  protected def makeDef[A: Read](kind: OptionDefKind, name: String): OptionDef[A, C] =
    new OptionDef[A, C](kind, name)
}

/**
 * A monadic commandline options parser.
 * {{{
 * 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("")
 *       .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  must be >0"))
 *       .keyValueName("", "")
 *       .text("maximum count for "),
 *     opt[Seq[File]]('j', "jars")
 *       .valueName(",...")
 *       .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]("...")
 *       .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
 * }
 *
 * // alternatively, use OParser.runParser returns (Option[Config], List[OEffect])
 * OParser.runParser(parser1, args, Config()) match {
 *   case (result, effects) =>
 *     OParser.runEffects(effects, new DefaultOEffectSetup {
 *       // override def displayToOut(msg: String): Unit = Console.out.println(msg)
 *       // override def displayToErr(msg: String): Unit = Console.err.println(msg)
 *       // override def reportError(msg: String): Unit = displayToErr("Error: " + msg)
 *       // override def reportWarning(msg: String): Unit = displayToErr("Warning: " + msg)
 *
 *       // ignore terminate
 *       override def terminate(exitState: Either[String, Unit]): Unit = ()
 *     })
 *
 *     result match {
 *       Some(config) =>
 *         // do something
 *       case _ =>
 *         // arguments are bad, error message will have been displayed
 *     }
 * }
 *}}}
 */
class OParser[A, C](head: OptionDef[A, C], rest: List[OptionDef[_, C]]) {

  /** Adds description in the usage text. */
  def text(x: String): OParser[A, C] = subHead[A](head.text(x))

  /** Adds short option -x. */
  def abbr(x: String): OParser[A, C] = subHead[A](head.abbr(x))

  /** Adds a callback function. */
  def action(f: (A, C) => C): OParser[A, C] = subHead[A](head.action(f))

  /** Requires the option to appear at least `n` times. */
  def minOccurs(n: Int): OParser[A, C] = subHead[A](head.minOccurs(n))

  /** Allows the argument to appear at most `n` times. */
  def maxOccurs(n: Int): OParser[A, C] = subHead[A](head.maxOccurs(n))

  /** Requires the option to appear at least once. */
  def required(): OParser[A, C] = minOccurs(1)

  /** Changes the option to be optional. */
  def optional(): OParser[A, C] = minOccurs(0)

  /** Allows the argument to appear multiple times. */
  def unbounded(): OParser[A, C] = maxOccurs(OptionDef.UNBOUNDED)

  /** Hides the option in any usage text. */
  def hidden(): OParser[A, C] = subHead[A](head.hidden())

  /** Adds value name used in the usage text. */
  def valueName(x: String): OParser[A, C] =
    subHead[A](head.valueName(x))

  /** Adds key name used in the usage text. */
  def keyName(x: String): OParser[A, C] =
    subHead[A](head.keyName(x))

  /** Adds key and value names used in the usage text. */
  def keyValueName(k: String, v: String): OParser[A, C] =
    subHead[A](head.keyValueName(k, v))

  /** Adds a parser under this command. */
  def children(cs: OParser[_, C]*): OParser[A, C] = {
    val options = cs.toList.flatMap(_.toList)
    val (withParent, withoutParent) = options.partition(_.hasParent)
    val updatedChildList = withParent ::: withoutParent.map(_.parent(head))
    OParser(head, rest ::: updatedChildList)
  }

  /** Adds custom validation. */
  def validate(f: A => Either[String, Unit]): OParser[A, C] =
    subHead[A](head.validate(f))

  /** provides a default to fallback to, e.g. for System.getenv */
  def withFallback(to: () => A): OParser[A, C] =
    subHead[A](head.withFallback(to))

  def toList: List[OptionDef[_, C]] = head :: rest
  def ++(other: OParser[_, C]): OParser[A, C] =
    OParser(head, rest ::: other.toList)

  def foreach(f: Unit => Unit): Unit = f(())

  def map(f: Unit => Unit): OParser[A, C] = this

  def flatMap(f: Unit => OParser[_, C]): OParser[A, C] =
    OParser(head, rest ::: f(()).toList)

  protected def subHead[B](head: OptionDef[B, C]): OParser[B, C] =
    OParser(head, rest)
}

object OParser {
  def apply[A, C](head: OptionDef[A, C], rest: List[OptionDef[_, C]]): OParser[A, C] =
    new OParser(head, rest)

  def builder[C]: OParserBuilder[C] = new OParserBuilder[C] {}

  def usage[C](parser: OParser[_, C]): String = usage(parser, RenderingMode.TwoColumns)

  def usage[C](parser: OParser[_, C], mode: RenderingMode): String = {
    val (h, u) = ORunner.renderUsage[C](mode, parser.toList)
    u
  }

  def sequence[A, C](parser: OParser[A, C], parsers: OParser[_, C]*): OParser[A, C] =
    if (parsers.isEmpty) parser
    else
      parser flatMap { p =>
        val ps = parsers.toList
        sequence(ps.head, ps.tail: _*)
      }

  private[this] lazy val psetup0 = new DefaultOParserSetup with OParserSetup {
    def showUsageAsError(): Unit = ()
    def showTryHelp(): Unit = ()
  }
  private[this] lazy val esetup0 = new DefaultOEffectSetup with OEffectSetup {
    def showUsageAsError(): Unit = ()
    def showTryHelp(): Unit = ()
  }

  /** Run the parser, and run the effects.
   */
  def parse[C](parser: OParser[_, C], args: CSeq[String], init: C): Option[C] =
    ORunner.runParser(args, init, parser.toList, psetup0) match {
      case (r, es) => ORunner.runEffects(es, esetup0); r
    }

  /** Run the parser, and run the effects.
   */
  def parse[C](
      parser: OParser[_, C],
      args: CSeq[String],
      init: C,
      psetup: OParserSetup,
      esetup: OEffectSetup
  ): Option[C] =
    ORunner.runParser(args, init, parser.toList, psetup) match {
      case (r, es) => ORunner.runEffects(es, esetup); r
    }

  /** Run the parser, and run the effects.
   */
  def parse[C](
      parser: OParser[_, C],
      args: CSeq[String],
      init: C,
      psetup: OParserSetup
  ): Option[C] =
    ORunner.runParser(args, init, parser.toList, psetup) match {
      case (r, es) => ORunner.runEffects(es, esetup0); r
    }

  /** Run the parser, and run the effects.
   */
  def parse[C](
      parser: OParser[_, C],
      args: CSeq[String],
      init: C,
      esetup: OEffectSetup
  ): Option[C] =
    ORunner.runParser(args, init, parser.toList, psetup0) match {
      case (r, es) => ORunner.runEffects(es, esetup); r
    }

  /** Run the parser, and return the result and the effects.
   */
  def runParser[C](parser: OParser[_, C], args: CSeq[String], init: C): (Option[C], List[OEffect]) =
    ORunner.runParser(args, init, parser.toList, psetup0)

  /** Run the parser, and return the result and the effects.
   */
  def runParser[C](
      parser: OParser[_, C],
      args: CSeq[String],
      init: C,
      psetup: OParserSetup
  ): (Option[C], List[OEffect]) =
    ORunner.runParser(args, init, parser.toList, psetup)

  def runEffects(es: List[OEffect]): Unit = ORunner.runEffects(es, esetup0)

  def runEffects(es: List[OEffect], esetup: OEffectSetup): Unit = ORunner.runEffects(es, esetup)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy