Merge pull request #796 from coursier/topic/less-scalaz

Less scalaz in core and cache
This commit is contained in:
Alexandre Archambault 2018-03-05 17:44:33 +01:00 committed by GitHub
commit 2d180e12d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 374 additions and 286 deletions

View File

@ -13,12 +13,10 @@ import coursier.internal.FileUtil
import coursier.util.Base64.Encoder
import scala.annotation.tailrec
import scalaz.Nondeterminism
import scalaz.concurrent.{Strategy, Task}
import java.io.{Serializable => _, _}
import java.nio.charset.Charset
import coursier.util.EitherT
import coursier.util.{EitherT, Schedulable}
import scala.concurrent.duration.{Duration, DurationInt}
import scala.util.Try
@ -320,11 +318,9 @@ object Cache {
private def contentLength(
url: String,
authentication: Option[Authentication],
logger0: Option[Logger]
logger: Option[Logger]
): Either[FileError, Option[Long]] = {
val logger = logger0.map(Logger.Extended(_))
var conn: URLConnection = null
try {
@ -361,19 +357,15 @@ object Cache {
}
}
private def download(
private def download[F[_]](
artifact: Artifact,
cache: File,
checksums: Set[String],
cachePolicy: CachePolicy,
pool: ExecutorService,
logger0: Option[Logger] = None,
ttl: Option[Duration] = defaultTtl
): Task[Seq[((File, String), Either[FileError, Unit])]] = {
implicit val pool0 = pool
val logger = logger0.map(Logger.Extended(_))
logger: Option[Logger],
ttl: Option[Duration]
)(implicit S: Schedulable[F]): F[Seq[((File, String), Either[FileError, Unit])]] = {
// Reference file - if it exists, and we get not found errors on some URLs, we assume
// we can keep track of these missing, and not try to get them again later.
@ -384,9 +376,9 @@ object Cache {
def referenceFileExists: Boolean = referenceFileOpt.exists(_.exists())
def fileLastModified(file: File): EitherT[Task, FileError, Option[Long]] =
def fileLastModified(file: File): EitherT[F, FileError, Option[Long]] =
EitherT {
Task {
S.schedule(pool) {
Right {
val lastModified = file.lastModified()
if (lastModified > 0L)
@ -401,9 +393,9 @@ object Cache {
url: String,
currentLastModifiedOpt: Option[Long], // for the logger
logger: Option[Logger]
): EitherT[Task, FileError, Option[Long]] =
): EitherT[F, FileError, Option[Long]] =
EitherT {
Task {
S.schedule(pool) {
var conn: URLConnection = null
try {
@ -445,19 +437,19 @@ object Cache {
}
}
def fileExists(file: File): Task[Boolean] =
Task {
def fileExists(file: File): F[Boolean] =
S.schedule(pool) {
file.exists()
}
def ttlFile(file: File): File =
new File(file.getParent, s".${file.getName}.checked")
def lastCheck(file: File): Task[Option[Long]] = {
def lastCheck(file: File): F[Option[Long]] = {
val ttlFile0 = ttlFile(file)
Task {
S.schedule(pool) {
if (ttlFile0.exists())
Some(ttlFile0.lastModified()).filter(_ > 0L)
else
@ -478,17 +470,17 @@ object Cache {
}
}
def shouldDownload(file: File, url: String): EitherT[Task, FileError, Boolean] = {
def shouldDownload(file: File, url: String): EitherT[F, FileError, Boolean] = {
def checkNeeded = ttl.fold(Task.now(true)) { ttl =>
def checkNeeded = ttl.fold(S.point(true)) { ttl =>
if (ttl.isFinite())
lastCheck(file).flatMap {
case None => Task.now(true)
S.bind(lastCheck(file)) {
case None => S.point(true)
case Some(ts) =>
Task(System.currentTimeMillis()).map(_ > ts + ttl.toMillis)
S.map(S.schedule(pool)(System.currentTimeMillis()))(_ > ts + ttl.toMillis)
}
else
Task.now(false)
S.point(false)
}
def check = for {
@ -504,22 +496,22 @@ object Cache {
}
EitherT {
fileExists(file).flatMap {
S.bind(fileExists(file)) {
case false =>
Task.now(Right(true))
S.point(Right(true))
case true =>
checkNeeded.flatMap {
S.bind(checkNeeded) {
case false =>
Task.now(Right(false))
S.point(Right(false))
case true =>
check.run.flatMap {
S.bind(check.run) {
case Right(false) =>
Task {
S.schedule(pool) {
doTouchCheckFile(file)
Right(false)
}
case other =>
Task.now(other)
S.point(other)
}
}
}
@ -547,9 +539,9 @@ object Cache {
def remote(
file: File,
url: String
): EitherT[Task, FileError, Unit] =
): EitherT[F, FileError, Unit] =
EitherT {
Task {
S.schedule(pool) {
val tmp = CachePath.temporaryFile(file)
@ -681,20 +673,20 @@ object Cache {
def errFile(file: File) = new File(file.getParentFile, "." + file.getName + ".error")
def remoteKeepErrors(file: File, url: String): EitherT[Task, FileError, Unit] = {
def remoteKeepErrors(file: File, url: String): EitherT[F, FileError, Unit] = {
val errFile0 = errFile(file)
def validErrFileExists =
EitherT {
Task[Either[FileError, Boolean]] {
S.schedule[Either[FileError, Boolean]](pool) {
Right(referenceFileExists && errFile0.exists())
}
}
def createErrFile =
EitherT {
Task[Either[FileError, Unit]] {
S.schedule[Either[FileError, Unit]](pool) {
if (referenceFileExists) {
if (!errFile0.exists())
FileUtil.write(errFile0, "".getBytes(UTF_8))
@ -706,7 +698,7 @@ object Cache {
def deleteErrFile =
EitherT {
Task[Either[FileError, Unit]] {
S.schedule[Either[FileError, Unit]](pool) {
if (errFile0.exists())
errFile0.delete()
@ -716,11 +708,11 @@ object Cache {
def retainError =
EitherT {
remote(file, url).run.flatMap {
S.bind(remote(file, url).run) {
case err @ Left(FileError.NotFound(_, Some(true))) =>
createErrFile.run.map(_ => err)
S.map(createErrFile.run)(_ => err: Either[FileError, Unit])
case other =>
deleteErrFile.run.map(_ => other)
S.map(deleteErrFile.run)(_ => other)
}
}
@ -728,7 +720,7 @@ object Cache {
case CachePolicy.FetchMissing | CachePolicy.LocalOnly | CachePolicy.LocalUpdate | CachePolicy.LocalUpdateChanging =>
validErrFileExists.flatMap { exists =>
if (exists)
EitherT(Task.now[Either[FileError, Unit]](Left(FileError.NotFound(url, Some(true)))))
EitherT(S.point[Either[FileError, Unit]](Left(FileError.NotFound(url, Some(true)))))
else
retainError
}
@ -738,7 +730,7 @@ object Cache {
}
}
def localInfo(file: File, url: String): EitherT[Task, FileError, Boolean] = {
def localInfo(file: File, url: String): EitherT[F, FileError, Boolean] = {
val errFile0 = errFile(file)
@ -752,12 +744,12 @@ object Cache {
else
Right(false)
EitherT(Task(res))
EitherT(S.schedule(pool)(res))
}
def checkFileExists(file: File, url: String, log: Boolean = true): EitherT[Task, FileError, Unit] =
def checkFileExists(file: File, url: String, log: Boolean = true): EitherT[F, FileError, Unit] =
EitherT {
Task {
S.schedule(pool) {
if (file.exists()) {
logger.foreach(_.foundLocally(url, file))
Right(())
@ -784,19 +776,19 @@ object Cache {
val requiredArtifactCheck = artifact.extra.get("required") match {
case None =>
EitherT(Task.now[Either[FileError, Unit]](Right(())))
EitherT(S.point[Either[FileError, Unit]](Right(())))
case Some(required) =>
cachePolicy0 match {
case CachePolicy.LocalOnly | CachePolicy.LocalUpdateChanging | CachePolicy.LocalUpdate =>
val file = localFile(required.url, cache, artifact.authentication.map(_.user))
localInfo(file, required.url).flatMap {
case true =>
EitherT(Task.now[Either[FileError, Unit]](Right(())))
EitherT(S.point[Either[FileError, Unit]](Right(())))
case false =>
EitherT(Task.now[Either[FileError, Unit]](Left(FileError.NotFound(file.toString))))
EitherT(S.point[Either[FileError, Unit]](Left(FileError.NotFound(file.toString))))
}
case _ =>
EitherT(Task.now[Either[FileError, Unit]](Right(())))
EitherT(S.point[Either[FileError, Unit]](Right(())))
}
}
@ -819,7 +811,7 @@ object Cache {
case true =>
remoteKeepErrors(file, url)
case false =>
EitherT(Task.now[Either[FileError, Unit]](Right(())))
EitherT(S.point[Either[FileError, Unit]](Right(())))
}
cachePolicy0 match {
@ -838,13 +830,10 @@ object Cache {
}
}
requiredArtifactCheck
.flatMap(_ => res)
.run
.map((file, url) -> _)
S.map(requiredArtifactCheck.flatMap(_ => res).run)((file, url) -> _)
}
Nondeterminism[Task].gather(tasks)
S.gather(tasks)
}
def parseChecksum(content: String): Option[BigInteger] = {
@ -887,14 +876,12 @@ object Cache {
.mkString))
}
def validateChecksum(
def validateChecksum[F[_]](
artifact: Artifact,
sumType: String,
cache: File,
pool: ExecutorService
): EitherT[Task, FileError, Unit] = {
implicit val pool0 = pool
)(implicit S: Schedulable[F]): EitherT[F, FileError, Unit] = {
val localFile0 = localFile(artifact.url, cache, artifact.authentication.map(_.user))
@ -903,7 +890,7 @@ object Cache {
case Some(sumUrl) =>
val sumFile = localFile(sumUrl, cache, artifact.authentication.map(_.user))
Task {
S.schedule(pool) {
val sumOpt = parseRawChecksum(FileUtil.readAllBytes(sumFile))
sumOpt match {
@ -934,12 +921,12 @@ object Cache {
}
case None =>
Task.now(Left(FileError.ChecksumNotFound(sumType, localFile0.getPath)))
S.point[Either[FileError, Unit]](Left(FileError.ChecksumNotFound(sumType, localFile0.getPath)))
}
}
}
def file(
def file[F[_]](
artifact: Artifact,
cache: File = default,
cachePolicy: CachePolicy = CachePolicy.UpdateChanging,
@ -947,22 +934,20 @@ object Cache {
logger: Option[Logger] = None,
pool: ExecutorService = defaultPool,
ttl: Option[Duration] = defaultTtl
): EitherT[Task, FileError, File] = {
implicit val pool0 = pool
)(implicit S: Schedulable[F]): EitherT[F, FileError, File] = {
val checksums0 = if (checksums.isEmpty) Seq(None) else checksums
val res = EitherT {
download(
S.map(download(
artifact,
cache,
checksums = checksums0.collect { case Some(c) => c }.toSet,
cachePolicy,
pool,
logger0 = logger,
logger = logger,
ttl = ttl
).map { results =>
)) { results =>
val checksum = checksums0.find {
case None => true
case Some(c) =>
@ -987,20 +972,20 @@ object Cache {
}
res.flatMap {
case (f, None) => EitherT(Task.now[Either[FileError, File]](Right(f)))
case (f, None) => EitherT(S.point[Either[FileError, File]](Right(f)))
case (f, Some(c)) =>
validateChecksum(artifact, c, cache, pool).map(_ => f)
}
}
def fetch(
def fetch[F[_]](
cache: File = default,
cachePolicy: CachePolicy = CachePolicy.UpdateChanging,
checksums: Seq[Option[String]] = defaultChecksums,
logger: Option[Logger] = None,
pool: ExecutorService = defaultPool,
ttl: Option[Duration] = defaultTtl
): Fetch.Content[Task] = {
)(implicit S: Schedulable[F]): Fetch.Content[F] = {
artifact =>
file(
artifact,
@ -1064,7 +1049,7 @@ object Cache {
} else
notFound(f)
EitherT(Task.now[Either[String, String]](res))
EitherT(S.point[Either[String, String]](res))
}
}
@ -1106,8 +1091,7 @@ object Cache {
val defaultConcurrentDownloadCount = 6
lazy val defaultPool =
Executors.newFixedThreadPool(defaultConcurrentDownloadCount, Strategy.DefaultDaemonThreadFactory)
lazy val defaultPool = Schedulable.fixedThreadPool(defaultConcurrentDownloadCount)
lazy val defaultTtl: Option[Duration] = {
def fromString(s: String) =
@ -1134,61 +1118,15 @@ object Cache {
def downloadedArtifact(url: String, success: Boolean): Unit = {}
def checkingUpdates(url: String, currentTimeOpt: Option[Long]): Unit = {}
def checkingUpdatesResult(url: String, currentTimeOpt: Option[Long], remoteTimeOpt: Option[Long]): Unit = {}
}
object Logger {
// adding new methods to this one, not to break bin compat in 2.10 / 2.11
abstract class Extended extends Logger {
def downloadLength(url: String, totalLength: Long, alreadyDownloaded: Long, watching: Boolean): Unit = {
downloadLength(url, totalLength, 0L, watching)
}
def downloadLength(url: String, totalLength: Long, alreadyDownloaded: Long, watching: Boolean): Unit = {}
def gettingLength(url: String): Unit = {}
def gettingLengthResult(url: String, length: Option[Long]): Unit = {}
}
object Extended {
def apply(logger: Logger): Extended =
logger match {
case e: Extended => e
case _ =>
new Extended {
override def foundLocally(url: String, f: File) =
logger.foundLocally(url, f)
override def downloadingArtifact(url: String, file: File) =
logger.downloadingArtifact(url, file)
override def downloadProgress(url: String, downloaded: Long) =
logger.downloadProgress(url, downloaded)
override def downloadedArtifact(url: String, success: Boolean) =
logger.downloadedArtifact(url, success)
override def checkingUpdates(url: String, currentTimeOpt: Option[Long]) =
logger.checkingUpdates(url, currentTimeOpt)
override def checkingUpdatesResult(url: String, currentTimeOpt: Option[Long], remoteTimeOpt: Option[Long]) =
logger.checkingUpdatesResult(url, currentTimeOpt, remoteTimeOpt)
}
}
}
def gettingLength(url: String): Unit = {}
def gettingLengthResult(url: String, length: Option[Long]): Unit = {}
}
var bufferSize = 1024*1024
def readFullySync(is: InputStream) = {
val buffer = new ByteArrayOutputStream()
val data = Array.ofDim[Byte](16384)
var nRead = is.read(data, 0, data.length)
while (nRead != -1) {
buffer.write(data, 0, nRead)
nRead = is.read(data, 0, data.length)
}
buffer.flush()
buffer.toByteArray
}
def withContent(is: InputStream, f: (Array[Byte], Int) => Unit): Unit = {
val data = Array.ofDim[Byte](16384)

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

@ -374,7 +374,7 @@ object TermDisplay {
class TermDisplay(
out: Writer,
val fallbackMode: Boolean = TermDisplay.defaultFallbackMode
) extends Cache.Logger.Extended {
) extends Cache.Logger {
import TermDisplay._

View File

@ -66,7 +66,7 @@ object FileUtil {
}
}
private def readFully(is: InputStream): Array[Byte] = {
def readFully(is: InputStream): Array[Byte] = {
val buffer = new ByteArrayOutputStream
val data = Array.ofDim[Byte](16384)

View File

@ -0,0 +1,42 @@
package coursier.util
import java.util.concurrent.{ExecutorService, Executors, ThreadFactory}
import scala.language.higherKinds
import scalaz.concurrent.{Task => ScalazTask}
trait Schedulable[F[_]] extends Gather[F] {
def schedule[A](pool: ExecutorService)(f: => A): F[A]
}
object Schedulable {
implicit val scalazTask: Schedulable[ScalazTask] =
new Schedulable[ScalazTask] {
def point[A](a: A) =
ScalazTask.point(a)
def schedule[A](pool: ExecutorService)(f: => A) =
ScalazTask(f)(pool)
def gather[A](elems: Seq[ScalazTask[A]]) =
ScalazTask.taskInstance.gather(elems)
def bind[A, B](elem: ScalazTask[A])(f: A => ScalazTask[B]) =
ScalazTask.taskInstance.bind(elem)(f)
}
def fixedThreadPool(size: Int): ExecutorService =
Executors.newFixedThreadPool(
size,
// from scalaz.concurrent.Strategy.DefaultDaemonThreadFactory
new ThreadFactory {
val defaultThreadFactory = Executors.defaultThreadFactory()
def newThread(r: Runnable) = {
val t = defaultThreadFactory.newThread(r)
t.setDaemon(true)
t
}
}
)
}

View File

@ -82,7 +82,7 @@ object Bootstrap extends CaseApp[BootstrapOptions] {
val bootstrapJar =
Option(Thread.currentThread().getContextClassLoader.getResourceAsStream("bootstrap.jar")) match {
case Some(is) => Cache.readFullySync(is)
case Some(is) => FileUtil.readFully(is)
case None =>
Console.err.println(s"Error: bootstrap JAR not found")
sys.exit(1)
@ -165,7 +165,7 @@ object Bootstrap extends CaseApp[BootstrapOptions] {
entry.setTime(f.lastModified())
outputZip.putNextEntry(entry)
outputZip.write(Cache.readFullySync(new FileInputStream(f)))
outputZip.write(FileUtil.readFully(new FileInputStream(f)))
outputZip.closeEntry()
}

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")}"
)
}
@ -515,11 +515,11 @@ class Helper(
errPrintln("\nMaximum number of iterations reached!")
}
if (res.metadataErrors.nonEmpty) {
if (res.errors.nonEmpty) {
anyError = true
errPrintln(
"\nError:\n" +
res.metadataErrors.map {
res.errors.map {
case ((module, version), errors) =>
s" $module:$version\n${errors.map(" " + _.replace("\n", " \n")).mkString("\n")}"
}.mkString("\n")

View File

@ -40,7 +40,7 @@ object Scaladex {
val b = try {
conn = new java.net.URL(url).openConnection().asInstanceOf[HttpURLConnection]
coursier.Platform.readFullySync(conn.getInputStream)
coursier.internal.FileUtil.readFully(conn.getInputStream)
} finally {
if (conn != null)
coursier.Cache.closeConn(conn)

View File

@ -2,8 +2,6 @@ package coursier.cli.util
import java.util.zip.{ZipEntry, ZipInputStream}
import coursier.Platform
object Zip {
def zipEntries(zipStream: ZipInputStream): Iterator[(ZipEntry, Array[Byte])] =
@ -17,7 +15,7 @@ object Zip {
def hasNext = nextEntry.nonEmpty
def next() = {
val ent = nextEntry.get
val data = Platform.readFullySync(zipStream)
val data = coursier.internal.FileUtil.readFully(zipStream)
update()

View File

@ -22,7 +22,7 @@ class CliBootstrapIntegrationTest extends FlatSpec with CliTestLib {
if (e == null)
throw new NoSuchElementException(s"Entry $path in zip file")
else if (e.getName == path)
coursier.Platform.readFullySync(zis)
coursier.internal.FileUtil.readFully(zis)
else
zipEntryContent(zis, path)
}
@ -53,7 +53,7 @@ class CliBootstrapIntegrationTest extends FlatSpec with CliTestLib {
val content = try {
fis = new FileInputStream(bootstrapFile)
coursier.Platform.readFullySync(fis)
coursier.internal.FileUtil.readFully(fis)
} finally {
if (fis != null) fis.close()
}

View File

@ -1,9 +1,8 @@
package coursier
import coursier.util.EitherT
import coursier.util.{EitherT, Gather, Monad}
import scala.language.higherKinds
import scalaz.{Monad, Nondeterminism}
object Fetch {
@ -59,12 +58,12 @@ object Fetch {
fetch: Content[F],
extra: Content[F]*
)(implicit
F: Nondeterminism[F]
F: Gather[F]
): Metadata[F] = {
modVers =>
F.map(
F.gatherUnordered {
F.gather {
modVers.map {
case (module, version) =>
def get(fetch: Content[F]) = find(repositories, module, version, fetch)

View File

@ -3,9 +3,8 @@ package coursier.core
import coursier.Fetch
import scala.language.higherKinds
import scalaz.Monad
import coursier.core.compatibility.encodeURIComponent
import coursier.util.EitherT
import coursier.util.{EitherT, Monad}
trait Repository extends Product with Serializable {
def find[F[_]](

View File

@ -1085,7 +1085,10 @@ final case class Resolution(
* Returns errors on dependencies
* @return errors
*/
def metadataErrors: Seq[(ModuleVersion, Seq[String])] = errorCache.toSeq
def errors: Seq[(ModuleVersion, Seq[String])] = errorCache.toSeq
@deprecated("Use errors instead", "1.1.0")
def metadataErrors: Seq[(ModuleVersion, Seq[String])] = errors
/**
* Removes from this `Resolution` dependencies that are not in `dependencies` neither brought

View File

@ -1,10 +1,10 @@
package coursier
package core
import coursier.util.Monad
import scala.annotation.tailrec
import scala.language.higherKinds
import scalaz.Monad
import scalaz.Scalaz.{ToFunctorOps, ToBindOps}
sealed abstract class ResolutionProcess {
@ -191,8 +191,11 @@ object ResolutionProcess {
.toVector
.foldLeft(F.point(Vector.empty[((Module, String), Either[Seq[String], (Artifact.Source, Project)])])) {
(acc, l) =>
for (v <- acc; e <- fetch(l))
yield v ++ e
F.bind(acc) { v =>
F.map(fetch(l)) { e =>
v ++ e
}
}
}
}

View File

@ -2,10 +2,9 @@ package coursier.ivy
import coursier.Fetch
import coursier.core._
import coursier.util.{EitherT, WebPage}
import coursier.util.{EitherT, Monad, WebPage}
import scala.language.higherKinds
import scalaz.Monad
final case class IvyRepository(
pattern: Pattern,

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

@ -3,10 +3,9 @@ package coursier.maven
import coursier.Fetch
import coursier.core._
import coursier.core.compatibility.encodeURIComponent
import coursier.util.{EitherT, WebPage}
import coursier.util.{EitherT, Monad, WebPage}
import scala.language.higherKinds
import scalaz.Monad
object MavenRepository {
val SnapshotTimestamp = "(.*-)?[0-9]{8}\\.[0-9]{6}-[0-9]+".r

View File

@ -1,7 +1,7 @@
package coursier.maven
import coursier.core._
import scalaz.Scalaz.{eitherMonad, listInstance, ToTraverseOps}
import coursier.util.Traverse.TraverseOps
object Pom {
import coursier.util.Xml._
@ -57,20 +57,23 @@ object Pom {
.map(_.children.filter(_.label == "exclusion"))
.getOrElse(Seq.empty)
xmlExclusions.toList.traverseU(module(_, defaultArtifactId = Some("*"))).right.map { exclusions =>
xmlExclusions
.eitherTraverse(module(_, defaultArtifactId = Some("*")))
.right
.map { exclusions =>
val optional = text(node, "optional", "").right.toSeq.contains("true")
val optional = text(node, "optional", "").right.toSeq.contains("true")
scopeOpt.getOrElse("") -> Dependency(
mod,
version0,
"",
exclusions.map(mod => (mod.organization, mod.name)).toSet,
Attributes(typeOpt.getOrElse(""), classifierOpt.getOrElse("")),
optional,
transitive = true
)
}
scopeOpt.getOrElse("") -> Dependency(
mod,
version0,
"",
exclusions.map(mod => (mod.organization, mod.name)).toSet,
Attributes(typeOpt.getOrElse(""), classifierOpt.getOrElse("")),
optional,
transitive = true
)
}
}
private def profileActivation(node: Node): (Option[Boolean], Activation) = {
@ -125,7 +128,9 @@ object Pom {
.getOrElse(Seq.empty)
for {
deps <- xmlDeps.toList.traverseU(dependency).right
deps <- xmlDeps
.eitherTraverse(dependency)
.right
depMgmts <- node
.children
@ -133,8 +138,7 @@ object Pom {
.flatMap(_.children.find(_.label == "dependencies"))
.map(_.children.filter(_.label == "dependency"))
.getOrElse(Seq.empty)
.toList
.traverseU(dependency)
.eitherTraverse(dependency)
.right
properties <- node
@ -142,8 +146,7 @@ object Pom {
.find(_.label == "properties")
.map(_.children.collect { case elem if elem.isElement => elem })
.getOrElse(Seq.empty)
.toList
.traverseU(property)
.eitherTraverse(property)
.right
} yield Profile(id, activeByDefault, activation, deps, depMgmts, properties.toMap)
@ -176,7 +179,7 @@ object Pom {
.map(_.children.filter(_.label == "dependency"))
.getOrElse(Seq.empty)
)
deps <- xmlDeps.toList.traverseU(dependency).right
deps <- xmlDeps.eitherTraverse(dependency).right
xmlDepMgmts <- point(
pom.children
@ -185,7 +188,7 @@ object Pom {
.map(_.children.filter(_.label == "dependency"))
.getOrElse(Seq.empty)
)
depMgmts <- xmlDepMgmts.toList.traverseU(dependency).right
depMgmts <- xmlDepMgmts.eitherTraverse(dependency).right
groupId <- Some(projModule.organization).filter(_.nonEmpty)
.orElse(parentModuleOpt.map(_.organization).filter(_.nonEmpty))
@ -211,7 +214,7 @@ object Pom {
.map(_.children.collect{case elem if elem.isElement => elem})
.getOrElse(Seq.empty)
)
properties <- xmlProperties.toList.traverseU(property).right
properties <- xmlProperties.eitherTraverse(property).right
xmlProfiles <- point(
pom
@ -220,7 +223,7 @@ object Pom {
.map(_.children.filter(_.label == "profile"))
.getOrElse(Seq.empty)
)
profiles <- xmlProfiles.toList.traverseU(profile).right
profiles <- xmlProfiles.eitherTraverse(profile).right
extraAttrs <- properties
.collectFirst { case ("extraDependencyAttributes", s) => extraAttributes(s) }
@ -307,7 +310,7 @@ object Pom {
Project(
finalProjModule,
version,
(relocationDependencyOpt.toList ::: deps).map {
(relocationDependencyOpt.toSeq ++ deps).map {
case (config, dep0) =>
val dep = extraAttrsMap.get(dep0.moduleVersion).fold(dep0)(attrs =>
dep0.copy(module = dep0.module.copy(attributes = attrs))
@ -426,8 +429,7 @@ object Pom {
.getOrElse(Seq.empty)
xmlSnapshotVersions
.toList
.traverseU(snapshotVersion)
.eitherTraverse(snapshotVersion)
.right
}
} yield {

View File

@ -1,7 +1,6 @@
package coursier.util
import scala.language.higherKinds
import scalaz.Monad
final case class EitherT[F[_], L, R](run: F[Either[L, R]]) {

View File

@ -0,0 +1,18 @@
package coursier.util
import scala.language.higherKinds
trait Gather[F[_]] extends Monad[F] {
def gather[A](elems: Seq[F[A]]): F[Seq[A]]
}
object Gather {
implicit def fromScalaz[F[_]](implicit N: scalaz.Nondeterminism[F]): Gather[F] =
new Gather[F] {
def point[A](a: A) = N.pure(a)
def bind[A, B](elem: F[A])(f: A => F[B]) = N.bind(elem)(f)
def gather[A](elems: Seq[F[A]]) = N.map(N.gather(elems))(l => l)
}
}

View File

@ -0,0 +1,21 @@
package coursier.util
import scala.language.higherKinds
trait Monad[F[_]] {
def point[A](a: A): F[A]
def bind[A, B](elem: F[A])(f: A => F[B]): F[B]
def map[A, B](elem: F[A])(f: A => B): F[B] =
bind(elem)(a => point(f(a)))
}
object Monad {
implicit def fromScalaz[F[_]](implicit M: scalaz.Monad[F]): Monad[F] =
new Monad[F] {
def point[A](a: A) = M.pure(a)
def bind[A, B](elem: F[A])(f: A => F[B]) = M.bind(elem)(f)
}
}

View File

@ -0,0 +1,53 @@
package coursier.util
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)
}
}
.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)))
}
}

View File

@ -3,10 +3,9 @@ package coursier
import java.io.{File, FileNotFoundException, IOException}
import java.net.{HttpURLConnection, URL, URLConnection}
import coursier.util.EitherT
import coursier.util.{EitherT, Monad}
import scala.language.higherKinds
import scalaz.Monad
object FallbackDependenciesRepository {

View File

@ -41,9 +41,6 @@ object CoursierPlugin extends AutoPlugin {
val coursierParentProjectCache = Keys.coursierParentProjectCache
val coursierResolutions = Keys.coursierResolutions
@deprecated("Use coursierResolutions instead", "1.0.0-RC4")
val coursierResolution = Keys.actualCoursierResolution
val coursierSbtClassifiersResolution = Keys.coursierSbtClassifiersResolution
val coursierDependencyTree = Keys.coursierDependencyTree

View File

@ -1,9 +1,8 @@
package coursier
import coursier.util.EitherT
import coursier.util.{EitherT, Monad}
import scala.language.higherKinds
import scalaz.Monad
final case class InterProjectRepository(projects: Seq[Project]) extends Repository {

View File

@ -51,8 +51,6 @@ object Keys {
private[coursier] val actualCoursierResolution = TaskKey[Resolution]("coursier-resolution")
@deprecated("Use coursierResolutions instead", "1.0.0-RC4")
val coursierResolution = actualCoursierResolution
val coursierSbtClassifiersResolution = TaskKey[Resolution]("coursier-sbt-classifiers-resolution")
val coursierDependencyTree = TaskKey[Unit](

View File

@ -781,17 +781,17 @@ object Tasks {
).throwException()
}
if (res.metadataErrors.nonEmpty) {
if (res.errors.nonEmpty) {
val internalRepositoriesLen = internalRepositories.length
val errors =
if (repositories.length > internalRepositoriesLen)
// drop internal repository errors
res.metadataErrors.map {
res.errors.map {
case (dep, errs) =>
dep -> errs.drop(internalRepositoriesLen)
}
else
res.metadataErrors
res.errors
ResolutionError.MetadataDownloadErrors(errors)
.throwException()

View File

@ -54,7 +54,7 @@ object CacheFetchTests extends TestSuite {
cleanTmpDir()
}
val errors = res.metadataErrors
val errors = res.errors
assert(errors.isEmpty)
}

View File

@ -45,7 +45,7 @@ abstract class CentralTests extends TestSuite {
.run(fetch0)
.map { res =>
val metadataErrors = res.metadataErrors
val metadataErrors = res.errors
val conflicts = res.conflicts
val isDone = res.isDone
assert(metadataErrors.isEmpty)
@ -183,7 +183,7 @@ abstract class CentralTests extends TestSuite {
): Future[T] = async {
val res = await(resolve(deps, extraRepos = extraRepos))
val metadataErrors = res.metadataErrors
val metadataErrors = res.errors
val conflicts = res.conflicts
val isDone = res.isDone
assert(metadataErrors.isEmpty)
@ -581,7 +581,7 @@ abstract class CentralTests extends TestSuite {
val res = await(resolve(deps))
val metadataErrors = res.metadataErrors
val metadataErrors = res.errors
val conflicts = res.conflicts
val isDone = res.isDone
assert(metadataErrors.isEmpty)
@ -619,7 +619,7 @@ abstract class CentralTests extends TestSuite {
val res = await(resolve(deps))
val metadataErrors = res.metadataErrors
val metadataErrors = res.errors
val conflicts = res.conflicts
val isDone = res.isDone
assert(metadataErrors.isEmpty)

View File

@ -1,11 +1,10 @@
package coursier
package test
import utest._
import coursier.maven.Pom
import coursier.core.compatibility._
import coursier.util.Traverse.TraverseOps
import coursier.maven.Pom
import utest._
object PomParsingTests extends TestSuite {
@ -217,7 +216,6 @@ object PomParsingTests extends TestSuite {
assert(result == expected)
}
'beFineWithCommentsInProperties{
import scalaz.Scalaz.{eitherMonad, listInstance, ToTraverseOps}
val properties =
"""
@ -258,7 +256,7 @@ object PomParsingTests extends TestSuite {
assert(node.label == "properties")
val children = node.children.collect { case elem if elem.isElement => elem }
val props0 = children.toList.traverseU(Pom.property)
val props0 = children.eitherTraverse(Pom.property)
assert(props0.isRight)

View File

@ -251,7 +251,7 @@ object ResolutionTests extends TestSuite {
assert(directDependencyErrors.isEmpty)
// metadataErrors have that
assert(res.metadataErrors == Seq((Module("acme", "missing-pom"), "1.0.0") -> List("Not found")))
assert(res.errors == Seq((Module("acme", "missing-pom"), "1.0.0") -> List("Not found")))
}
}
'single{

View File

@ -2,10 +2,9 @@ package coursier
package test
import coursier.core._
import coursier.util.EitherT
import coursier.util.{EitherT, Monad}
import scala.language.higherKinds
import scalaz.Monad
final case class TestRepository(projects: Map[(Module, String), Project]) extends Repository {
val source = new core.Artifact.Source {