caseapp.core.parser.ConsParser.scala Maven / Gradle / Ivy
package caseapp.core.parser
import caseapp.core.argparser.{ArgParser, Consumed}
import caseapp.core.{Arg, Error}
import caseapp.core.util.NameOps.toNameOps
import shapeless.{:: => :*:, HList}
final case class ConsParser[H, T <: HList, DT <: HList](
arg: Arg,
argParser: ArgParser[H],
default: Option[H],
tail: Parser.Aux[T, DT]
) extends Parser[H :*: T] {
type D = Option[H] :*: DT
def init: D =
None :: tail.init
def step(args: List[String], d: Option[H] :*: tail.D): Either[(Error, List[String]), Option[(D, List[String])]] =
args match {
case Nil =>
Right(None)
case firstArg :: rem =>
val matchedOpt = (Iterator(arg.name) ++ arg.extraNames.iterator)
.map(n => n -> n(firstArg))
.collectFirst {
case (n, Right(valueOpt)) => n -> valueOpt
}
matchedOpt match {
case Some((name, valueOpt)) =>
val (res, rem0) = valueOpt match {
case Some(value) =>
val res0 = argParser(d.head, value)
.right
.map(h => Some(Some(h) :: d.tail))
(res0, rem)
case None =>
rem match {
case Nil =>
val res0 = argParser(d.head)
.right
.map(h => Some(Some(h) :: d.tail))
(res0, Nil)
case th :: tRem =>
val (Consumed(usedArg), res) = argParser.optional(d.head, th)
val res0 = res.right.map(h => Some(Some(h) :: d.tail))
(res0, if (usedArg) tRem else rem)
}
}
res
.left
.map { err =>
(Error.ParsingArgument(name, err), rem0)
}
.right
.map(_.map((_, rem0)))
case None =>
tail
.step(args, d.tail)
.right
.map(_.map {
case (t, args) => (d.head :: t, args)
})
}
}
def get(d: D): Either[Error, H :*: T] = {
val maybeHead = d.head
.orElse(default)
.toRight {
Error.RequiredOptionNotSpecified(
arg.name.option,
arg.extraNames.map(_.option)
)
}
val maybeTail = tail.get(d.tail)
(maybeHead, maybeTail) match {
case (Left(headErr), Left(tailErrs)) => Left(headErr.append(tailErrs))
case (Left(headErr), _) => Left(headErr)
case (_, Left(tailErrs)) => Left(tailErrs)
case (Right(h), Right(t)) => Right(h :: t)
}
}
val args: Seq[Arg] =
arg +: tail.args
def mapHead[I](f: H => I): Parser.Aux[I :*: T, D] =
map { l =>
f(l.head) :: l.tail
}
}