No flags found
Use flags to group coverage reports by test type, project and/or folders.
Then setup custom commit statuses and notifications for each flag.
e.g., #unittest #integration
#production #enterprise
#frontend #backend
5e76e1e
... +0 ...
4d7a553
Use flags to group coverage reports by test type, project and/or folders.
Then setup custom commit statuses and notifications for each flag.
e.g., #unittest #integration
#production #enterprise
#frontend #backend
48 | 48 | private val scalaReader: Reader[ScalaConf] = |
|
49 | 49 | Reader.flagOpt("dir").and( |
|
50 | 50 | Reader.flagOpt("package"), |
|
51 | - | Reader.flagOpt("identifiers").mapTry(_.map(s => IdentifierStrategy.byName(s).toRight(InvalidEnumValue(s, IdentifierStrategy.all.map(_.toString)))).sequence), |
|
51 | + | Reader.flagOpt("identifiers").mapTry((a, p) => a.map(s => IdentifierStrategy.byName(s).toRight(InvalidEnumValue(s, IdentifierStrategy.all.map(_.toString), p))).sequence), |
|
52 | 52 | Reader.flagOpt("config") |
|
53 | 53 | ).map { case (dir, pkg, idf, conf) => ScalaConf(dir, pkg, idf, conf) } |
|
54 | 54 | private val writerReader: Reader[WriterConf] = Reader.flag("output").on { |
|
55 | - | case "scala" => scalaReader |
|
56 | - | case v => Reader.error(s"Unknown output '$v'") |
|
55 | + | case ("scala", _) => scalaReader |
|
56 | + | case (v, p) => Reader.error(s"Unknown output '$v'", p) |
|
57 | 57 | }.map { case (_, w) => w } |
|
58 | 58 | ||
59 | 59 | private val genReader: Reader[GenConf] = |
9 | 9 | ||
10 | 10 | def read(args: Array[String]): Result[A] = read(Params(args)) |
|
11 | 11 | ||
12 | - | def read(args: String): Result[A] = read(args.split(" ").map(_.trim).filter(_.nonEmpty)) |
|
12 | + | def read(args: String): Result[A] = read(Params(args)) |
|
13 | 13 | ||
14 | 14 | def and[B](r: Reader[B]): Reader[(A, B)] = And(this, r) |
|
15 | 15 |
32 | 32 | ||
33 | 33 | def map[B](f: A => B): Reader[B] = Map(this, f) |
|
34 | 34 | ||
35 | - | def mapTry[B](f: A => Either[ArgError, B]): Reader[B] = MapTry(this, f) |
|
35 | + | def mapTry[B](f: (A, Params) => Either[ArgError, B]): Reader[B] = MapTry(this, f) |
|
36 | 36 | ||
37 | - | def validate(p: A => Boolean, err: A => ArgError): Reader[A] = Filter(this, p, err) |
|
37 | + | def validate(p: A => Boolean, err: (A, Params) => ArgError): Reader[A] = Filter(this, p, err) |
|
38 | 38 | ||
39 | - | def validate(p: A => Boolean, validation: Option[String]): Reader[A] = validate(p, ValidationError(_, validation)) |
|
39 | + | def validate(p: A => Boolean, validation: Option[String]): Reader[A] = validate(p, ValidationError(_, validation, _)) |
|
40 | 40 | ||
41 | 41 | def validate(p: A => Boolean, validation: String): Reader[A] = validate(p, Some(validation)) |
|
42 | 42 | ||
43 | 43 | def validate(p: A => Boolean): Reader[A] = validate(p, None) |
|
44 | 44 | ||
45 | - | def inEnum[B >: A](set: Set[B]): Reader[B] = validate(a => set.contains(a), InvalidEnumValue(_, set)) |
|
45 | + | def inEnum[B >: A](set: Set[B]): Reader[B] = validate(set.contains(_), InvalidEnumValue(_, set, _)) |
|
46 | 46 | ||
47 | 47 | def inEnum[B >: A](v: B, o: B*): Reader[B] = inEnum((v :: o.toList).toSet) |
|
48 | 48 | ||
49 | - | def on[B](f: A => Reader[B]): Reader[(A, B)] = Dynamic(this, f) |
|
49 | + | def on[B](f: (A, Params) => Reader[B]): Reader[(A, B)] = Dynamic(this, f) |
|
50 | 50 | } |
|
51 | 51 | ||
52 | 52 | object Reader { |
70 | 70 | ||
71 | 71 | def error[A](errs: ArgError): Error[A] = Error[A](errs) |
|
72 | 72 | ||
73 | - | def error[A](e: String): Error[A] = error(CustomError(e)) |
|
73 | + | def error[A](e: String, p: Params): Error[A] = error(CustomError(e, p)) |
|
74 | 74 | ||
75 | 75 | final case class Arg(pos: Int, name: Option[String], values: Option[Set[String]]) extends Reader[String] { |
|
76 | - | override def read(params: Params): Result[String] = params.read(this).filter(v => values.forall(_.contains(v)), InvalidEnumValue(_, values.getOrElse(Set()))) |
|
76 | + | override def read(params: Params): Result[String] = params.read(this).filter(v => values.forall(_.contains(v)), InvalidEnumValue(_, values.getOrElse(Set()), _)) |
|
77 | 77 | ||
78 | 78 | // ugly, I don't have a better way to mix covariance and String reader |
|
79 | 79 | override def inEnum[B >: String](set: Set[B]): Reader[B] = Arg(pos, name, Some(set.asInstanceOf[Set[String]])) |
108 | 108 | } |
|
109 | 109 | ||
110 | 110 | final case class And[A, B](ra: Reader[A], rb: Reader[B]) extends Reader[(A, B)] { |
|
111 | - | override def read(params: Params): Result[(A, B)] = ra.read(params).chain((_, p) => rb.read(p)) |
|
111 | + | override def read(params: Params): Result[(A, B)] = ra.read(params).and(rb.read) |
|
112 | 112 | } |
|
113 | 113 | ||
114 | 114 | final case class And2[A, B, C](ra: Reader[A], rb: Reader[B], rc: Reader[C]) extends Reader[(A, B, C)] { |
|
115 | - | override def read(params: Params): Result[(A, B, C)] = ra.read(params) |
|
116 | - | .chain((_, p) => rb.read(p)) |
|
117 | - | .chain((_, p) => rc.read(p)).map { case ((a, b), c) => (a, b, c) } |
|
115 | + | override def read(params: Params): Result[(A, B, C)] = ra.read(params).and(rb.read).and(rc.read).map { case ((a, b), c) => (a, b, c) } |
|
118 | 116 | } |
|
119 | 117 | ||
120 | 118 | final case class And3[A, B, C, D](ra: Reader[A], rb: Reader[B], rc: Reader[C], rd: Reader[D]) extends Reader[(A, B, C, D)] { |
|
121 | - | override def read(params: Params): Result[(A, B, C, D)] = ra.read(params) |
|
122 | - | .chain((_, p) => rb.read(p)) |
|
123 | - | .chain((_, p) => rc.read(p)) |
|
124 | - | .chain((_, p) => rd.read(p)).map { case (((a, b), c), d) => (a, b, c, d) } |
|
119 | + | override def read(params: Params): Result[(A, B, C, D)] = ra.read(params).and(rb.read).and(rc.read).and(rd.read).map { case (((a, b), c), d) => (a, b, c, d) } |
|
125 | 120 | } |
|
126 | 121 | ||
127 | 122 | final case class Or[A](r1: Reader[A], r2: Reader[A]) extends Reader[A] { |
140 | 135 | override def read(params: Params): Result[B] = r.read(params).map(f) |
|
141 | 136 | } |
|
142 | 137 | ||
143 | - | final case class MapTry[A, B](r: Reader[A], f: A => Either[ArgError, B]) extends Reader[B] { |
|
144 | - | override def read(params: Params): Result[B] = r.read(params).flatMap((a, p) => Result.from(f(a), p)) |
|
138 | + | final case class MapTry[A, B](r: Reader[A], f: (A, Params) => Either[ArgError, B]) extends Reader[B] { |
|
139 | + | override def read(params: Params): Result[B] = r.read(params).flatMap((a, p) => f(a, p).fold(Result(_), Result(_, p))) |
|
145 | 140 | } |
|
146 | 141 | ||
147 | - | final case class Filter[A](r: Reader[A], f: A => Boolean, e: A => ArgError) extends Reader[A] { |
|
142 | + | final case class Filter[A](r: Reader[A], f: A => Boolean, e: (A, Params) => ArgError) extends Reader[A] { |
|
148 | 143 | override def read(params: Params): Result[A] = r.read(params).filter(f, e) |
|
149 | 144 | } |
|
150 | 145 | ||
151 | - | final case class Dynamic[A, B](r: Reader[A], f: A => Reader[B]) extends Reader[(A, B)] { |
|
152 | - | override def read(params: Params): Result[(A, B)] = r.read(params).chain((a, p) => f(a).read(p)) |
|
146 | + | final case class Dynamic[A, B](r: Reader[A], f: (A, Params) => Reader[B]) extends Reader[(A, B)] { |
|
147 | + | override def read(params: Params): Result[(A, B)] = r.read(params).chain((a, p) => f(a, p).read(p)) |
|
153 | 148 | } |
|
154 | 149 | ||
155 | 150 | final case class Error[A](errs: ArgError) extends Reader[A] { |
|
156 | - | override def read(params: Params): Result[A] = Result.Failure(errs, params) |
|
151 | + | override def read(params: Params): Result[A] = Result(errs) |
|
157 | 152 | } |
|
158 | 153 | ||
159 | 154 | } |
4 | 4 | import fr.loicknuchel.scalargs.ArgError._ |
|
5 | 5 | import fr.loicknuchel.scalargs.Reader.{Arg, ArgOpt, Flag, FlagBool, FlagCheck, FlagList, FlagNel, FlagOpt} |
|
6 | 6 | ||
7 | + | // TODO alternative names: Args, Opts, Ctx |
|
7 | 8 | case class Params(args: List[(Int, String)], flags: Map[String, List[String]], readArgs: Set[Int], readFlags: Set[String]) { |
|
8 | - | private def addArg(arg: String): Params = copy(args = args :+ (args.length -> arg)) |
|
9 | - | ||
10 | - | private def addFlag(flag: String): Params = copy(flags = flags + (flag -> flags.getOrElse(flag, List()))) |
|
9 | + | def size: Int = readArgs.size + readFlags.size |
|
11 | 10 | ||
12 | - | private def addFlagValue(flag: String, value: String): Params = copy(flags = flags + (flag -> (flags.getOrElse(flag, List()) :+ value))) |
|
11 | + | def nonEmpty: Boolean = readArgs.nonEmpty || readFlags.nonEmpty |
|
13 | 12 | ||
14 | - | def read(a: Arg): Result[String] = arg(a.pos, a.name).flatMap { |
|
15 | - | case (None, p) => Result.Failure(ArgumentNotFound(a.pos, a.name, a.values), p) |
|
16 | - | case (Some(v), p) => Result.Success(v, p) |
|
13 | + | def read(a: Arg): Result[String] = readArg(a.pos, a.name).flatMap { |
|
14 | + | case (None, _) => Result(ArgumentNotFound(a.pos, a.name, a.values, this)) |
|
15 | + | case (Some(v), p) => Result(v, p) |
|
17 | 16 | } |
|
18 | 17 | ||
19 | - | def read(a: ArgOpt): Result[Option[String]] = arg(a.pos, a.name) |
|
18 | + | def read(a: ArgOpt): Result[Option[String]] = readArg(a.pos, a.name) |
|
20 | 19 | ||
21 | - | def read(f: Flag): Result[String] = flag(f.name).flatMap { |
|
22 | - | case (None, p) => Result.Failure(FlagNotFound(f.name), p) |
|
23 | - | case (Some(List()), p) => Result.Failure(NoFlagValue(f.name), p) |
|
24 | - | case (Some(List(v)), p) => Result.Success(v, p) |
|
25 | - | case (Some(l), p) => Result.Failure(UniqueFlagHasMultipleValues(f.name, l), p) |
|
20 | + | def read(f: Flag): Result[String] = readFlag(f.name).flatMap { |
|
21 | + | case (None, _) => Result(FlagNotFound(f.name, this)) |
|
22 | + | case (Some(List()), _) => Result(NoFlagValue(f.name, this)) |
|
23 | + | case (Some(List(v)), p) => Result(v, p) |
|
24 | + | case (Some(l), _) => Result(UniqueFlagHasMultipleValues(f.name, l, this)) |
|
26 | 25 | } |
|
27 | 26 | ||
28 | - | def read(f: FlagOpt): Result[Option[String]] = flag(f.name).flatMap { |
|
29 | - | case (None, p) => Result.Success(None, p) |
|
30 | - | case (Some(List()), p) => Result.Failure(NoFlagValue(f.name), p) |
|
31 | - | case (Some(List(v)), p) => Result.Success(Some(v), p) |
|
32 | - | case (Some(l), p) => Result.Failure(UniqueFlagHasMultipleValues(f.name, l), p) |
|
27 | + | def read(f: FlagOpt): Result[Option[String]] = readFlag(f.name).flatMap { |
|
28 | + | case (None, p) => Result(None, p) |
|
29 | + | case (Some(List()), _) => Result(NoFlagValue(f.name, this)) |
|
30 | + | case (Some(List(v)), p) => Result(Some(v), p) |
|
31 | + | case (Some(l), _) => Result(UniqueFlagHasMultipleValues(f.name, l, this)) |
|
33 | 32 | } |
|
34 | 33 | ||
35 | - | def read(f: FlagList): Result[List[String]] = flag(f.name).flatMap { |
|
36 | - | case (Some(l), p) => Result.Success(l, p) |
|
37 | - | case (None, p) => Result.Failure(FlagNotFound(f.name), p) |
|
34 | + | def read(f: FlagList): Result[List[String]] = readFlag(f.name).flatMap { |
|
35 | + | case (Some(l), p) => Result(l, p) |
|
36 | + | case (None, _) => Result(FlagNotFound(f.name, this)) |
|
38 | 37 | } |
|
39 | 38 | ||
40 | - | def read(f: FlagNel): Result[NonEmptyList[String]] = flag(f.name).flatMap { |
|
41 | - | case (None, p) => Result.Failure(FlagNotFound(f.name), p) |
|
42 | - | case (Some(List()), p) => Result.Failure(NoFlagValue(f.name), p) |
|
43 | - | case (Some(List(v)), p) => Result.Success(NonEmptyList.of(v), p) |
|
44 | - | case (Some(l), p) => Result.Success(NonEmptyList.fromListUnsafe(l), p) |
|
39 | + | def read(f: FlagNel): Result[NonEmptyList[String]] = readFlag(f.name).flatMap { |
|
40 | + | case (None, _) => Result(FlagNotFound(f.name, this)) |
|
41 | + | case (Some(List()), _) => Result(NoFlagValue(f.name, this)) |
|
42 | + | case (Some(List(v)), p) => Result(NonEmptyList.of(v), p) |
|
43 | + | case (Some(l), p) => Result(NonEmptyList.fromListUnsafe(l), p) |
|
45 | 44 | } |
|
46 | 45 | ||
47 | - | def read(f: FlagBool): Result[Boolean] = flag(f.name).flatMap { |
|
48 | - | case (None, p) => Result.Success(false, p) |
|
49 | - | case (Some(List()), p) => Result.Success(true, p) |
|
50 | - | case (Some(List(v)), p) => Result.Failure(FlagHasValue(f.name, v), p) |
|
51 | - | case (Some(l), p) => Result.Failure(EmptyFlagHasMultipleValues(f.name, l), p) |
|
46 | + | def read(f: FlagBool): Result[Boolean] = readFlag(f.name).flatMap { |
|
47 | + | case (None, p) => Result(false, p) |
|
48 | + | case (Some(List()), p) => Result(true, p) |
|
49 | + | case (Some(List(v)), _) => Result(FlagHasValue(f.name, v, this)) |
|
50 | + | case (Some(l), _) => Result(EmptyFlagHasMultipleValues(f.name, l, this)) |
|
52 | 51 | } |
|
53 | 52 | ||
54 | - | def read(f: FlagCheck): Result[Unit] = flag(f.name).flatMap { |
|
55 | - | case (None, p) => Result.Failure(FlagNotFound(f.name), p) |
|
56 | - | case (Some(List()), p) => Result.Success((), p) |
|
57 | - | case (Some(List(v)), p) => Result.Failure(FlagHasValue(f.name, v), p) |
|
58 | - | case (Some(l), p) => Result.Failure(EmptyFlagHasMultipleValues(f.name, l), p) |
|
53 | + | def read(f: FlagCheck): Result[Unit] = readFlag(f.name).flatMap { |
|
54 | + | case (None, _) => Result(FlagNotFound(f.name, this)) |
|
55 | + | case (Some(List()), p) => Result((), p) |
|
56 | + | case (Some(List(v)), _) => Result(FlagHasValue(f.name, v, this)) |
|
57 | + | case (Some(l), _) => Result(EmptyFlagHasMultipleValues(f.name, l, this)) |
|
59 | 58 | } |
|
60 | 59 | ||
61 | - | private def arg(pos: Int, name: Option[String]): Result[Option[String]] = |
|
62 | - | if (readArgs.contains(pos)) Result.Failure(ArgumentReadTwice(pos, name), this) |
|
63 | - | else Result.Success(args.find(_._1 == pos).map(_._2), copy(readArgs = readArgs + pos)) |
|
60 | + | def arg(a: Int*): Params = copy(readArgs = readArgs ++ a) // only for tests |
|
61 | + | def flag(f: String*): Params = copy(readFlags = readFlags ++ f) // only for tests |
|
62 | + | ||
63 | + | private def readArg(pos: Int, name: Option[String]): Result[Option[String]] = |
|
64 | + | if (readArgs.contains(pos)) Result(ArgumentReadTwice(pos, name, this)) |
|
65 | + | else Result(args.find(_._1 == pos).map(_._2), arg(pos)) |
|
66 | + | ||
67 | + | private def readFlag(name: String): Result[Option[List[String]]] = |
|
68 | + | if (readFlags.contains(name)) Result(FlagReadTwice(name, this)) |
|
69 | + | else Result(flags.get(name), flag(name)) |
|
64 | 70 | ||
65 | - | private def flag(name: String): Result[Option[List[String]]] = |
|
66 | - | if (readFlags.contains(name)) Result.Failure(FlagReadTwice(name), this) |
|
67 | - | else Result.Success(flags.get(name), copy(readFlags = readFlags + name)) |
|
71 | + | private def addArg(arg: String): Params = copy(args = args :+ (args.length -> arg)) |
|
72 | + | ||
73 | + | private def addFlag(flag: String): Params = copy(flags = flags + (flag -> flags.getOrElse(flag, List()))) |
|
74 | + | ||
75 | + | private def addFlagValue(flag: String, value: String): Params = copy(flags = flags + (flag -> (flags.getOrElse(flag, List()) :+ value))) |
|
68 | 76 | } |
|
69 | 77 | ||
70 | 78 | object Params { |
84 | 92 | }._2 |
|
85 | 93 | } |
|
86 | 94 | ||
87 | - | def apply(args: String): Params = Params(args.split(" ")) |
|
95 | + | def apply(args: String): Params = Params(args.split(" ").map(_.trim).filter(_.nonEmpty)) |
|
88 | 96 | } |
1 | 1 | package fr.loicknuchel.scalargs |
|
2 | 2 | ||
3 | + | import cats.data.NonEmptyList |
|
4 | + | ||
3 | 5 | sealed trait ArgError { |
|
6 | + | val params: Params |
|
7 | + | ||
4 | 8 | def getMessage: String |
|
5 | 9 | } |
|
6 | 10 |
11 | 15 | } |
|
12 | 16 | ||
13 | 17 | final case class ArgumentNotFound(pos: Int, |
|
14 | - | name: Option[String] = None, |
|
15 | - | values: Option[Set[String]] = None) extends ArgError { |
|
16 | - | override def getMessage: String = s"Missing argument ${pos + 1}${name.map(n => s" ($n${valuesMessage.map(m => s", $m").getOrElse("")})").getOrElse(valuesMessage.map(m => s" ($m)").getOrElse(""))}" |
|
18 | + | name: Option[String], |
|
19 | + | values: Option[Set[String]], |
|
20 | + | params: Params) extends ArgError { |
|
21 | + | override def getMessage: String = s"Missing ${argName(pos, name)}${possibleValues(values)}" |
|
22 | + | } |
|
23 | + | ||
24 | + | object ArgumentNotFound { |
|
25 | + | def apply(pos: Int, params: Params): ArgumentNotFound = new ArgumentNotFound(pos, None, None, params) |
|
26 | + | ||
27 | + | def apply(pos: Int, name: String, params: Params): ArgumentNotFound = new ArgumentNotFound(pos, Some(name), None, params) |
|
17 | 28 | ||
18 | - | private def valuesMessage: Option[String] = values.map(v => s"possible values: ${v.mkString(", ")}") |
|
29 | + | def apply(pos: Int, values: Set[String], params: Params): ArgumentNotFound = new ArgumentNotFound(pos, None, Some(values), params) |
|
30 | + | ||
31 | + | def apply(pos: Int, name: String, values: Set[String], params: Params): ArgumentNotFound = new ArgumentNotFound(pos, Some(name), Some(values), params) |
|
19 | 32 | } |
|
20 | 33 | ||
21 | - | final case class ArgumentReadTwice(pos: Int, name: Option[String] = None) extends BasicArgError(s"Argument ${pos + 1}${name.map(n => s" ($n)").getOrElse("")} already read") |
|
34 | + | final case class FlagNotFound(name: String, params: Params) extends BasicArgError(s"Missing ${flagName(name)}") |
|
35 | + | ||
36 | + | final case class ArgumentReadTwice(pos: Int, name: Option[String], params: Params) extends BasicArgError(s"${argName(pos, name).capitalize} should not be used twice") |
|
22 | 37 | ||
23 | - | final case class FlagNotFound(name: String) extends BasicArgError(s"Missing flag --$name") |
|
38 | + | object ArgumentReadTwice { |
|
39 | + | def apply(pos: Int, params: Params): ArgumentReadTwice = new ArgumentReadTwice(pos, None, params) |
|
40 | + | } |
|
24 | 41 | ||
25 | - | final case class FlagReadTwice(name: String) extends BasicArgError(s"Flag --$name already read") |
|
42 | + | final case class FlagReadTwice(name: String, params: Params) extends BasicArgError(s"${flagName(name)} should not be used twice") |
|
26 | 43 | ||
27 | - | final case class NoFlagValue(name: String) extends BasicArgError(s"Flag --$name is present but has no value") |
|
44 | + | final case class NoFlagValue(name: String, params: Params) extends BasicArgError(s"Missing a value for ${flagName(name)}") |
|
28 | 45 | ||
29 | - | final case class FlagHasValue(name: String, value: String) extends BasicArgError(s"Flag --$name has '$value' as value but expects no value") |
|
46 | + | final case class FlagHasValue(name: String, value: String, params: Params) extends BasicArgError(s"${flagName(name)} has '$value' as value but expects no value") |
|
30 | 47 | ||
31 | - | final case class EmptyFlagHasMultipleValues(name: String, values: List[String]) extends BasicArgError(s"Flag --$name has multiple values (${values.mkString(", ")}) but expects none") |
|
48 | + | final case class EmptyFlagHasMultipleValues(name: String, values: List[String], params: Params) extends BasicArgError(s"${flagName(name)} has multiple values (${values.mkString(", ")}) but expects none") |
|
32 | 49 | ||
33 | - | final case class UniqueFlagHasMultipleValues(name: String, values: List[String]) extends BasicArgError(s"Flag --$name has multiple values (${values.mkString(", ")}) but expects one") |
|
50 | + | final case class UniqueFlagHasMultipleValues(name: String, values: List[String], params: Params) extends BasicArgError(s"${flagName(name)} has multiple values (${values.mkString(", ")}) but expects only one") |
|
34 | 51 | ||
35 | - | final case class ValidationError[A](value: A, validation: Option[String] = None) extends BasicArgError(validation.map(_ + s" (value: $value)").getOrElse(s"Value '$value' did not pass validation")) |
|
52 | + | final case class ValidationError[A](value: A, validation: Option[String], params: Params) extends BasicArgError(validation.map(_ + s" (value: $value)").getOrElse(s"Value '$value' did not pass validation")) |
|
36 | 53 | ||
37 | - | final case class InvalidEnumValue[A](value: A, values: Set[A]) extends BasicArgError(s"Value '$value' is not in the allowed values: ${values.mkString(", ")}") |
|
54 | + | object ValidationError { |
|
55 | + | def apply[A](value: A, params: Params): ValidationError[A] = new ValidationError(value, None, params) |
|
38 | 56 | ||
39 | - | final case class NoValidAlternative(e1: ArgError, e2: ArgError, tail: List[ArgError] = List()) extends BasicArgError(s"Invalid arguments, here are your options (fix the error you want):${(e1 :: e2 :: tail).map("\n - " + _.getMessage).mkString}") |
|
57 | + | def apply[A](value: A, validation: String, params: Params): ValidationError[A] = new ValidationError(value, Some(validation), params) |
|
58 | + | } |
|
59 | + | ||
60 | + | final case class InvalidEnumValue[A](value: A, values: Set[A], params: Params) extends BasicArgError(s"Value '$value' is not allowed${possibleValues(Some(values))}") |
|
61 | + | ||
62 | + | // TODO replace InvalidEnumValue once a Reader can differentiate arg & flag |
|
63 | + | // final case class InvalidEnumValueForArgument[A](pos: Int, name: Option[String], value: A, values: Set[A], params: Params) extends BasicArgError(s"Value '$value' is not allowed for ${argName(pos, name)}${possibleValues(Some(values))}") |
|
64 | + | // final case class InvalidEnumValueForFlag[A](name: String, value: A, values: Set[A], params: Params) extends BasicArgError(s"Value '$value' is not allowed for ${flagName(name)}${possibleValues(Some(values))}") |
|
65 | + | ||
66 | + | final case class NoValidAlternative(e1: ArgError, e2: ArgError, tail: List[ArgError], params: Params) extends ArgError { |
|
67 | + | override def getMessage: String = getRelevantErrors match { |
|
68 | + | case NonEmptyList(e, List()) => e.getMessage |
|
69 | + | case errs => s"Invalid arguments, here are your options (fix the error you want):${errs.map(e => "\n" + addIndentation("- " + e.getMessage)).toList.mkString}" |
|
70 | + | } |
|
71 | + | ||
72 | + | private def getRelevantErrors: NonEmptyList[ArgError] = { |
|
73 | + | // keep only deepest errors (most relevant) |
|
74 | + | val all = NonEmptyList.of(e1, e2) ++ tail |
|
75 | + | val max = all.toList.map(_.params.size).max |
|
76 | + | NonEmptyList.fromListUnsafe(all.filter(_.params.size == max)) // it's safe because max is always present somewhere |
|
77 | + | } |
|
78 | + | ||
79 | + | private def addIndentation(text: String): String = text.split("\n").map(" " + _).mkString("\n") |
|
80 | + | } |
|
40 | 81 | ||
41 | 82 | object NoValidAlternative { |
|
42 | - | def apply(e1: ArgError, e2: ArgError, others: ArgError*): ArgError = NoValidAlternative(e1, e2, others.toList) |
|
83 | + | def apply(e1: ArgError, e2: ArgError, others: ArgError*): ArgError = NoValidAlternative(e1, e2, others.toList, (e1 :: e2 :: others.toList).map(_.params).maxBy(_.size)) |
|
43 | 84 | } |
|
44 | 85 | ||
45 | - | final case class CustomError(value: String) extends BasicArgError(value) |
|
86 | + | final case class CustomError(value: String, params: Params) extends BasicArgError(value) |
|
87 | + | ||
88 | + | private def argName(pos: Int, name: Option[String]): String = s"argument ${pos + 1}${name.map(n => s" ($n)").getOrElse("")}" |
|
89 | + | ||
90 | + | private def flagName(name: String): String = s"--$name flag" |
|
46 | 91 | ||
92 | + | private def possibleValues[A](values: Option[Set[A]]): String = values.map(v => s", possible values: ${v.mkString(", ")}").getOrElse("") |
|
47 | 93 | } |
9 | 9 | ||
10 | 10 | def flatMap[B](f: (A, Params) => Result[B]): Result[B] |
|
11 | 11 | ||
12 | - | // like `flatMap` but keep the previous result |
|
12 | + | def and[B](f: Params => Result[B]): Result[(A, B)] = flatMap((a, p) => f(p).map(b => (a, b))) |
|
13 | + | ||
13 | 14 | def chain[B](f: (A, Params) => Result[B]): Result[(A, B)] = flatMap((a, p) => f(a, p).map(b => (a, b))) |
|
14 | 15 | ||
15 | - | def filter(p: A => Boolean, e: A => ArgError): Result[A] |
|
16 | + | def filter(p: A => Boolean, e: (A, Params) => ArgError): Result[A] |
|
16 | 17 | ||
17 | - | def filter(p: A => Boolean, validation: Option[String]): Result[A] = filter(p, a => ValidationError(a, validation)) |
|
18 | + | def filter(p: A => Boolean, validation: Option[String]): Result[A] = filter(p, ValidationError(_, validation, _)) |
|
18 | 19 | ||
19 | 20 | def filter(p: A => Boolean, validation: String): Result[A] = filter(p, Some(validation)) |
|
20 | 21 |
26 | 27 | ||
27 | 28 | def orElse[B >: A](r1: => Result[B], r2: => Result[B], r3: => Result[B]): Result[B] |
|
28 | 29 | ||
29 | - | def get: Option[A] |
|
30 | - | ||
31 | - | def err: Option[ArgError] |
|
32 | - | ||
33 | 30 | def toEither: Either[ArgError, A] |
|
34 | 31 | } |
|
35 | 32 | ||
36 | 33 | object Result { |
|
37 | 34 | ||
38 | - | def apply[A](value: A, params: Params): Result[A] = Success(value, params) |
|
35 | + | def apply[A](value: A, params: Params): Success[A] = Success(value, params) |
|
39 | 36 | ||
40 | - | def fail[A](e: ArgError, params: Params): Result[A] = Failure(e, params) |
|
41 | - | ||
42 | - | def from[A](e: Either[ArgError, A], params: Params): Result[A] = e.fold(Result.Failure(_, params), Result.Success(_, params)) |
|
37 | + | def apply(error: ArgError): Failure = Failure(error) |
|
43 | 38 | ||
44 | 39 | case class Success[A](value: A, params: Params) extends Result[A] { |
|
45 | 40 | override def map[B](f: A => B): Result[B] = Success(f(value), params) |
|
46 | 41 | ||
47 | 42 | override def flatMap[B](f: (A, Params) => Result[B]): Result[B] = f(value, params) |
|
48 | 43 | ||
49 | - | override def filter(p: A => Boolean, e: A => ArgError): Result[A] = if (p(value)) this else Failure(e(value), params) |
|
44 | + | override def filter(p: A => Boolean, e: (A, Params) => ArgError): Result[A] = if (p(value)) this else Failure(e(value, params)) |
|
50 | 45 | ||
51 | 46 | override def orElse[B >: A](r: => Result[B]): Result[B] = this |
|
52 | 47 | ||
53 | 48 | override def orElse[B >: A](r1: => Result[B], r2: => Result[B]): Result[B] = this |
|
54 | 49 | ||
55 | 50 | override def orElse[B >: A](r1: => Result[B], r2: => Result[B], r3: => Result[B]): Result[B] = this |
|
56 | 51 | ||
57 | - | override def get: Option[A] = Some(value) |
|
58 | - | ||
59 | - | override def err: Option[ArgError] = None |
|
60 | - | ||
61 | 52 | override def toEither: Either[ArgError, A] = Right(value) |
|
62 | 53 | } |
|
63 | 54 | ||
64 | - | case class Failure(error: ArgError, params: Params) extends Result[Nothing] { |
|
55 | + | case class Failure(error: ArgError) extends Result[Nothing] { |
|
56 | + | val params: Params = error.params |
|
57 | + | ||
65 | 58 | override def map[B](f: Nothing => B): Result[B] = this |
|
66 | 59 | ||
67 | 60 | override def flatMap[B](f: (Nothing, Params) => Result[B]): Result[B] = this |
|
68 | 61 | ||
69 | - | override def filter(p: Nothing => Boolean, e: Nothing => ArgError): Result[Nothing] = this |
|
62 | + | override def filter(p: Nothing => Boolean, e: (Nothing, Params) => ArgError): Result[Nothing] = this |
|
70 | 63 | ||
71 | 64 | override def orElse[B >: Nothing](r: => Result[B]): Result[B] = r match { |
|
72 | 65 | case res: Success[_] => res |
|
73 | - | case Result.Failure(e, _) => Result.Failure(NoValidAlternative(error, e), params) |
|
66 | + | case Failure(e) => Result(NoValidAlternative(error, e)) |
|
74 | 67 | } |
|
75 | 68 | ||
76 | 69 | override def orElse[B >: Nothing](r1: => Result[B], r2: => Result[B]): Result[B] = r1 match { |
|
77 | 70 | case res: Success[_] => res |
|
78 | - | case Result.Failure(e1, _) => r2 match { |
|
71 | + | case Failure(e1) => r2 match { |
|
79 | 72 | case res: Success[_] => res |
|
80 | - | case Result.Failure(e2, _) => Result.Failure(NoValidAlternative(error, e1, List(e2)), params) |
|
73 | + | case Failure(e2) => Result(NoValidAlternative(error, e1, e2)) |
|
81 | 74 | } |
|
82 | 75 | } |
|
83 | 76 | ||
84 | 77 | override def orElse[B >: Nothing](r1: => Result[B], r2: => Result[B], r3: => Result[B]): Result[B] = r1 match { |
|
85 | 78 | case res: Success[_] => res |
|
86 | - | case Result.Failure(e1, _) => r2 match { |
|
79 | + | case Failure(e1) => r2 match { |
|
87 | 80 | case res: Success[_] => res |
|
88 | - | case Result.Failure(e2, _) => r3 match { |
|
81 | + | case Failure(e2) => r3 match { |
|
89 | 82 | case res: Success[_] => res |
|
90 | - | case Result.Failure(e3, _) => Result.Failure(NoValidAlternative(error, e1, List(e2, e3)), params) |
|
83 | + | case Failure(e3) => Result(NoValidAlternative(error, e1, e2, e3)) |
|
91 | 84 | } |
|
92 | 85 | } |
|
93 | 86 | } |
|
94 | 87 | ||
95 | - | override def get: Option[Nothing] = None |
|
96 | - | ||
97 | - | override def err: Option[ArgError] = Some(error) |
|
98 | - | ||
99 | 88 | override def toEither: Either[ArgError, Nothing] = Left(error) |
|
100 | 89 | } |
|
101 | 90 |
Files | Coverage |
---|---|
src/main/scala/fr/loicknuchel | 0.37% 97.43% |
Project Totals (23 files) | 97.43% |
4d7a553
5e76e1e