harness.cli.Parser.scala Maven / Gradle / Ivy
package harness.cli
import cats.data.NonEmptyList
import cats.syntax.either.*
import cats.syntax.option.*
import harness.cli.error.*
import harness.core.*
import scala.annotation.tailrec
import scala.annotation.unchecked.uncheckedVariance
import scala.reflect.ClassTag
sealed trait Parser[+A] {
def optionalName: Option[Name]
def helpMessage: HelpMessage
def parse(values: List[Arg.ValueLike], params: List[Arg.ParamLike]): Parser.ParseResult[A]
/**
* This should do 2 things:
* 1) Fail if there are duplicate param names
* 2) Convert Defaultable.Auto into Defaultable.Some/Defaultable.None based on start of long name
*/
def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Parser[A])]
def map[B](f: A => B): Parser[B] = Parser.Mapped(this, f)
def mapOrFail[B](f: A => Either[String, B]): Parser[B] = Parser.MappedOrFail(this, f)
final def <||[A2 >: A](that: Parser[A2]): Parser[A2] =
Parser.merge(this, that)(
Values.Or(_, _),
Params.Or(_, _),
{
case (Parser.Empty, b: Parser[A2]) => b
case (a: Parser[A], Parser.Empty) => a
case (a, b) => Parser.Or(a, b)
},
)
final def <||>[B](that: Parser[B]): Parser[Either[A, B]] =
Parser.merge(this, that)(
(a, b) => Values.Or(a.map(_.asLeft), b.map(_.asRight)),
(a, b) => Params.Or(a.map(_.asLeft), b.map(_.asRight)),
{
case (Parser.Empty, b: Parser[B]) => b.map(_.asRight)
case (a: Parser[A], Parser.Empty) => a.map(_.asLeft)
case (a, b) => Parser.Or(a.map(_.asLeft), b.map(_.asRight))
},
)
final def ^>>[B](that: Parser[B])(implicit zip: Zip[A @uncheckedVariance, B]): Parser[zip.Out] =
Parser.merge(this, that)(
Values.Then(_, _, zip),
Params.And(_, _, zip),
(a, b) =>
(a, b, zip) match {
case (Parser.Empty, Parser.Empty, _: Zip.ZipLeftId[?]) => Parser.Empty.asInstanceOf[Parser[zip.Out]]
case (Parser.Empty, Parser.Empty, _: Zip.ZipRightId[?]) => Parser.Empty.asInstanceOf[Parser[zip.Out]]
case (a: Parser[A], Parser.Empty, _: Zip.ZipRightId[?]) => a.asInstanceOf[Parser[zip.Out]]
case (Parser.Empty, b: Parser[B], _: Zip.ZipLeftId[?]) => b.asInstanceOf[Parser[zip.Out]]
case (a, b, _) => Parser.Then(a, b, zip)
},
)
final def bracketed(name: LongName): Values[A] = Values.Bracketed(name, this, Nil)
final def build: Either[BuildError, Parser[Either[HelpType, A]]] =
for {
(up, help2) <- Parser.help.buildInternal(Set.empty)
(_, self2) <- this.buildInternal(up)
} yield help2 <||> self2
}
object Parser {
// =====| |=====
case object Empty extends Parser[Unit] {
override def optionalName: Option[Name] = None
override def helpMessage: HelpMessage = HelpMessage.RootMessage.Empty
override def parse(values: List[Arg.ValueLike], params: List[Arg.ParamLike]): Parser.ParseResult[Unit] = Parser.ParseResult.Success((), Nil, values, params)
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Parser[Unit])] = (usedParams, this).asRight
}
final case class Then[A, B, O](
left: Parser[A],
right: Parser[B],
zip: Zip.Out[A, B, O],
) extends Parser[O] {
override def optionalName: Option[Name] = None
override def helpMessage: HelpMessage = HelpMessage.RootMessage.And(left.helpMessage, right.helpMessage)
override def parse(values: List[Arg.ValueLike], params: List[Arg.ParamLike]): Parser.ParseResult[O] =
left.parse(values, params) match {
case Parser.ParseResult.Success(value1, parsed1, remainingValues1, remainingParams1) =>
right.parse(remainingValues1, remainingParams1) match {
case ParseResult.Success(value2, parsed2, remainingValues2, remainingParams2) =>
Parser.ParseResult.Success(zip.zip(value1, value2), parsed1 ::: parsed2, remainingValues2, remainingParams2)
case fail @ ParseResult.Fail(_, _) => fail
}
case fail @ ParseResult.Fail(_, _) => fail
}
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Parser[O])] =
for {
(up2, left2) <- left.buildInternal(usedParams)
(up3, right2) <- right.buildInternal(up2)
} yield (up3, Then(left2, right2, zip))
}
final case class Or[A](
left: Parser[A],
right: Parser[A],
) extends Parser[A] {
override def optionalName: Option[Name] = None
override def helpMessage: HelpMessage = HelpMessage.RootMessage.Or(left.helpMessage, right.helpMessage)
override def parse(values: List[Arg.ValueLike], params: List[Arg.ParamLike]): Parser.ParseResult[A] =
left.parse(values, params) match {
case success @ Parser.ParseResult.Success(_, _, _, _) => success
case Parser.ParseResult.Fail(error1, help1) =>
right.parse(values, params) match {
case success @ Parser.ParseResult.Success(_, _, _, _) => success
case Parser.ParseResult.Fail(error2, help2) => Parser.ParseResult.Fail(ParseError.RootOr(error1, error2), HelpMessage.RootMessage.Or(help1, help2))
}
}
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Parser[A])] =
for {
(up2, left2) <- left.buildInternal(usedParams)
(up3, right2) <- right.buildInternal(usedParams)
} yield (up2 | up3, Or(left2, right2))
override def map[B](f: A => B): Parser[B] = Parser.Or(left.map(f), right.map(f))
override def mapOrFail[B](f: A => Either[String, B]): Parser[B] = Parser.Or(left.mapOrFail(f), right.mapOrFail(f))
}
final case class Mapped[A, B](
parser: Parser[A],
f: A => B,
) extends Parser[B] {
override def optionalName: Option[Name] = parser.optionalName
override def helpMessage: HelpMessage = parser.helpMessage
override def parse(values: List[Arg.ValueLike], params: List[Arg.ParamLike]): Parser.ParseResult[B] =
parser.parse(values, params).map(f)
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Parser[B])] =
parser.buildInternal(usedParams).map { case (up2, p) => (up2, Mapped(p, f)) }
override def map[C](f: B => C): Parser[C] = Mapped(parser, this.f.andThen(f))
override def mapOrFail[C](f: B => Either[String, C]): Parser[C] = MappedOrFail(parser, this.f.andThen(f))
}
final case class MappedOrFail[A, B](
parser: Parser[A],
f: A => Either[String, B],
) extends Parser[B] {
override def optionalName: Option[Name] = parser.optionalName
override def helpMessage: HelpMessage = parser.helpMessage
override def parse(values: List[Arg.ValueLike], params: List[Arg.ParamLike]): Parser.ParseResult[B] =
parser.parse(values, params).mapOrFail(this)(f)
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Parser[B])] =
parser.buildInternal(usedParams).map { case (up2, p) => (up2, MappedOrFail(p, f)) }
override def map[C](f: B => C): Parser[C] = MappedOrFail(parser, this.f(_).map(f))
override def mapOrFail[C](f: B => Either[String, C]): Parser[C] = MappedOrFail(parser, this.f(_).flatMap(f))
}
// =====| |=====
sealed trait ParseResult[+A] {
final def map[B](f: A => B): ParseResult[B] = this match
case Parser.ParseResult.Success(value, parsed, remainingValues, remainingParams) => Parser.ParseResult.Success(f(value), parsed, remainingValues, remainingParams)
case fail @ Parser.ParseResult.Fail(_, _) => fail
final def mapOrFail[B](parser: Parser[?])(f: A => Either[String, B]): ParseResult[B] = this match {
case Parser.ParseResult.Success(value, parsed, remainingValues, remainingParams) =>
f(value) match {
case Right(value) => Parser.ParseResult.Success(value, parsed, remainingValues, remainingParams)
case Left(error) => Parser.ParseResult.Fail(ParseError.RootValidation(parsed, ParseError.FailedValidation(error)), parser.helpMessage.addHints(HelpHint.Error(error) :: Nil))
}
case fail @ Parser.ParseResult.Fail(_, _) => fail
}
final def toFinal: Parser.FinalParseResult[A] = this match
case Parser.ParseResult.Success(value, parsed, Nil, Nil) => Parser.FinalParseResult.Success(value, parsed)
case Parser.ParseResult.Success(_, _, upvH :: upvT, Nil) =>
Parser.FinalParseResult.Fail(
ParseError.UnparsedValues(NonEmptyList(upvH, upvT)),
HelpMessage.ValueMessage.UnparsedArgs(NonEmptyList(upvH, upvT)),
)
case Parser.ParseResult.Success(_, _, Nil, uppH :: uppT) =>
Parser.FinalParseResult.Fail(
ParseError.UnparsedParams(NonEmptyList(uppH, uppT)),
HelpMessage.ParamMessage.UnparsedArgs(NonEmptyList(uppH, uppT)),
)
case Parser.ParseResult.Success(_, _, upvH :: upvT, uppH :: uppT) =>
Parser.FinalParseResult.Fail(
ParseError.RootAnd(
ParseError.UnparsedValues(NonEmptyList(upvH, upvT)),
ParseError.UnparsedParams(NonEmptyList(uppH, uppT)),
),
HelpMessage.RootMessage.And(
HelpMessage.ValueMessage.UnparsedArgs(NonEmptyList(upvH, upvT)),
HelpMessage.ParamMessage.UnparsedArgs(NonEmptyList(uppH, uppT)),
),
)
case Parser.ParseResult.Fail(error, help) => Parser.FinalParseResult.Fail(error, help)
}
object ParseResult {
final case class Success[+A](value: A, parsed: List[ParsedArg], remainingValues: List[Arg.ValueLike], remainingParams: List[Arg.ParamLike]) extends ParseResult[A]
final case class Fail(error: ParseError, help: HelpMessage) extends ParseResult[Nothing]
}
sealed trait FinalParseResult[+A]
object FinalParseResult {
final case class Success[+A](value: A, parsed: List[ParsedArg]) extends FinalParseResult[A]
final case class Fail(error: ParseError, help: HelpMessage) extends FinalParseResult[Nothing]
}
// =====| |=====
private[cli] def validateNoDuplicates(usedParams: Set[SimpleName], myParams: Set[SimpleName]): Either[BuildError, Set[SimpleName]] = {
val overlap = usedParams & myParams
if (overlap.isEmpty) (usedParams | myParams).asRight
else BuildError.DuplicateParams(overlap).asLeft
}
private[cli] def defaultAuto(usedParams: Set[SimpleName], long: LongName, short: Defaultable.Optional[ShortName]): (Set[SimpleName], Defaultable.Optional[ShortName]) =
(short, long.firstChar.expand) match
case (Defaultable.Auto, Right((_, lower))) if !usedParams.contains(lower) => (usedParams + lower, Defaultable.Some(lower))
case (Defaultable.Auto, Right((upper, _))) if !usedParams.contains(upper) => (usedParams + upper, Defaultable.Some(upper))
case (Defaultable.Auto, Left(digit)) if !usedParams.contains(digit) => (usedParams + digit, Defaultable.Some(digit))
case _ => (usedParams, short)
private[cli] def defaultAuto(usedParams: Set[SimpleName], long: BooleanLongName, short: Defaultable.Optional[BooleanShortName]): (Set[SimpleName], Defaultable.Optional[BooleanShortName]) =
(short, long.base.firstChar.expand) match
case (Defaultable.Auto, Right((upper, lower))) if !usedParams.contains(lower) && !usedParams.contains(upper) => (usedParams + lower + upper, Defaultable.Some(BooleanShortName(upper, lower)))
case _ => (usedParams, short)
private def merge[A, B, C](a: Parser[A], b: Parser[B])(
mergeValues: (Values[A], Values[B]) => Values[C],
mergeParams: (Params[A], Params[B]) => Params[C],
mergeRoot: (Parser[A], Parser[B]) => Parser[C],
): Parser[C] =
(a, b) match
case (a: Values[A], b: Values[B]) => mergeValues(a, b)
case (a: Params[A], b: Params[B]) => mergeParams(a, b)
case _ => mergeRoot(a, b)
// =====| |=====
val unit: Parser[Unit] = Parser.Empty
private[cli] val help: Parser[HelpType] =
Values.Ignored ^>>
(
Params.valueWith("help-extra", 'H', hints = "Show detailed help message" :: Nil)(Values.Ignored.map(_ => HelpType.HelpExtra)) <||
Params.valueWith("help", 'h', hints = "Show help message" :: Nil)(Values.Ignored.map(_ => HelpType.Help))
) &&
Params.Ignored
}
sealed trait Values[+A] extends Parser[A] {
def parseValues(values: List[Arg.ValueLike]): Values.ParseResult[A]
override def helpMessage: HelpMessage.ValueMessage
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Values[A])]
override final def parse(values: List[Arg.ValueLike], params: List[Arg.ParamLike]): Parser.ParseResult[A] = parseValues(values).toParserParseResult(params)
final def ^>>[B](that: Values[B])(implicit zip: Zip[A @uncheckedVariance, B]): Values[zip.Out] = Values.Then(this, that, zip)
final def <||[A2 >: A](that: Values[A2]): Values[A2] = Values.Or(this, that)
final def <||>[B](that: Values[B]): Values[Either[A, B]] = Values.Or(this.map(_.asLeft), that.map(_.asRight))
override def map[B](f: A => B): Values[B] = Values.Mapped(this, f)
override def mapOrFail[B](f: A => Either[String, B]): Values[B] = Values.MappedOrFail(this, f)
final def optional: Values[Option[A]] = Values.Optional(this, false)
final def optional(breakOnAnyError: Boolean): Values[Option[A]] = Values.Optional(this, breakOnAnyError)
final def repeated: Values[List[A]] = Values.Repeated(this, true)
final def repeated(breakOnAnyError: Boolean): Values[List[A]] = Values.Repeated(this, breakOnAnyError)
final def repeatedNel: Values[NonEmptyList[A]] = Values.RepeatedNel(this, true)
final def repeatedNel(breakOnAnyError: Boolean): Values[NonEmptyList[A]] = Values.RepeatedNel(this, breakOnAnyError)
final def withDefault[A2 >: A](default: A2): Values[A2] = Values.WithDefault(this, default, false)
final def withDefault[A2 >: A](default: A2, breakOnAnyError: Boolean): Values[A2] = Values.WithDefault(this, default, breakOnAnyError)
final def withOptionalDefault[A2 >: A](default: Option[A2]): Values[A2] = default.fold(this)(this.withDefault(_))
final def withOptionalDefault[A2 >: A](default: Option[A2], breakOnAnyError: Boolean): Values[A2] = default.fold(this)(this.withDefault(_, breakOnAnyError))
}
object Values {
// =====| Builders |=====
def value[A: StringDecoder](
name: LongName,
hints: List[HelpHint.Make] = Nil,
): Values[A] =
Values
.SingleValue(
name = name,
hints = hints.map(HelpHint(_)),
)
.mapOrFail(StringDecoder[A].decode)
// =====| |=====
final case class SingleValue(
name: LongName,
hints: List[HelpHint],
) extends Values[String] {
override def optionalName: Option[Name] = name.some
override def helpMessage: HelpMessage.ValueMessage = HelpMessage.ValueMessage.Value(name).addHints(hints)
override def parseValues(values: List[Arg.ValueLike]): Values.ParseResult[String] =
values match
case (arg @ Arg.Value(_, value)) :: rest => Values.ParseResult.Success(value, ParsedValueArg(name :: Nil, arg :: Nil) :: Nil, rest)
case (arg @ Arg.Bracketed(_, _, _)) :: _ =>
Values.ParseResult.Fail(ParseError.ValueError(name, arg, ParseError.ExpectedValueArg), helpMessage.addHints(HelpHint.Error("Expected value arg") :: Nil))
case Nil =>
Values.ParseResult.Fail(ParseError.ValueError(name, ParseError.MissingRequiredValue), helpMessage.addHints(HelpHint.Error("Missing required value") :: Nil))
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Values[String])] =
(usedParams, this).asRight
}
final case class Bracketed[A](
name: LongName,
parser: Parser[A],
hints: List[HelpHint],
) extends Values[A] {
override def optionalName: Option[Name] = name.some
override def helpMessage: HelpMessage.ValueMessage = HelpMessage.ValueMessage.Bracketed(name, parser.helpMessage).addHints(hints)
override def parseValues(values: List[Arg.ValueLike]): Values.ParseResult[A] =
values match {
case (arg @ Arg.Bracketed(_, values, params)) :: tail =>
val parsedArg = ParsedValueArg(name :: Nil, arg :: Nil)
parser.parse(values, params).toFinal match {
case Parser.FinalParseResult.Success(value, _) => Values.ParseResult.Success(value, parsedArg :: Nil, tail)
case Parser.FinalParseResult.Fail(error, help) =>
Values.ParseResult.Fail(
ParseError.SingleValueError(parsedArg :: Nil, ParseError.BracketedError(error)),
HelpMessage.ValueMessage.Bracketed(name, help).addHints(hints),
)
}
case (arg @ Arg.Value(_, _)) :: _ =>
Values.ParseResult.Fail(ParseError.ValueError(name, arg, ParseError.ExpectedBracketedArg), helpMessage.addHints(HelpHint.Error("Expected bracketed arg") :: Nil))
case Nil => Values.ParseResult.Fail(ParseError.ValueError(name, ParseError.MissingRequiredValue), helpMessage.addHints(HelpHint.Error("Missing required value") :: Nil))
}
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Values[A])] =
parser.buildInternal(Set.empty).map { case (_, p) => (usedParams, Bracketed(name, p, hints)) }
}
final case class Raw(
name: LongName,
hints: List[HelpHint],
) extends Values[Arg.ValueLike] {
override def optionalName: Option[Name] = name.some
override def helpMessage: HelpMessage.ValueMessage = HelpMessage.ValueMessage.Raw(name).addHints(hints)
override def parseValues(values: List[Arg.ValueLike]): Values.ParseResult[Arg.ValueLike] =
values match
case h :: t => Values.ParseResult.Success(h, ParsedValueArg(name :: Nil, h :: Nil) :: Nil, t)
case Nil => Values.ParseResult.Fail(ParseError.ValueError(name, ParseError.MissingRequiredValue), helpMessage.addHints(HelpHint.Error("Missing required value") :: Nil))
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Values[Arg.ValueLike])] =
(usedParams, this).asRight
}
case object Ignored extends Values[Unit] {
override def optionalName: Option[Name] = None
override def helpMessage: HelpMessage.ValueMessage = HelpMessage.ValueMessage.Empty
override def parseValues(values: List[Arg.ValueLike]): Values.ParseResult[Unit] =
Values.ParseResult.Success((), ParsedValueArg(Nil, values) :: Nil, Nil)
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Values[Unit])] =
(usedParams, this).asRight
}
final case class Optional[A](
parser: Values[A],
breakOnAnyError: Boolean,
) extends Values[Option[A]] {
override def optionalName: Option[Name] = parser.optionalName
override def helpMessage: HelpMessage.ValueMessage = parser.helpMessage.addHints(HelpHint.Optional :: Nil)
override def parseValues(values: List[Arg.ValueLike]): Values.ParseResult[Option[A]] =
parser.parseValues(values) match
case Values.ParseResult.Success(value, parsed, remaining) => Values.ParseResult.Success(value.some, parsed, remaining)
case Values.ParseResult.Fail(error, _) if breakOnAnyError || error.onlyContainsMissingRequiredValue => Values.ParseResult.Success(None, Nil, values)
case fail @ Values.ParseResult.Fail(_, _) => fail
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Values[Option[A]])] =
parser.buildInternal(usedParams).map { case (up2, p) => (up2, Optional(p, breakOnAnyError)) }
}
final case class Repeated[A](
parser: Values[A],
breakOnAnyError: Boolean,
) extends Values[List[A]] {
override def optionalName: Option[Name] = parser.optionalName
override def helpMessage: HelpMessage.ValueMessage = parser.helpMessage.addHints(HelpHint.Repeated :: Nil)
@tailrec
private def loop(
values: List[Arg.ValueLike],
parsed: List[ParsedValueArg],
rStack: List[A],
): Values.ParseResult[List[A]] =
parser.parseValues(values) match
case Values.ParseResult.Success(value, parsed2, remaining) if parsed2.nonEmpty => loop(remaining, parsed ::: parsed2, value :: rStack)
case Values.ParseResult.Fail(error, _) if breakOnAnyError || error.onlyContainsMissingRequiredValue => Values.ParseResult.Success(rStack.reverse, parsed, values)
case fail @ Values.ParseResult.Fail(_, _) => fail
case Values.ParseResult.Success(_, _, _) => Values.ParseResult.Success(rStack.reverse, parsed, values)
override def parseValues(values: List[Arg.ValueLike]): Values.ParseResult[List[A]] =
loop(values, Nil, Nil)
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Values[List[A]])] =
parser.buildInternal(usedParams).map { case (up2, p) => (up2, Repeated(p, breakOnAnyError)) }
}
final case class RepeatedNel[A](
parser: Values[A],
breakOnAnyError: Boolean,
) extends Values[NonEmptyList[A]] {
override def optionalName: Option[Name] = parser.optionalName
override def helpMessage: HelpMessage.ValueMessage = parser.helpMessage.addHints(HelpHint.RepeatedNel :: Nil)
@tailrec
private def loop(
values: List[Arg.ValueLike],
parsed: List[ParsedValueArg],
rStack: NonEmptyList[A],
): Values.ParseResult[NonEmptyList[A]] =
parser.parseValues(values) match
case Values.ParseResult.Success(value, parsed2, remaining) if parsed2.nonEmpty => loop(remaining, parsed ::: parsed2, value :: rStack)
case Values.ParseResult.Fail(error, _) if breakOnAnyError || error.onlyContainsMissingRequiredValue => Values.ParseResult.Success(rStack.reverse, parsed, values)
case fail @ Values.ParseResult.Fail(_, _) => fail
case Values.ParseResult.Success(_, _, _) => Values.ParseResult.Success(rStack.reverse, parsed, values)
override def parseValues(values: List[Arg.ValueLike]): Values.ParseResult[NonEmptyList[A]] =
parser.parseValues(values) match
case Values.ParseResult.Success(value, parsed, remaining) => loop(remaining, parsed, NonEmptyList.one(value))
case fail @ Values.ParseResult.Fail(_, _) => fail
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Values[NonEmptyList[A]])] =
parser.buildInternal(usedParams).map { case (up2, p) => (up2, RepeatedNel(p, breakOnAnyError)) }
}
final case class WithDefault[A](
parser: Values[A],
default: A,
breakOnAnyError: Boolean,
) extends Values[A] {
override def optionalName: Option[Name] = parser.optionalName
override def helpMessage: HelpMessage.ValueMessage = parser.helpMessage.addHints(HelpHint.Default(default) :: Nil)
override def parseValues(values: List[Arg.ValueLike]): Values.ParseResult[A] =
parser.parseValues(values) match
case Values.ParseResult.Success(value, parsed, remaining) => Values.ParseResult.Success(value, parsed, remaining)
case Values.ParseResult.Fail(error, _) if breakOnAnyError || error.onlyContainsMissingRequiredValue => Values.ParseResult.Success(default, Nil, values)
case fail @ Values.ParseResult.Fail(_, _) => fail
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Values[A])] =
parser.buildInternal(usedParams).map { case (up2, p) => (up2, WithDefault(p, default, breakOnAnyError)) }
}
final case class Mapped[A, B](
parser: Values[A],
f: A => B,
) extends Values[B] {
override def optionalName: Option[Name] = parser.optionalName
override def helpMessage: HelpMessage.ValueMessage = parser.helpMessage
override def parseValues(values: List[Arg.ValueLike]): Values.ParseResult[B] =
parser.parseValues(values).map(f)
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Values[B])] =
parser.buildInternal(usedParams).map { case (up2, p) => (up2, Mapped(p, f)) }
}
final case class MappedOrFail[A, B](
parser: Values[A],
f: A => Either[String, B],
) extends Values[B] {
override def optionalName: Option[Name] = parser.optionalName
override def helpMessage: HelpMessage.ValueMessage = parser.helpMessage
override def parseValues(values: List[Arg.ValueLike]): Values.ParseResult[B] =
parser.parseValues(values).mapOrFail(this)(f)
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Values[B])] =
parser.buildInternal(usedParams).map { case (up2, p) => (up2, MappedOrFail(p, f)) }
}
final case class Then[A, B, O](
a: Values[A],
b: Values[B],
zip: Zip.Out[A, B, O],
) extends Values[O] {
override def optionalName: Option[Name] = None
override def helpMessage: HelpMessage.ValueMessage = HelpMessage.ValueMessage.Then(a.helpMessage, b.helpMessage)
override def parseValues(values: List[Arg.ValueLike]): Values.ParseResult[O] =
a.parseValues(values) match {
case Values.ParseResult.Success(value1, parsed1, remaining1) =>
b.parseValues(remaining1) match {
case Values.ParseResult.Success(value2, parsed2, remaining2) => Values.ParseResult.Success(zip.zip(value1, value2), parsed1 ::: parsed2, remaining2)
case fail @ Values.ParseResult.Fail(_, _) => fail
}
case fail @ Values.ParseResult.Fail(_, _) => fail
}
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Values[O])] =
for {
(up2, a2) <- a.buildInternal(usedParams)
(up3, b2) <- b.buildInternal(up2)
} yield (up3, Then(a2, b2, zip))
}
final case class Or[A](
left: Values[A],
right: Values[A],
) extends Values[A] {
override def optionalName: Option[Name] = None
override def helpMessage: HelpMessage.ValueMessage = HelpMessage.ValueMessage.Or(left.helpMessage, right.helpMessage)
override def parseValues(values: List[Arg.ValueLike]): Values.ParseResult[A] =
left.parseValues(values) match {
case success @ Values.ParseResult.Success(_, _, _) => success
case Values.ParseResult.Fail(error1, help1) =>
right.parseValues(values) match {
case success @ Values.ParseResult.Success(_, _, _) => success
case Values.ParseResult.Fail(error2, help2) => Values.ParseResult.Fail(ParseError.ValueErrorOr(error1, error2), HelpMessage.ValueMessage.Or(help1, help2))
}
}
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Values[A])] =
for {
(up2, left2) <- left.buildInternal(usedParams)
(up3, right2) <- right.buildInternal(usedParams)
} yield (up2 | up3, Or(left2, right2))
override def map[B](f: A => B): Values[B] = Values.Or(left.map(f), right.map(f))
override def mapOrFail[B](f: A => Either[String, B]): Values[B] = Values.Or(left.mapOrFail(f), right.mapOrFail(f))
}
// =====| |=====
sealed trait ParseResult[+A] {
final def map[B](f: A => B): ParseResult[B] = this match
case ParseResult.Success(value, parsed, remaining) => ParseResult.Success(f(value), parsed, remaining)
case fail @ ParseResult.Fail(_, _) => fail
final def mapOrFail[B](parser: Values[?])(f: A => Either[String, B]): ParseResult[B] = this match {
case ParseResult.Success(value, parsed, remaining) =>
f(value) match {
case Right(value) => ParseResult.Success(value, parsed, remaining)
case Left(error) => ParseResult.Fail(ParseError.SingleValueError(parsed, ParseError.FailedValidation(error)), parser.helpMessage.addHints(HelpHint.Error(error) :: Nil))
}
case fail @ ParseResult.Fail(_, _) => fail
}
final def toFinal: FinalParseResult[A] = this match
case ParseResult.Success(value, parsed, Nil) => FinalParseResult.Success(value, parsed)
case ParseResult.Success(_, _, h :: t) => FinalParseResult.Fail(ParseError.UnparsedValues(NonEmptyList(h, t)), HelpMessage.ValueMessage.UnparsedArgs(NonEmptyList(h, t)))
case ParseResult.Fail(error, help) => FinalParseResult.Fail(error, help)
final def toParserParseResult(params: List[Arg.ParamLike]): Parser.ParseResult[A] = this match
case ParseResult.Success(value, parsed, remaining) => Parser.ParseResult.Success(value, parsed, remaining, params)
case ParseResult.Fail(error, help) => Parser.ParseResult.Fail(error, help)
}
object ParseResult {
final case class Success[+A](value: A, parsed: List[ParsedValueArg], remaining: List[Arg.ValueLike]) extends ParseResult[A]
final case class Fail(error: ParseError.ValueError, help: HelpMessage.ValueMessage) extends ParseResult[Nothing]
}
sealed trait FinalParseResult[+A]
object FinalParseResult {
final case class Success[+A](value: A, parsed: List[ParsedValueArg]) extends FinalParseResult[A]
final case class Fail(error: ParseError.ValueError | ParseError.UnparsedValues, help: HelpMessage.ValueMessage) extends FinalParseResult[Nothing]
}
}
sealed trait Params[+A] extends Parser[A] {
def parseParams(params: List[Arg.ParamLike]): Params.ParseResult[A]
override def helpMessage: HelpMessage.ParamMessage
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Params[A])]
override final def parse(values: List[Arg.ValueLike], params: List[Arg.ParamLike]): Parser.ParseResult[A] = parseParams(params).toParserParseResult(values)
override def map[B](f: A => B): Params[B] = Params.Mapped(this, f)
override def mapOrFail[B](f: A => Either[String, B]): Params[B] = Params.MappedOrFail(this, f)
// --- ---
final def optional: Params[Option[A]] = Params.Optional(this)
final def repeated: Params[List[A]] = Params.Repeated(this)
final def repeatedNel: Params[NonEmptyList[A]] = Params.RepeatedNel(this)
final def withDefault[A2 >: A](default: A2): Params[A2] = Params.WithDefault(this, default)
final def withOptionalDefault[A2 >: A](default: Option[A2]): Params[A2] = default.fold(this)(this.withDefault(_))
final def &&[B](that: Params[B])(implicit zip: Zip[A @uncheckedVariance, B]): Params[zip.Out] = Params.And(this, that, zip)
final def <||[A2 >: A](that: Params[A2]): Params[A2] = Params.Or(this, that)
final def <||>[B](that: Params[B]): Params[Either[A, B]] = Params.Or(this.map(_.asLeft), that.map(_.asRight))
final def ||[A2 >: A](that: Params[A2]): Params[A2] = (this, that) match
case (Params.FirstOfByArgIndex(options1), Params.FirstOfByArgIndex(options2)) => Params.FirstOfByArgIndex(options1 ::: options2)
case (Params.FirstOfByArgIndex(options1), _) => Params.FirstOfByArgIndex(options1 :+ that)
case (_, Params.FirstOfByArgIndex(options2)) => Params.FirstOfByArgIndex(this :: options2)
case (_, _) => Params.FirstOfByArgIndex(NonEmptyList.of(this, that))
}
object Params {
// =====| Builders |=====
def value[A: StringDecoder](
longName: LongName,
shortName: Defaultable.Optional[ShortName] = Defaultable.Auto,
aliases: List[SimpleName] = Nil,
hints: List[HelpHint.Make] = Nil,
): Params[A] =
Params.ParamWithValues(
longName = longName,
shortName = shortName,
aliases = aliases,
hints = hints.map(HelpHint(_)),
valueParser = Values.value[A](longName),
)
def `enum`[A <: Enum[A], Enc](
longName: LongName,
shortName: Defaultable.Optional[ShortName] = Defaultable.Auto,
aliases: List[SimpleName] = Nil,
hints: List[HelpHint.Make] = Nil,
)(implicit
ewc: Enum.WithEnc[A, Enc],
enc: StringEncoder[Enc],
dec: StringDecoder[Enc],
ct: ClassTag[A],
): Params[A] = {
implicit val aDec: StringDecoder[A] = StringDecoder.fromOptionF(ct.getClass.getSimpleName, dec.decodeAccumulating(_).toOption.flatMap(ewc.decode))
Params.ParamWithValues(
longName = longName,
shortName = shortName,
aliases = aliases,
hints = hints.map(HelpHint(_)) :+ HelpHint.EnumValues(ewc.encodedValues.map(enc.encode)),
valueParser = Values.value[A](longName),
)
}
def valueWith[A](
longName: LongName,
shortName: Defaultable.Optional[ShortName] = Defaultable.Auto,
aliases: List[SimpleName] = Nil,
hints: List[HelpHint.Make] = Nil,
)(valueParser: Values[A]): Params[A] =
Params.ParamWithValues(
longName = longName,
shortName = shortName,
aliases = aliases,
hints = hints.map(HelpHint(_)),
valueParser = valueParser,
)
def ifPresent[A](
longName: LongName,
value: A,
shortName: Defaultable.Optional[ShortName] = Defaultable.Auto,
aliases: List[SimpleName] = Nil,
hints: List[HelpHint.Make] = Nil,
): Params[A] =
Params.IfPresent(
longName = longName,
shortName = shortName,
aliases = aliases,
hints = hints.map(HelpHint(_)),
value = value,
)
def flag(
longName: LongName,
value: Boolean = true,
shortName: Defaultable.Optional[ShortName] = Defaultable.Auto,
aliases: List[SimpleName] = Nil,
hints: List[HelpHint.Make] = Nil,
): Params[Boolean] =
Params
.IfPresent(
longName = longName,
shortName = shortName,
aliases = aliases,
hints = hints.map(HelpHint(_)),
value = (),
)
.optional
.map {
case Some(_) => value
case None => !value
}
object toggle {
def apply(
longName: BooleanLongName,
shortName: Defaultable.Optional[BooleanShortName] = Defaultable.Auto,
aliases: List[BooleanName] = Nil,
hints: List[HelpHint.Make] = Nil,
): Params[Boolean] =
Params.BooleanToggle(
longName = longName,
shortName = shortName,
aliases = aliases,
hints = hints.map(HelpHint(_)),
)
def prefixTrue(
truePrefix: LongName,
baseName: LongName,
shortName: Defaultable.Optional[BooleanShortName] = Defaultable.Auto,
aliases: List[BooleanName] = Nil,
hints: List[HelpHint.Make] = Nil,
): Params[Boolean] =
Params.BooleanToggle(
longName = BooleanLongName.PrefixTrue(truePrefix, baseName),
shortName = shortName,
aliases = aliases,
hints = hints.map(HelpHint(_)),
)
def prefixFalse(
falsePrefix: LongName,
baseName: LongName,
shortName: Defaultable.Optional[BooleanShortName] = Defaultable.Auto,
aliases: List[BooleanName] = Nil,
hints: List[HelpHint.Make] = Nil,
): Params[Boolean] =
Params.BooleanToggle(
longName = BooleanLongName.PrefixFalse(falsePrefix, baseName),
shortName = shortName,
aliases = aliases,
hints = hints.map(HelpHint(_)),
)
def prefixBoth(
truePrefix: LongName,
falsePrefix: LongName,
baseName: LongName,
shortName: Defaultable.Optional[BooleanShortName] = Defaultable.Auto,
aliases: List[BooleanName] = Nil,
hints: List[HelpHint.Make] = Nil,
): Params[Boolean] =
Params.BooleanToggle(
longName = BooleanLongName.PrefixBoth(truePrefix, falsePrefix, baseName),
shortName = shortName,
aliases = aliases,
hints = hints.map(HelpHint(_)),
)
}
def firstOf[A](
parser0: Params[A],
parser1: Params[A],
parserN: Params[A]*,
): Params[A] =
Params.FirstOfByArgIndex(NonEmptyList(parser0, parser1 :: parserN.toList))
// =====| |=====
final case class Const[A](value: A) extends Params[A] {
override def optionalName: Option[Name] = None
override def helpMessage: HelpMessage.ParamMessage = HelpMessage.ParamMessage.Empty
override def parseParams(params: List[Arg.ParamLike]): ParseResult[A] =
ParseResult.Success(value, Nil, params)
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Params[A])] =
(usedParams, this).asRight
}
final case class ParamWithValues[A](
longName: LongName,
shortName: Defaultable.Optional[ShortName],
aliases: List[SimpleName],
hints: List[HelpHint],
valueParser: Values[A],
) extends Params[A] {
private val names: Set[SimpleName] = List(longName :: shortName.toOption.toList, aliases).flatten.toSet
override def optionalName: Option[Name] = longName.some
override def helpMessage: HelpMessage.ParamMessage = HelpMessage.ParamMessage.Param(longName, shortName.toOption, aliases, valueParser.helpMessage).addHints(hints)
override def parseParams(params: List[Arg.ParamLike]): Params.ParseResult[A] =
findParam(names, params) match {
case Some((param, rest)) =>
val parsedArg = ParsedParamArg(longName :: Nil, param :: Nil)
valueParser.parseValues(param.values).toFinal match {
case Values.FinalParseResult.Success(value, _) => Params.ParseResult.Success(value, parsedArg :: Nil, rest)
case Values.FinalParseResult.Fail(error, help) =>
Params.ParseResult.Fail(
ParseError.SingleParamError(parsedArg :: Nil, ParseError.ParamValuesValidation(error)),
HelpMessage.ParamMessage.Param(longName, shortName.toOption, aliases, help).addHints(hints),
)
}
case None =>
Params.ParseResult.Fail(ParseError.ParamError(longName, ParseError.MissingRequiredParam), helpMessage.addHints(HelpHint.Error("Missing required param") :: Nil))
}
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Params[A])] =
for {
up2 <- Parser.validateNoDuplicates(usedParams, names)
(_, valueParser2) <- valueParser.buildInternal(Set.empty)
(up3, shortName2) = Parser.defaultAuto(up2, longName, shortName)
} yield (
up3,
ParamWithValues(
longName = longName,
shortName = shortName2,
aliases = aliases,
hints = hints,
valueParser = valueParser2,
),
)
override def map[B](f: A => B): Params.ParamWithValues[B] = copy(valueParser = valueParser.map(f))
override def mapOrFail[B](f: A => Either[String, B]): Params.ParamWithValues[B] = copy(valueParser = valueParser.mapOrFail(f))
}
final case class IfPresent[A](
longName: LongName,
shortName: Defaultable.Optional[ShortName],
aliases: List[SimpleName],
hints: List[HelpHint],
value: A,
) extends Params[A] {
private val names: Set[SimpleName] = List(longName :: shortName.toOption.toList, aliases).flatten.toSet
override def optionalName: Option[Name] = longName.some
override def helpMessage: HelpMessage.ParamMessage = HelpMessage.ParamMessage.Param(longName, shortName.toOption, aliases, HelpMessage.ValueMessage.Empty).addHints(hints)
override def parseParams(params: List[Arg.ParamLike]): ParseResult[A] =
findParam(names, params) match {
case Some((param, rest)) =>
val parsedArg = ParsedParamArg(longName :: Nil, param :: Nil)
param.values match {
case Nil => Params.ParseResult.Success(value, parsedArg :: Nil, rest)
case h :: t =>
Params.ParseResult.Fail(
ParseError.SingleParamError(parsedArg :: Nil, ParseError.ParamValuesValidation(ParseError.UnparsedValues(NonEmptyList(h, t)))),
HelpMessage.ParamMessage.Param(longName, shortName.toOption, aliases, HelpMessage.ValueMessage.UnparsedArgs(NonEmptyList(h, t))).addHints(hints),
)
}
case None =>
Params.ParseResult.Fail(ParseError.ParamError(longName, ParseError.MissingRequiredParam), helpMessage.addHints(HelpHint.Error("Missing required param") :: Nil))
}
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Params[A])] =
for {
up2 <- Parser.validateNoDuplicates(usedParams, names)
(up3, shortName2) = Parser.defaultAuto(up2, longName, shortName)
} yield (
up3,
IfPresent(
longName = longName,
shortName = shortName2,
aliases = aliases,
hints = hints,
value = value,
),
)
}
final case class BooleanToggle(
longName: BooleanLongName,
shortName: Defaultable.Optional[BooleanShortName],
aliases: List[BooleanName],
hints: List[HelpHint],
) extends Params[Boolean] {
private val names: Map[SimpleName, Boolean] = List(longName :: Nil, shortName.toOption.toList).flatten.flatMap { n => List(n.trueName -> true, n.falseName -> false) }.toMap
override def optionalName: Option[Name] = longName.some
override def helpMessage: HelpMessage.ParamMessage = HelpMessage.ParamMessage.Param(longName, shortName.toOption, aliases, HelpMessage.ValueMessage.Empty).addHints(hints)
override def parseParams(params: List[Arg.ParamLike]): ParseResult[Boolean] =
findParamTagged(names, params) match {
case Some((param, value, rest)) =>
val parsedArg = ParsedParamArg(longName :: Nil, param :: Nil)
param.values match {
case Nil => Params.ParseResult.Success(value, parsedArg :: Nil, rest)
case h :: t =>
Params.ParseResult.Fail(
ParseError.SingleParamError(parsedArg :: Nil, ParseError.ParamValuesValidation(ParseError.UnparsedValues(NonEmptyList(h, t)))),
HelpMessage.ParamMessage.Param(longName, shortName.toOption, aliases, HelpMessage.ValueMessage.UnparsedArgs(NonEmptyList(h, t))).addHints(hints),
)
}
case None =>
Params.ParseResult.Fail(ParseError.ParamError(longName, ParseError.MissingRequiredParam), helpMessage.addHints(HelpHint.Error("Missing required param") :: Nil))
}
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Params[Boolean])] =
for {
up2 <- Parser.validateNoDuplicates(usedParams, names.keySet)
(up3, shortName2) = Parser.defaultAuto(up2, longName, shortName)
} yield (
up3,
BooleanToggle(
longName = longName,
shortName = shortName2,
aliases = aliases,
hints = hints,
),
)
}
final case class Raw(
name: LongName,
hints: List[HelpHint],
) extends Params[Arg.ParamLike] {
override def optionalName: Option[Name] = name.some
override def helpMessage: HelpMessage.ParamMessage = HelpMessage.ParamMessage.Raw(name).addHints(hints)
override def parseParams(params: List[Arg.ParamLike]): Params.ParseResult[Arg.ParamLike] =
params match
case h :: t => Params.ParseResult.Success(h, ParsedParamArg(name :: Nil, h :: Nil) :: Nil, t)
case Nil => Params.ParseResult.Fail(ParseError.ParamError(name, ParseError.MissingRequiredParam), helpMessage.addHints(HelpHint.Error("Missing required param") :: Nil))
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Params[Arg.ParamLike])] =
(usedParams, this).asRight
}
case object Ignored extends Params[Unit] {
override def optionalName: Option[Name] = None
override def helpMessage: HelpMessage.ParamMessage = HelpMessage.ParamMessage.Empty
override def parseParams(params: List[Arg.ParamLike]): Params.ParseResult[Unit] =
Params.ParseResult.Success((), ParsedParamArg(Nil, params) :: Nil, Nil)
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Params[Unit])] =
(usedParams, this).asRight
}
final case class Optional[A](
parser: Params[A],
) extends Params[Option[A]] {
override def optionalName: Option[Name] = parser.optionalName
override def helpMessage: HelpMessage.ParamMessage = parser.helpMessage.addHints(HelpHint.Repeated :: Nil)
override def parseParams(params: List[Arg.ParamLike]): Params.ParseResult[Option[A]] = parser.parseParams(params) match
case Params.ParseResult.Success(value, parsed, remaining) => Params.ParseResult.Success(value.some, parsed, remaining)
case Params.ParseResult.Fail(error, _) if error.onlyContainsMissingRequiredParam => Params.ParseResult.Success(None, Nil, params)
case fail @ Params.ParseResult.Fail(_, _) => fail
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Params[Option[A]])] =
parser.buildInternal(usedParams).map { case (up2, p) => (up2, Optional(p)) }
}
final case class Repeated[A](
parser: Params[A],
) extends Params[List[A]] {
override def optionalName: Option[Name] = parser.optionalName
override def helpMessage: HelpMessage.ParamMessage = parser.helpMessage.addHints(HelpHint.Repeated :: Nil)
@tailrec
private def loop(
params: List[Arg.ParamLike],
parsed: List[ParsedParamArg],
rStack: List[A],
): Params.ParseResult[List[A]] =
parser.parseParams(params) match
case Params.ParseResult.Success(value, parsed2, remaining) if parsed2.nonEmpty => loop(remaining, parsed ::: parsed2, value :: rStack)
case Params.ParseResult.Fail(error, _) if error.onlyContainsMissingRequiredParam => Params.ParseResult.Success(rStack.reverse, parsed, params)
case fail @ Params.ParseResult.Fail(_, _) => fail
case Params.ParseResult.Success(_, _, _) => Params.ParseResult.Success(rStack.reverse, parsed, params)
override def parseParams(params: List[Arg.ParamLike]): Params.ParseResult[List[A]] =
loop(params, Nil, Nil)
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Params[List[A]])] =
parser.buildInternal(usedParams).map { case (up2, p) => (up2, Repeated(p)) }
}
final case class RepeatedNel[A](
parser: Params[A],
) extends Params[NonEmptyList[A]] {
override def optionalName: Option[Name] = parser.optionalName
override def helpMessage: HelpMessage.ParamMessage = parser.helpMessage.addHints(HelpHint.RepeatedNel :: Nil)
@tailrec
private def loop(
params: List[Arg.ParamLike],
parsed: List[ParsedParamArg],
rStack: NonEmptyList[A],
): Params.ParseResult[NonEmptyList[A]] =
parser.parseParams(params) match
case Params.ParseResult.Success(value, parsed2, remaining) if parsed2.nonEmpty => loop(remaining, parsed ::: parsed2, value :: rStack)
case Params.ParseResult.Fail(error, _) if error.onlyContainsMissingRequiredParam => Params.ParseResult.Success(rStack.reverse, parsed, params)
case fail @ Params.ParseResult.Fail(_, _) => fail
case Params.ParseResult.Success(_, _, _) => Params.ParseResult.Success(rStack.reverse, parsed, params)
override def parseParams(params: List[Arg.ParamLike]): Params.ParseResult[NonEmptyList[A]] =
parser.parseParams(params) match
case Params.ParseResult.Success(value, parsed, params) => loop(params, parsed, NonEmptyList.one(value))
case fail @ Params.ParseResult.Fail(_, _) => fail
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Params[NonEmptyList[A]])] =
parser.buildInternal(usedParams).map { case (up2, p) => (up2, RepeatedNel(p)) }
}
final case class WithDefault[A](
parser: Params[A],
default: A,
) extends Params[A] {
override def optionalName: Option[Name] = parser.optionalName
override def helpMessage: HelpMessage.ParamMessage = parser.helpMessage.addHints(HelpHint.Default(default) :: Nil)
override def parseParams(params: List[Arg.ParamLike]): Params.ParseResult[A] =
parser.parseParams(params) match
case success @ Params.ParseResult.Success(_, _, _) => success
case Params.ParseResult.Fail(error, _) if error.onlyContainsMissingRequiredParam => Params.ParseResult.Success(default, Nil, params)
case fail @ Params.ParseResult.Fail(_, _) => fail
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Params[A])] =
parser.buildInternal(usedParams).map { case (up2, p) => (up2, WithDefault(p, default)) }
override def map[B](f: A => B): Params[B] = Params.WithDefault(parser.map(f), f(default))
}
final case class Mapped[A, B](
parser: Params[A],
f: A => B,
) extends Params[B] {
override def optionalName: Option[Name] = parser.optionalName
override def helpMessage: HelpMessage.ParamMessage = parser.helpMessage
override def parseParams(params: List[Arg.ParamLike]): Params.ParseResult[B] =
parser.parseParams(params).map(f)
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Params[B])] =
parser.buildInternal(usedParams).map { case (up2, p) => (up2, Mapped(p, f)) }
override def map[C](f: B => C): Params[C] = Params.Mapped(parser, this.f.andThen(f))
override def mapOrFail[C](f: B => Either[String, C]): Params[C] = Params.MappedOrFail(parser, this.f.andThen(f))
}
final case class MappedOrFail[A, B](
parser: Params[A],
f: A => Either[String, B],
) extends Params[B] {
override def optionalName: Option[Name] = parser.optionalName
override def helpMessage: HelpMessage.ParamMessage = parser.helpMessage
override def parseParams(params: List[Arg.ParamLike]): Params.ParseResult[B] =
parser.parseParams(params).mapOrFail(this)(f)
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Params[B])] =
parser.buildInternal(usedParams).map { case (up2, p) => (up2, MappedOrFail(p, f)) }
override def map[C](f: B => C): Params[C] = Params.MappedOrFail(parser, this.f(_).map(f))
override def mapOrFail[C](f: B => Either[String, C]): Params[C] = Params.MappedOrFail(parser, this.f(_).flatMap(f))
}
final case class And[A, B, O](
left: Params[A],
right: Params[B],
zip: Zip.Out[A, B, O],
) extends Params[O] {
override def optionalName: Option[Name] = None
override def helpMessage: HelpMessage.ParamMessage = HelpMessage.ParamMessage.And(left.helpMessage, right.helpMessage)
override def parseParams(params: List[Arg.ParamLike]): Params.ParseResult[O] =
left.parseParams(params) match {
case Params.ParseResult.Success(a, parsed1, params2) =>
right.parseParams(params2) match {
case Params.ParseResult.Success(b, parsed2, params3) => Params.ParseResult.Success(zip.zip(a, b), parsed1 ::: parsed2, params3)
case fail @ Params.ParseResult.Fail(_, _) => fail
}
case fail1 @ Params.ParseResult.Fail(error1, help1) =>
right.parseParams(params) match {
case Params.ParseResult.Success(_, _, _) => fail1
case Params.ParseResult.Fail(error2, help2) => Params.ParseResult.Fail(ParseError.ParamErrorAnd(error1, error2), HelpMessage.ParamMessage.And(help1, help2))
}
}
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Params[O])] =
for {
(up2, left2) <- left.buildInternal(usedParams)
(up3, right2) <- right.buildInternal(up2)
} yield (up3, And(left2, right2, zip))
}
final case class FirstOfByArgIndex[A](
options: NonEmptyList[Params[A]],
) extends Params[A] {
override def optionalName: Option[Name] = None
override def helpMessage: HelpMessage.ParamMessage = options.map(_.helpMessage).reduceLeft(HelpMessage.ParamMessage.Or(_, _))
override def parseParams(params: List[Arg.ParamLike]): Params.ParseResult[A] = {
val results = options.map(_.parseParams(params))
val (errors, successes) = results.toList.partitionMap {
case success @ Params.ParseResult.Success(_, _, _) => success.asRight
case fail @ Params.ParseResult.Fail(_, _) => fail.asLeft
}
successes match {
case h :: Nil => h
case list @ (_ :: _) => list.minBy(s => ParsedParams(s.parsed.flatMap(_.args)))
case Nil => Params.ParseResult.Fail(errors.map(_._1).reduceLeft(ParseError.ParamErrorOr(_, _)), errors.map(_._2).reduceLeft(HelpMessage.ParamMessage.Or(_, _)))
}
}
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Params[A])] =
options.traverse(_.buildInternal(usedParams)).map { nel => (nel.map(_._1).reduceLeft(_ | _), FirstOfByArgIndex(nel.map(_._2))) }
override def map[B](f: A => B): Params[B] = Params.FirstOfByArgIndex(options.map(_.map(f)))
override def mapOrFail[B](f: A => Either[String, B]): Params[B] = Params.FirstOfByArgIndex(options.map(_.mapOrFail(f)))
}
final case class Or[A](
left: Params[A],
right: Params[A],
) extends Params[A] {
override def optionalName: Option[Name] = None
override def helpMessage: HelpMessage.ParamMessage = HelpMessage.ParamMessage.Or(left.helpMessage, right.helpMessage)
override def parseParams(params: List[Arg.ParamLike]): Params.ParseResult[A] =
left.parseParams(params) match {
case success @ Params.ParseResult.Success(_, _, _) => success
case Params.ParseResult.Fail(error1, help1) =>
right.parseParams(params) match {
case success @ Params.ParseResult.Success(_, _, _) => success
case Params.ParseResult.Fail(error2, help2) => Params.ParseResult.Fail(ParseError.ParamErrorOr(error1, error2), HelpMessage.ParamMessage.Or(help1, help2))
}
}
override def buildInternal(usedParams: Set[SimpleName]): Either[BuildError, (Set[SimpleName], Params[A])] =
for {
(up2, left2) <- left.buildInternal(usedParams)
(up3, right2) <- right.buildInternal(usedParams)
} yield (up2 | up3, Or(left2, right2))
override def map[B](f: A => B): Params[B] = Params.Or(left.map(f), right.map(f))
override def mapOrFail[B](f: A => Either[String, B]): Params[B] = Params.Or(left.mapOrFail(f), right.mapOrFail(f))
}
// =====| |=====
sealed trait ParseResult[+A] {
final def map[B](f: A => B): ParseResult[B] = this match
case ParseResult.Success(value, parsed, remaining) => ParseResult.Success(f(value), parsed, remaining)
case fail @ ParseResult.Fail(_, _) => fail
final def mapOrFail[B](parser: Params[?])(f: A => Either[String, B]): ParseResult[B] = this match {
case ParseResult.Success(value, parsed, remaining) =>
f(value) match {
case Right(value) => ParseResult.Success(value, parsed, remaining)
case Left(error) => ParseResult.Fail(ParseError.SingleParamError(parsed, ParseError.FailedValidation(error)), parser.helpMessage.addHints(HelpHint.Error(error) :: Nil))
}
case fail @ ParseResult.Fail(_, _) => fail
}
final def toFinal: FinalParseResult[A] = this match
case ParseResult.Success(value, parsed, Nil) => FinalParseResult.Success(value, parsed)
case ParseResult.Success(_, _, h :: t) => FinalParseResult.Fail(ParseError.UnparsedParams(NonEmptyList(h, t)), HelpMessage.ParamMessage.UnparsedArgs(NonEmptyList(h, t)))
case ParseResult.Fail(error, help) => FinalParseResult.Fail(error, help)
final def toParserParseResult(values: List[Arg.ValueLike]): Parser.ParseResult[A] = this match
case ParseResult.Success(value, parsed, remaining) => Parser.ParseResult.Success(value, parsed, values, remaining)
case ParseResult.Fail(error, help) => Parser.ParseResult.Fail(error, help)
}
object ParseResult {
final case class Success[+A](value: A, parsed: List[ParsedParamArg], remaining: List[Arg.ParamLike]) extends ParseResult[A]
final case class Fail(error: ParseError.ParamError, help: HelpMessage.ParamMessage) extends ParseResult[Nothing]
}
sealed trait FinalParseResult[+A]
object FinalParseResult {
final case class Success[+A](value: A, parsed: List[ParsedParamArg]) extends FinalParseResult[A]
final case class Fail(error: ParseError.ParamError | ParseError.UnparsedParams, help: HelpMessage.ParamMessage) extends FinalParseResult[Nothing]
}
final case class ParsedParams private (params: List[Arg.ParamLike])
object ParsedParams {
def apply(params: List[Arg.ParamLike]): ParsedParams = new ParsedParams(params.sorted)
implicit val ordering: Ordering[ParsedParams] =
(x: ParsedParams, y: ParsedParams) => {
@tailrec
def loop(
x: List[Arg.ParamLike],
y: List[Arg.ParamLike],
): Int =
(x, y) match {
case (xH :: xT, yH :: yT) =>
Arg.ParamLike.ordering.compare(xH, yH) match {
case 0 => loop(xT, yT)
case c => c
}
case (_ :: _, Nil) => -1
case (Nil, _ :: _) => 1
case (Nil, Nil) => 0
}
loop(x.params, y.params)
}
}
// =====| |=====
def findParamTagged[A](names: Map[SimpleName, A], params: List[Arg.ParamLike]): Option[(Arg.ParamLike, A, List[Arg.ParamLike])] = {
@tailrec
def loop(
queue: List[Arg.ParamLike],
stack: List[Arg.ParamLike],
): Option[(Arg.ParamLike, A, List[Arg.ParamLike])] =
queue match {
case head :: tail =>
names.get(head.name) match {
case Some(tag) => (head, tag, stack.reverse ::: tail).some
case None => loop(tail, head :: stack)
}
case Nil => None
}
loop(params, Nil)
}
def findParam(names: Set[SimpleName], params: List[Arg.ParamLike]): Option[(Arg.ParamLike, List[Arg.ParamLike])] = {
@tailrec
def loop(
queue: List[Arg.ParamLike],
stack: List[Arg.ParamLike],
): Option[(Arg.ParamLike, List[Arg.ParamLike])] =
queue match {
case head :: tail if names.contains(head.name) => (head, stack.reverse ::: tail).some
case head :: tail => loop(tail, head :: stack)
case Nil => None
}
loop(params, Nil)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy