mirror of https://github.com/sbt/sbt.git
Add coursier.util.ValidationNel
This commit is contained in:
parent
dc87950dc4
commit
a83df3e1c8
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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")}"
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue