Other files ignored by Codecov
Showing 5 of 13 files from the diff.
@@ -48,12 +48,12 @@
Loading
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] = |
@@ -4,67 +4,75 @@
Loading
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,5 +92,5 @@
Loading
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 | } |
@@ -9,7 +9,7 @@
Loading
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,21 +32,21 @@
Loading
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,10 +70,10 @@
Loading
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,20 +108,15 @@
Loading
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,20 +135,20 @@
Loading
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 | } |
@@ -1,6 +1,10 @@
Loading
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,37 +15,79 @@
Loading
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,12 +9,13 @@
Loading
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,21 @@
Loading
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 |
@@ -54,48 +49,42 @@
Loading
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 | 97.43% |
Project Totals (23 files) | 97.43% |
35.5
openjdk11= TRAVIS_OS_NAME=linux
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file.
The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files.
The size and color of each slice is representing the number of statements and the coverage, respectively.