Add coursier.util.ValidationNel

This commit is contained in:
Alexandre Archambault 2018-02-27 22:54:48 +01:00
parent dc87950dc4
commit a83df3e1c8
6 changed files with 145 additions and 87 deletions

View File

@ -4,19 +4,16 @@ import java.net.MalformedURLException
import coursier.core.Authentication
import coursier.ivy.IvyRepository
import coursier.util.Parse
import scalaz.{Validation, ValidationNel}
import scalaz.Scalaz.vectorInstance
import scalaz.Scalaz.{ToEitherOpsFromEither, ToNelOps, ToTraverseOps, ToValidationOps}
import coursier.util.{Parse, ValidationNel}
import coursier.util.Traverse.TraverseOps
object CacheParse {
def repository(s: String): Validation[String, Repository] =
def repository(s: String): Either[String, Repository] =
if (s == "ivy2local" || s == "ivy2Local")
Cache.ivy2Local.success
Right(Cache.ivy2Local)
else if (s == "ivy2cache" || s == "ivy2Cache")
Cache.ivy2Cache.success
Right(Cache.ivy2Cache)
else {
val repo = Parse.repository(s)
@ -78,34 +75,38 @@ object CacheParse {
Left(s"No password found in user info of URL $url")
}
}
}.validation
}
}
def repositories(l: Seq[String]): ValidationNel[String, Seq[Repository]] =
l.toVector.traverseU { s =>
repository(s).leftMap(_.wrapNel)
l.toVector.validationNelTraverse { s =>
ValidationNel.fromEither(repository(s))
}
def cachePolicies(s: String): ValidationNel[String, Seq[CachePolicy]] =
s.split(',').toVector.traverseM[({ type L[X] = ValidationNel[String, X] })#L, CachePolicy] {
case "offline" =>
Vector(CachePolicy.LocalOnly).successNel
case "update-local-changing" =>
Vector(CachePolicy.LocalUpdateChanging).successNel
case "update-local" =>
Vector(CachePolicy.LocalUpdate).successNel
case "update-changing" =>
Vector(CachePolicy.UpdateChanging).successNel
case "update" =>
Vector(CachePolicy.Update).successNel
case "missing" =>
Vector(CachePolicy.FetchMissing).successNel
case "force" =>
Vector(CachePolicy.ForceDownload).successNel
case "default" =>
Vector(CachePolicy.LocalOnly, CachePolicy.FetchMissing).successNel
case other =>
s"Unrecognized mode: $other".failureNel
}
s
.split(',')
.toVector
.validationNelTraverse[String, Seq[CachePolicy]] {
case "offline" =>
ValidationNel.success(Seq(CachePolicy.LocalOnly))
case "update-local-changing" =>
ValidationNel.success(Seq(CachePolicy.LocalUpdateChanging))
case "update-local" =>
ValidationNel.success(Seq(CachePolicy.LocalUpdate))
case "update-changing" =>
ValidationNel.success(Seq(CachePolicy.UpdateChanging))
case "update" =>
ValidationNel.success(Seq(CachePolicy.Update))
case "missing" =>
ValidationNel.success(Seq(CachePolicy.FetchMissing))
case "force" =>
ValidationNel.success(Seq(CachePolicy.ForceDownload))
case "default" =>
ValidationNel.success(Seq(CachePolicy.LocalOnly, CachePolicy.FetchMissing))
case other =>
ValidationNel.failure(s"Unrecognized mode: $other")
}
.map(_.flatten)
}

View File

