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

com.concurrentthought.cla.OptParser.scala Maven / Gradle / Ivy

package com.concurrentthought.cla
import org.parboiled.scala._  // scalastyle:ignore
import org.parboiled.errors.{ErrorUtils, ParsingException}

/** A set of "Elements", used for convenient access from clients. */
object Elems {
  sealed trait Elem

  final case class OptElem(optional: Boolean, flags_remaining: FlagsAndType_Or_RemainingElem, help: String) extends Elem

  sealed abstract class FlagsAndType_Or_RemainingElem extends Elem
  final case class  FlagsAndTypeElem(flags: FlagsElem, typ: TypeElem[_]) extends FlagsAndType_Or_RemainingElem
  final case class  RemainingElem(name: String) extends FlagsAndType_Or_RemainingElem


  final case class FlagsElem(flags: Seq[FlagElem]) extends Elem
  final case class FlagElem(flag: String) extends Elem
  final case class StringElem(text: String) extends Elem

  import scala.reflect.ClassTag

  sealed abstract class TypeElem[T : ClassTag](val initialValueStr: String)(toT: String => T) extends Elem {
    val initialValue: Option[T] = try {
      if (initialValueStr.trim == "") None else Some(toT(initialValueStr))
    } catch {
      case scala.util.control.NonFatal(ex) =>
        throw new ParsingException(
          s"Invalid initial value string, '$initialValueStr' for type ${implicitly[ClassTag[T]]}. Cause: $ex",
          ex)
    }
  }

  final case class  FlagTypeElem(   ivs: String) extends TypeElem[Boolean](removeEQ(ivs))(_.toBoolean)
  final case class  StringTypeElem( ivs: String) extends TypeElem[String](removeEQ(ivs))(_.toString)
  final case class  ByteTypeElem(   ivs: String) extends TypeElem[Byte](removeEQ(ivs))(_.toByte)
  final case class  CharTypeElem(   ivs: String) extends TypeElem[Char](removeEQ(ivs))(_(0))
  final case class  IntTypeElem(    ivs: String) extends TypeElem[Int](removeEQ(ivs))(_.toInt)
  final case class  LongTypeElem(   ivs: String) extends TypeElem[Long](removeEQ(ivs))(_.toLong)
  final case class  FloatTypeElem(  ivs: String) extends TypeElem[Float](removeEQ(ivs))(_.toFloat)
  final case class  DoubleTypeElem( ivs: String) extends TypeElem[Double](removeEQ(ivs))(_.toDouble)

  final case class  SeqTypeElem(delimiter: String, ivs: String)  extends TypeElem[String](toVS(ivs))(identity)
  final case class  PathTypeElem(ivs: String)   extends TypeElem[String](removeEQ(ivs))(identity)

  private def removeEQ(s:String) =
    if (s.startsWith("=")) s.substring(1,s.length) else s

  // Because of the way the parse strings are passed to SeqTypeElem, the ivs
  // string includes the delimiter string, so we remove it here, then remove
  // the equals sign.
  private def toVS(s:String) = {
    val ary = s.split("=",2)
    if (ary.length == 2) removeEQ(ary(1)) else ""
  }
}

// scalastyle:off

/** Parse a line defining an option. */
object OptParser extends Parser {

  val knownTypes = Vector ("flag", "~flag", "string", "byte", "char", "int", "long", "float", "double", "seq", "path")

  import Elems._

  protected def whiteSpace = " \n\r\t\f"

  def Opt: Rule1[OptElem] = rule { OptionalOpt | RequiredOpt }

  def OptionalOpt: Rule1[OptElem] = rule { group(OptionalFlagsAndType_Or_Remaining ~
    optional(WhiteSpacePlus) ~ Help) ~~> ((ft: FlagsAndType_Or_RemainingElem, help: String) => OptElem(true, ft, help)) }
  def RequiredOpt: Rule1[OptElem] = rule { group(FlagsAndType_Or_Remaining ~
    optional(WhiteSpacePlus) ~ Help) ~~> ((ft: FlagsAndType_Or_RemainingElem, help: String) => OptElem(false, ft, help)) }

  def LeftBracket  = rule { "[" ~ WhiteSpaceStar }
  def RightBracket = rule { WhiteSpaceStar ~ "]" }

  def OptionalFlagsAndType_Or_Remaining: Rule1[FlagsAndType_Or_RemainingElem] = rule {
    group(LeftBracket ~ FlagsAndType_Or_Remaining ~ RightBracket) }

  def FlagsAndType_Or_Remaining: Rule1[FlagsAndType_Or_RemainingElem] = rule {
    (FlagsAndType | Remaining) }

  def FlagsAndType: Rule1[FlagsAndTypeElem] = rule {
    group(Flags ~ WhiteSpacePlus ~ TypeAndInit) ~~> ((f: FlagsElem, t: TypeElem[_]) => FlagsAndTypeElem(f,t)) }

  // def Remaining: Rule1[RemainingElem] = rule { RT ~ (WhiteSpacePlus | EOI) }
  // protected def RT = rule { Name ~~> (se => RemainingElem(se.text)) }
  def Remaining: Rule1[RemainingElem] = rule { Name ~~> (se => RemainingElem(se.text)) }

  def Flags: Rule1[FlagsElem] = rule { oneOrMore(Flag, separator = WhiteSpaceStar ~ "|" ~ WhiteSpaceStar) ~~> FlagsElem }
  def Flag: Rule1[FlagElem]   = rule { Flag2 ~> FlagElem }
  def Flag2 = rule { ("--" | "-") ~ N2 }

  def TypeAndInit: Rule1[TypeElem[_]] = rule {
    FlagType | NotFlagType |
    StringType | ByteType | CharType |
    IntType | LongType | FloatType | DoubleType |
    SeqType | PathType }

  def FlagType:     Rule1[FlagTypeElem]    = rule { "flag"   ~ push(FlagTypeElem("false")) }
  def NotFlagType:  Rule1[FlagTypeElem]    = rule { "~flag"  ~ push(FlagTypeElem("true"))  }
  def StringType:   Rule1[StringTypeElem]  = rule { "string" ~  InitialValue ~> StringTypeElem }
  def ByteType:     Rule1[ByteTypeElem]    = rule { "byte"   ~  InitialValue ~> ByteTypeElem }
  def CharType:     Rule1[CharTypeElem]    = rule { "char"   ~  InitialValue ~> CharTypeElem }
  def IntType:      Rule1[IntTypeElem]     = rule { "int"    ~  InitialValue ~> IntTypeElem }
  def LongType:     Rule1[LongTypeElem]    = rule { "long"   ~  InitialValue ~> LongTypeElem }
  def FloatType:    Rule1[FloatTypeElem]   = rule { "float"  ~  InitialValue ~> FloatTypeElem }
  def DoubleType:   Rule1[DoubleTypeElem]  = rule { "double" ~  InitialValue ~> DoubleTypeElem }

  def SeqType:   Rule1[SeqTypeElem]  = rule { "seq" ~ SeqDIV ~~> SeqTypeElem }
  def PathType:  Rule1[PathTypeElem] = rule { "path" ~ InitialValue ~> PathTypeElem }
  protected def SeqDIV = rule { group(Delim ~ InitialValue) ~> identity }

  def Help: Rule1[String] = rule { zeroOrMore(ANY) ~> identity }

  def InitialValue = rule { optional("=" ~ oneOrMore(noneOf(whiteSpace+"[]"))) }

  // Keep the trailing ")", for use in splitting "upstream".
  def Delim = rule { "(" ~ D2 ~ ")" }
  protected def D2: Rule1[String] = rule { oneOrMore(noneOf(")")) ~> identity }

  def Name  = rule { N2 ~> StringElem }
  protected def N2 = rule { LDU ~ zeroOrMore(LDU | "-")  }

  def LDU   = rule { Letter | Digit | "_" }
  def Letter = rule { "a" - "z" | "A" - "Z"  }
  def Digit = rule { "0" - "9" }

  def WhiteSpace     = rule { anyOf(whiteSpace) }
  def WhiteSpaceStar = rule { zeroOrMore(WhiteSpace) }
  def WhiteSpacePlus = rule { oneOrMore(WhiteSpace) }

  /** Parse a full option string */
  def parse(s: String): Either[ParsingException, OptElem] =
    parseWithRule(s, Opt)

  def parseWithRule[E](s: String, rule: Rule1[E]): Either[ParsingException, E] =
    try {
      val result: ParsingResult[E] = ReportingParseRunner(rule).run(s.trim)
      result.result match {
        case Some(x) => Right(x)
        case None => Left(new ParsingException(
          s"Invalid input: `${s}'\n  Error:" + ErrorUtils.printParseErrors(result)))
      }
    } catch {
      case pe: ParsingException => Left(pe)
    }
}

// scalastyle:on




© 2015 - 2025 Weber Informatics LLC | Privacy Policy