@ -1,7 +1,5 @@
package coursier
import scalaz.{Failure, Success}
sealed abstract class CachePolicy extends Product with Serializable
object CachePolicy {
@ -81,15 +79,15 @@ object CachePolicy {
def fromOption(value: Option[String], description: String): Option[Seq[CachePolicy]] =
value.filter(_.nonEmpty).flatMap {
str =>
CacheParse.cachePolicies(str) match {
case Success(Seq()) =>
CacheParse.cachePolicies(str).either match {
case Right(Seq()) =>
Console.err.println(
s"Warning: no mode found in $description, ignoring it."
)
None
case Success(policies) =>
case Right(policies) =>
Some(policies)
case Failure(errors) =>
case Left(_) =>
Console.err.println(
s"Warning: unrecognized mode in $description, ignoring it."
)

View File

@ -17,7 +17,7 @@ import coursier.util.{Parse, Print}
import scala.annotation.tailrec
import scala.concurrent.duration.Duration
import scalaz.concurrent.{Strategy, Task}
import scalaz.{Failure, Nondeterminism, Success}
import scalaz.Nondeterminism
object Helper {
@ -81,11 +81,11 @@ class Helper(
if (common.mode.isEmpty)
CachePolicy.default
else
CacheParse.cachePolicies(common.mode) match {
case Success(cp) => cp
case Failure(errors) =>
CacheParse.cachePolicies(common.mode).either match {
case Right(cp) => cp
case Left(errors) =>
prematureExit(
s"Error parsing modes:\n${errors.list.toList.map(" "+_).mkString("\n")}"
s"Error parsing modes:\n${errors.map(" "+_).mkString("\n")}"
)
}
@ -116,12 +116,12 @@ class Helper(
repos
}
val standardRepositories = repositoriesValidation match {
case Success(repos) =>
val standardRepositories = repositoriesValidation.either match {
case Right(repos) =>
repos
case Failure(errors) =>
case Left(errors) =>
prematureExit(
s"Error with repositories:\n${errors.list.toList.map(" "+_).mkString("\n")}"
s"Error with repositories:\n${errors.map(" "+_).mkString("\n")}"
)
}

View File

@ -1,12 +1,11 @@
package coursier.ivy
import scala.language.implicitConversions
import scalaz.{Failure, Success, ValidationNel}
import scalaz.Scalaz.{ToEitherOpsFromEither, ToFoldableOps, ToTraverseOps, ToValidationOps, vectorInstance}
import coursier.util.Traverse.TraverseOps
import coursier.util.ValidationNel
import fastparse.all._
import scala.language.implicitConversions
final case class PropertiesPattern(chunks: Seq[PropertiesPattern.ChunkOrProperty]) {
def string: String = chunks.map(_.string).mkString
@ -15,43 +14,43 @@ final case class PropertiesPattern(chunks: Seq[PropertiesPattern.ChunkOrProperty
def substituteProperties(properties: Map[String, String]): Either[String, Pattern] = {
val validation = chunks.toVector.traverseM[({ type L[X] = ValidationNel[String, X] })#L, Pattern.Chunk] {
val validation = chunks.validationNelTraverse[String, Seq[Pattern.Chunk]] {
case ChunkOrProperty.Prop(name, alternativesOpt) =>
properties.get(name) match {
case Some(value) =>
Vector(Pattern.Chunk.Const(value)).successNel
ValidationNel.success(Seq(Pattern.Chunk.Const(value)))
case None =>
alternativesOpt match {
case Some(alt) =>
PropertiesPattern(alt)
.substituteProperties(properties)
.right
.map(_.chunks.toVector)
.validation
.toValidationNel
ValidationNel.fromEither(
PropertiesPattern(alt)
.substituteProperties(properties)
.right
.map(_.chunks.toVector)
)
case None =>
name.failureNel
ValidationNel.failure(name)
}
}
case ChunkOrProperty.Opt(l @ _*) =>
PropertiesPattern(l)
.substituteProperties(properties)
.right
.map(l => Vector(Pattern.Chunk.Opt(l.chunks: _*)))
.validation
.toValidationNel
ValidationNel.fromEither(
PropertiesPattern(l)
.substituteProperties(properties)
.right
.map(l => Seq(Pattern.Chunk.Opt(l.chunks: _*)))
)
case ChunkOrProperty.Var(name) =>
Vector(Pattern.Chunk.Var(name)).successNel
ValidationNel.success(Seq(Pattern.Chunk.Var(name)))
case ChunkOrProperty.Const(value) =>
Vector(Pattern.Chunk.Const(value)).successNel
ValidationNel.success(Seq(Pattern.Chunk.Const(value)))
}.map(Pattern(_))
}.map(c => Pattern(c.flatten))
validation.toEither.left.map { notFoundProps =>
s"Property(ies) not found: ${notFoundProps.toList.mkString(", ")}"
validation.either.left.map { notFoundProps =>
s"Property(ies) not found: ${notFoundProps.mkString(", ")}"
}
}
}
@ -68,30 +67,30 @@ final case class Pattern(chunks: Seq[Pattern.Chunk]) {
def substituteVariables(variables: Map[String, String]): Either[String, String] = {
def helper(chunks: Seq[Chunk]): ValidationNel[String, Seq[Chunk.Const]] =
chunks.toVector.traverseU[ValidationNel[String, Seq[Chunk.Const]]] {
chunks.validationNelTraverse[String, Seq[Chunk.Const]] {
case Chunk.Var(name) =>
variables.get(name) match {
case Some(value) =>
Seq(Chunk.Const(value)).successNel
ValidationNel.success(Seq(Chunk.Const(value)))
case None =>
name.failureNel
ValidationNel.failure(name)
}
case Chunk.Opt(l @ _*) =>
val res = helper(l)
if (res.isSuccess)
res
else
Seq().successNel
ValidationNel.success(Seq())
case c: Chunk.Const =>
Seq(c).successNel
ValidationNel.success(Seq(c))
}.map(_.flatten)
val validation = helper(chunks)
validation match {
case Failure(notFoundVariables) =>
Left(s"Variables not found: ${notFoundVariables.toList.mkString(", ")}")
case Success(constants) =>
validation.either match {
case Left(notFoundVariables) =>
Left(s"Variables not found: ${notFoundVariables.mkString(", ")}")
case Right(constants) =>
val b = new StringBuilder
constants.foreach(b ++= _.value)
Right(b.result())

View File

@ -5,16 +5,49 @@ import scala.collection.mutable.ListBuffer
object Traverse {
implicit class TraverseOps[T](val seq: Seq[T]) {
def eitherTraverse[L, R](f: T => Either[L, R]): Either[L, Seq[R]] =
// Warning: iterates on the whole sequence no matter what, even if the first element is a Left
seq.foldLeft[Either[L, ListBuffer[R]]](Right(new ListBuffer)) {
case (l @ Left(_), _) => l
case (Right(b), elem) =>
f(elem) match {
case Left(l) => Left(l)
case Right(r) => Right(b += r)
seq
.foldLeft[Either[L, ListBuffer[R]]](Right(new ListBuffer)) {
case (l @ Left(_), _) => l
case (Right(b), elem) =>
f(elem) match {
case Left(l) => Left(l)
case Right(r) => Right(b += r)
}
}
.right
.map(_.result())
def validationNelTraverse[L, R](f: T => ValidationNel[L, R]): ValidationNel[L, Seq[R]] = {
val e = seq
.foldLeft[Either[ListBuffer[L], ListBuffer[R]]](Right(new ListBuffer)) {
case (l @ Left(b), elem) =>
f(elem).either match {
case Left(l0) => Left(b ++= l0)
case Right(_) => l
}
case (Right(b), elem) =>
f(elem).either match {
case Left(l) => Left(new ListBuffer[L] ++= l)
case Right(r) => Right(b += r)
}
}
.left
.map { b =>
b.result() match {
case Nil => sys.error("Can't happen")
case h :: t => ::(h, t)
}
}
}
.right
.map(_.result())
ValidationNel(e)
}
}
}

View File

@ -0,0 +1,27 @@
package coursier.util
// not covariant because scala.:: isn't (and is there a point in being covariant in R but not L?)
final case class ValidationNel[L, R](either: Either[::[L], R]) {
def isSuccess: Boolean =
either.isRight
def map[S](f: R => S): ValidationNel[L, S] =
ValidationNel(either.right.map(f))
}
object ValidationNel {
def fromEither[L, R](either: Either[L, R]): ValidationNel[L, R] =
ValidationNel(either.left.map(l => ::(l, Nil)))
def success[L]: SuccessBuilder[L] =
new SuccessBuilder
def failure[R]: FailureBuilder[R] =
new FailureBuilder
final class SuccessBuilder[L] {
def apply[R](r: R): ValidationNel[L, R] =
ValidationNel(Right(r))
}
final class FailureBuilder[R] {
def apply[L](l: L): ValidationNel[L, R] =
ValidationNel(Left(::(l, Nil)))
}
}